[MUSIC PLAYING] PAUL LEWIS: So I was on the
way to the office this morning, and I realized it is very
much like web dev, rush hour. SURMA: OK. PAUL LEWIS: Rush hour
is like all the traffic. Everyone is trying to get to
the office at 9:00 AM, right? So all these people are in
their cars, and on trains, and everything. Everyone's just
rushing, and nobody can move for anybody else. And I think that's
like web because-- SURMA: Yes, please
explain this a bit more? PAUL LEWIS: Yes. Because you've got
the main thread. You've got this one thread. And on that main thread,
you've got all these work styles, JavaScript,
layout, paint, composite, your framework. Everything is running,
and everybody's competing for this one resource,
the road or, in this case, the main thread. SURMA: Oh, so Mr. Framework
has a car, Mr. Paint has a car, Mr. Business Logic has a car. And they all just want
to go on the road, but it's already full. PAUL LEWIS: Exactly. And everybody's in
the same boat where everybody gets the angry tweets
and sees all this performance advice. And nobody knows what to do
because all this stuff is just constrained into
this one place-- rush hour. As we described in
that video, that's kind of how we feel when we
look at the web at large. We look at it, and we go,
all this code should be here. But it just feels like the
traffic is the problem. There's just too much going
through the main thread. SURMA: And traditionally,
the main thread is full. It's overworked and underpaid. You would say, cool,
I'd use threads. On any other platform,
you could do that. Just spin up a thread, put
some code there, run it there, call a function. Hooray! Everyone's happy. But it turns out JavaScript
and the web is special. And it is inherently single
threaded, so you can't do that. PAUL LEWIS: Right, exactly. Every thread is kind of its
own little universe, isn't it? Like we heard, it's
a [INAUDIBLE],, right? And so you can't just go, just
call this on another thread, but you've got some shared
stuff that you can work on. So that's a challenge. And then it gets
more interesting because say, for example,
you're trying to build-- I don't know-- a chess game
just for argument's sake. And you've got a chess engine. And the chess engine-- it takes a few
hundred milliseconds to calculate a move. SURMA: It gets
exponentially difficult. PAUL LEWIS: And you
build it with DOM. And then some bright spark goes,
do you know what we should do? 3D. And you're, like,
[LAUGHS] I was already behind on my [INAUDIBLE] budget. If you could just not do the
60-frames-a-second thing, that'll be great. And some even brighter
spark says, how about VR? SURMA: Yeah. I want to stand on
the chess board. PAUL LEWIS: Yeah, I want to
be, like, right in the game. And you're thinking, um,
there was already rush hour. SURMA: Turns out frame
rate-- quite important when it comes to VR. PAUL LEWIS: Yeah, and
it could be voice. There are so many
things that it could be. SURMA: So this becomes
increasingly unlikely for you to be able to do this
successfully on the web currently. PAUL LEWIS: Right. And so this is the question that
we have been thinking through for the last little while. Is there anything we can
do, anything we can suggest, think of to help? SURMA: We have two birds. We're looking for a stone. PAUL LEWIS: Exactly. And-- [LAUGHTER] SURMA: Ah, that went well. PAUL LEWIS: OK. Wow, I really need to think of
what I was going to say next. OK, this. SURMA: Actor model. So we kind of
stumbled over this. The actor model is, as it says
right here, about 45 years old. And it's been made
popular by Erlang and then continued
with Elixir and Pony. It's languages that use
the actor model to this day and successfully so. And we realized
that it's actually a really good fit for the web. PAUL LEWIS: Yeah. Because what it does
is it makes a feature of that single-threadedness
of JavaScript. But we like to explain--
if you've come across it before, great. If you've not come
across the actor model, we like to explain it
in a very specific way. So check this one out. SURMA: So when we
did Supercharge, you saw us on screen. But behind the cameras,
we had an entire crew. And that means we had one
person working the camera. We had one person worrying
about if our audio was good. We had a director. And each of these people
were solely responsible for that specific device. PAUL LEWIS: And instead of going
over and pressing one person's buttons or just
messing with settings, those people actually have to
communicate with one another to get the job done. It you like, there are
actors in the system, but they've got to send
messages to one another and communicate and
collaborate in order to get the final thing working. SURMA: So that's kind of where-- more video, more
production value. It's good, right? So that's where we
see a mentality that fits the web really well. And you start
thinking about, where can you draw a line
for individual pieces of responsibility in your app. And instead of thinking
about classes and how you call the method
on the other class, you can now think
about these actors and how you can send the right
message to request something to happen. PAUL LEWIS: Right. There are these
areas of ownership. So let's think about, how,
at a conceptual level, how would you
think this through? What would it look
like a little bit? OK. So imagine you have
an actor, and it's job is to run your user interface. That's its area of ownership. That's what it does. That's its job and only that. You might also
have another actor whose job is to handle
state for your application and yet another one who
handles the storage. Now, imagine in your app
a typical interaction would be something like
favoriting an item. The user interface, when
the user taps on it, it will send a message
over to the state that says this was favorited. In turn, the actor
handling the state will send a message
to the storage to say we need to remember
that they favorited this item. Now we could also,
at this point, introduce a new actor
into this story, something that could broadcast. Because when the state changes,
typically, what we'd want to do is, we'd want to send
that both to the user interface and the storage to be
reflected in both, I suppose, really. SURMA: Yes. And you can kind of see
here, that it really is a separation of concerns. Ah, the click, there you go. [INAUDIBLE] transition--
do recommend. It really helps you to
think about your app in a different way,
helping you to figure out where does new code go, which
module can you switch out to fix a problem
that you're having? It's a really good way to
structure the app in this way. PAUL LEWIS: Yeah, absolutely. And we use to
separation of concerns when we talk about HTML,
JavaScript, and CSS, or when we talk about components
in a modern framework. So it's another version of
that same story I suppose. SURMA: Another
benefit that you get-- and we have heard about this
problem a couple of times, I think yesterday and today-- is that we often see
big chunks of monolithic JavaScript just run-- frameworks updating the
virtual DOM, and then the DOM, or something like that. And with this pattern, you
introduce a natural breaking point where you give the browser
a chance to ship a frame. Because every time
you send a message, there is a point where
you say, OK, browser can intervene and ship a frame
if we are out of frame budget. PAUL LEWIS: Exactly. Now, a little side effect
a positive one of this is location independence. And we'll come back to this. This is a sort of
repeating refrain that we're going to get
into a little bit more. But think about actors as,
they're not all the same. They have different
requirements. Some actors will not need to
have access to the main thread, for example, because of
the kind of work they do. And as such, we might
be able to run them in different locations,
i.e., not on the main thread. As I say, we'll
come back to that. But the idea here
is maybe we just bought ourselves a little
capacity for rush hour. SURMA: Because as
long as the messages get delivered to
the actor, the actor will then do the same work
as it did before and will respond with the same message. So the entire app keeps
behaving the same way, no matter where it runs. And because of that
location independence, we can lower the
likelihood of long work impacting the main thread
and making your app janky. PAUL LEWIS: Exactly. So conceptually,
that is what it is. But I like seeing code. I think code helps. And so we're not
launching a product. We're not launching a
framework, or even a library. We just wanted to have a chat
with you about architectures. And we've been using
some actor-based stuff for the last little while
with our colleague Tim. And the three of us have
just been putting some code together. So what we're going
to do is, we're just going to show you a
little bit of the code that we've been using. We've been using it to
build some of our apps. SURMA: And we'll
share that at the end. You're very welcome
to try it out. And you're also very
welcome to write your own. We don't really care if we use
our version or someone else's version. It's more about the concept,
about the architecture. PAUL LEWIS: Absolutely. But with this in mind, let's
talk about a particular app, something like a stopwatch
app, which you would start, it would count up in seconds-- pause, play, that kind of thing. And then you might reset
the time if you're done. So in our code, we have
this Actor base class. And that top function up there,
hookup, is the first thing that you'd need to know. And the job of hookup
is to kind of register an actor in the system so
that we can talk to it later, so we can send it
messages later on. Because ultimately,
we won't know where this actor is in the system. And so we just need almost
like a registry, I suppose, where we can say, I'm going
to tell you there's an actor, and it's found under this name. SURMA: It's basically
the equivalent of what you might know
from custom elements. There's custom
elements that. define. And you say, this custom element
is now known under this name. And this does the exact
same thing but for actors. PAUL LEWIS: So then we have
our two actors, a Clock one and a UI one. And then in the bootstrap,
we instantiate both our UI and hook it up, so
it's available under UI as a string name. And then we do the same
with the clock like so. So now, we can talk about how
you might implement something like the clock itself. And in our case, when you've
got something like this, it's almost like
a pure data actor. It doesn't have any
need to go near the DOM. It just wants to
tick, and pause, and all those kinds of things. SURMA: I mean, what
do you need, really. You need a set interval,
and that's pretty much it. PAUL LEWIS: So time out? SURMA: Sure. PAUL LEWIS: I have a thing
against set interval. It's a long story. Come find me after,
and I'll explain why. Anyway, imagine this then. We're going to model
this as a state machine. We start with a paused state. Our clock is paused. We can transition
to a running state. Every second, we'll tick, and
we'll go to the tick state. And that will take us
back to the running state. And you can imagine being
in this tick, running, tick, running state like in a clock. SURMA: Like ping-pong. PAUL LEWIS: Like
a clock, Indeed. We could pause,
and we could reset. And when we reset,
we go back to that. SURMA: And that's a
really nice pattern here that plays along well with the
message passing of the actor model. Because all of these
triggers, as they are called, in the state machine
world could just be a message. You send a message to this state
machine, it's being ingested, and then a transition happens. PAUL LEWIS: Absolutely. SURMA: So we found,
actually, that there are a lot of implementations
for state machines out there in the wild, and
that's not very unexpected. And we have kind of
been using xstate, which is written by
David from Microsoft, and it's working really well. It allows you to declare your
state machine as a JSON object. So you just declare
your states and then what the transitions are. And then you just pass this
to this machine constructor, and you get a state machine. PAUL LEWIS: So our clock
extends the Actor base class, and what we do is we
instantiate our state Machine. We say, go to the
initial state, which happens to that paused state. And then later on, imagine
that we receive a message. And our Actor base class
has this onMessage callback, which is, I got a message. What do I do? In this case, we would
assume that the state would be changing inside
of our state machine. So we use the state
machine transition to get from wherever it was
to wherever it needs to be. So the message is basically
driving the clock. And we'll inspect what
the new state value is. So if we find that
our clock is running, we'll set a tick time
out for one second. If we tick, we increment
our tick count. And then the clock will
send itself a message. Now, it could call
its own functions. But we tend to be a little bit-- we like it fair. And so what we do is we make
sure the clock sends itself a message like every
other actor would have to send it a message. SURMA: And you can add it
like a message queue that buffers all the messages. And the actor goes through
one message, processes, then it goes to
the next message. And so if you just
call your own function, it would kind of be
cutting that line. So if you want to
keep it fair, you would just queue your
app message at the end and wait like a good human. PAUL LEWIS: Exactly,
or a computer. SURMA: Sure. PAUL LEWIS: Cancel
the tick if you're paused if there's one pending. When we reset, we'll
reset the tick count. And again, the clock will send
itself a message to pause. And now, let's talk
about sending messages. That's next, yes. So the clock, is going
to have to send a state update to the user
interface so that it can reflect the time going
up and so on, and so forth. And this is the
opposite to the hookup. This is lookup,
which is also in-- SURMA: Hookup, lookup, hookup,
lookup, hookup, lookup. PAUL LEWIS: I lose him for hours
at a time when he does this. OK, hookup and lookup. So we look up the UI, and
then we can send it a message. SURMA: And that's very
important to note here. The handle, this UI
variable that we have there, it is not the actor instance. You can't go in and change a
member variable of that class. It is just an object with a send
method and only that method. Because that's the
only way you're allowed to interact with
any of the other actors. PAUL LEWIS: Exactly. And so in this case, we're
going to send the UI a message. And the message is
going to say what the time is and whether or
not the clock is running. We found that TypeScript is
really helpful at this point, because those messages
need to be well-formed, and well-understood. And there needs to
be a data contract. And we've just found,
from practical experience, that TypeScript is a
really good way of saying, this object looks like this. This is a number. This is a string. This is another object,
and so on and so forth. So just take that as
what it is, really. SURMA: A recommendation. PAUL LEWIS: Yeah,
a recommendation. We found that useful. Let's talk about
the UI a little bit. Interestingly, you can
bring your own framework. SURMA: Yeah. In this model, we don't really
care what kind of framework you use. You can use React, you can
use Vue, you can use Lit, you can use Svelte-- whatever
you feel comfortable with or whatever makes
sense in your scenario. The interesting shift here is
that the UI framework is not your base platform, not
your entry point any more. The center of the universe
has kind of moved. PAUL LEWIS: It's that
bootstrap that we showed [? at the top. ?] SURMA: UI is just one
participant of many in the system of actors. PAUL LEWIS: And if you find
that it's not behaving well, for whatever reason,
you can swap it out. The only thing it
has to do is listen to messages of a particular
type, which is kind of cool. In our case, then we're
going to use Preact. It works just great. So we're going to import preact. Our UI extends the Actor class. And when it receives
a message, we're going to have it render using
Preact to, in this case, the document body. SURMA: Same again here. We need to send messages back. PAUL LEWIS: Absolutely. And to do that, it is a
case of, on the UI side, we find our clock actor,
we do a lookup on it, and we will send it a
message, in this case, for this particular example,
sending it a message to start. There'll be one for stop,
or reset, and so on. Now, we get to talk
about that location independence a teeny bit more. Because one of the questions
that Surma, Tim, and I ask ourselves when we're
making our actors is, does this actor need
access to the main thread? And this is kind of to do
with the rush hour thing. Our general rule of
thumb-- and there are some caveats we'll
mention in a moment. But out general rule of thumb is
that a UI actor is the one that really needs the DOM. And therefore, it's
the one that ought to be on the main thread
wherever possible. There is an exception. The exception here is
that certain APIs-- those for media security, device
capabilities, and identity-- are only available on
the main thread today. SURMA: We think it's a bug. And we've been talking
to some Chrome engineers about exposing
these kind of APIs in a worker and somewhere else. But that's just not the
world we live in today. So for now, that's
a restriction. PAUL LEWIS: Tools not rules-- so you might be thinking, ah, I
should move all my actors away from the main thread. We'll get to that. But the thing is, if you've
got a really chatty actor that needs to talk to
the UI actor, you might want to leave it alongside
the UI actor on the method. Because as Jason and
Shubhie were talking about earlier, that there's
a cost to the thread hop, and that might be more expensive
than just sending a message and just keeping the actor
alongside the UI actor, OK? SURMA: So basically, if you
want to do that, just measure and see what the impact is. PAUL LEWIS: Exactly. So the location independence--
for all that notwithstanding, imagine we were
back here where we started with our four actors. And they're all on the main
thread, which is probably where we put them by default.
We're sort of saying, you might want to look
at it more like this. And you might be thinking, why
do they say, not main thread? Surely, they just
meant Web Workers. And we kind of did because,
in most cases, when we build these apps, web
workers do feature heavily. We do move quite a lot of
our actors to web workers, especially if
they're non-chatty. SURMA: But not quite. So we actually
think, or have tried, that it's sometimes very,
very useful to run an actor, for example, on the server side. And this is kind of
an interesting jump to make because it
allows you to incorporate your back end into
the architecture of your entire app. It is just another
actor in the system. And as a matter
of fact, the game that you've been
playing all day-- and it totally
had no bugs at all whatsoever-- is actually
written in this model. So every player that
is playing is an actor. The admin panel that the MCs use
to control the app is an actor. The presentation view--
just another actor. And then the Firebase Storage-- a shared actor that's
running on the server side. PAUL LEWIS: Now
interestingly, the mechanism by which they chat using
the hookup and lookup can be anything. It could be Fetch. It could be a WebSocket. And it really doesn't matter. SURMA: Your Firebase database. PAUL LEWIS: Yeah. So long as talk,
these actors can talk, and they've got a way of
sending messages to one another, you're all set. So back to our
original question. Did we actually
help with rush hour? Would this actually help? Well, let's review. SURMA: So one thing
that we definitely did achieve is that we are
making it less likely to have big chunks of uninterruptable
JavaScript and more little chunks where the
browser can stop it in between and ship a frame. So that's definitely one
advantage that we have. PAUL LEWIS: The
location independence, hopefully, some of our actors
can be run successfully away from the main thread. Hopefully, it's like fewer
cars on the road at rush hour. That's good for
everybody typically. SURMA: And as a
result, a lot of work that can happen in
an unexpected way, if you're processing a big
API response to something, that can happen in the worker
and not affect your main thread from going into jank mode. PAUL LEWIS: There are
some other benefits that Surma, Tim, and I have
noticed as well working in this particular pattern. One is better testing. With that kind of
area of ownership, it's easier to look
at an actor and go, oh, well I know
what you should do. And you've got an
onMessage that I can call, and I can make sure that
you do the right things. So the testing seems to
become a little bit easier. SURMA: Yeah. Off on the other side,
you can mock another actor by just implementing the
messages that that actor needs to receive and not
do the actual work, but just send prerecorded
messages back. PAUL LEWIS: You have
a clear separation of concerns, which
again, it helps you in terms of maybe dividing
the work with your teammates or even just
deciding for yourself which actor needs to be
responsible for this part of the system? SURMA: And you get
the code splitting. Because you have actors that
can be hooked up to the system at any point in time,
really, it allows you to split them up
and load them lazily. You can just import
them when you need them. PAUL LEWIS: Yeah. That's good. And bring your own framework. If you want to use a particular
library or framework, you can. There's not a prescriptive way. If you want to use the
[? one ?] thing, use it. Great. Now, there are some
considerations in this world, in this setup that we described. One is actor
performance challenges. If you imagine your
UI actor, imagine it decides to run along and
just be not very yieldy. You still have that problem. It's no different to a
process or an application in an operating system deciding
it's going to hog the CPU. This is not going to go away. But we do think
that the Scheduler API that Jason and Shubhie
mentioned in the previous talk is a huge part of this story. Because it's a great way
for individual actors to start breaking their
work up into smaller chunks. You may also be
sitting there going, I'm not sure I could
actorize my blog. And we would agree. It's not necessarily
for all use cases. This works really
well when you've got apps and in particular
apps where you think, actually, I can ring fence. I can mark off particular
parts of this application. And I can have some
kind of owner for it. That's where it
works really well. SURMA: And there's definitely a
different mental model to this. As I said, it kind
of shifts the center of the universe away
from the UI framework into many center pieces--
all the actors are just communicating. So it definitely took us a
time to build an intuition for where do we draw the line? What becomes an actor? What is just part of an
already existing actor? What kind of messages
should we send? How granular should
this entire set up be? So if it seems weird
at first, if you're playing around with this, that
is kind of to be expected. It's a very different way
of architecting a web app. PAUL LEWIS: So that
was the rush hour bit. That future-facing stuff
like the VR, AR, and so on-- well, we have some
thoughts there too. Watch this. So you were talking
about actors before. And I want to talk
about cameras. And the reason I want
to talk about cameras is because it all plays
into that story, right? So if can, have one of these
lenses before I drop it, because I don't want to. Your modern camera
has two bits-- the camera body, which
is the thing that holds the state when you're
shooting in JPEG or RAW-- SURMA: Yeah. It's like the business logic. It knows how to take the
picture and how to store it and it does all that. PAUL LEWIS: Yeah. Whether you're shooting video,
or taking photos with it, or whether you are
autofocus or manual focus-- you get the idea. Similar to like
a web app, that's the state of what's going on. But you've got different
lenses for different tasks. So that one would be something
like a portrait lens. This one might be a wide-angle-- take something of a landscape. SURMA: As long as we make sure
that the mounts are compatible, which I guess, in
actor world, means that they speak with the
same messages to each other. PAUL LEWIS: Yes. So the messages that they
send are really important. They're standardized, right? And everybody kind of plays
to that same data contract. Other than that, you
can do what you like. SURMA: Plug it in. PAUL LEWIS: Off you go. SURMA: Last video, I promise. PAUL LEWIS: Yeah. So camera lenses--
why camera lenses? How does that apply
to this story? When we talked
about this earlier, I think very naturally, we would
have all thought of the DOM. We would have thought
of, in the chess game, we would have thought
of this version. But there's the freedom
that you get from a UI actor that, as long as it can
speak the right messages, it can be implemented in
different technologies. So you could have a
different actor that does 3D. And now you get that. It just has to send the same
messages as the standard DOM version. Or maybe one full
XR, similarly, it needs to be able to do that-- send the right messages. And perhaps something
like Voice as well-- similar kind of story. So as a byproduct
of the actor model, we've all of a sudden got
these different swappable UI actors that we can take with
us wherever we need to go. And you might say,
well, actually, all I need in this particular
app is the XR and Voice. Or maybe I want DOM and Voice. SURMA: And you could
even imagine a setup where you have a DOM actor with
lots of effects and visuals and one DOM actor implements
the same app, but with a much less intense memory consumption
version, like a low-end version of your website. And once you detect that
the device we're running on is actually kind of
struggling to keep up. You can actually switch
it out in the middle of the app and downgrade to
the lower visual version. PAUL LEWIS: Or one would say
reduced motion or something like that. And technically,
this will be called multi-view or
multi-modal, which is a very fancy way of saying
there are lots of ways to interact with your web app. And as a byproduct,
within the actor model, it enables you to
do that pretty well. So if you're interested
in looking at the hookup and lookup of the
Actor base class that we mentioned
earlier, this would be the place to go if you
want to take a snap at that. SURMA: This is actually, not
only the Actor base class, but it's actually
a boiler plate. So it gets you started. There's rollup configured
where you can just build it, does the code splitting,
the lazy loading. It should get you
started really quickly, so you can start
writing actors and get a feel for how it feels. PAUL LEWIS: Let's be clear. It's experimental. It's just the stomping
ground that we've been using for the
last little while. And we'd love you to take a
look and have a chat with us and just tell us what you think. So in summary, we're actually
quite excited that something from 45 years ago has
kind of come full circle, and it seems to be the thing-- SURMA: It's been
hiding in plain sight. PAUL LEWIS: Yeah. And from some of the
places in computing, we've kind of brought it
over, not in a purist way. We have our own take
on these things. But it respects that
single-threadedness of JavaScript. It seems to help with rush hour. And it also seems to enable
us to go multi-modal, which is really kind of exciting. And on that note, thanks. SURMA: Thank you very much. [MUSIC PLAYING]