Programming Servo: A generic “worker event-loop”
Let’s take a look at how in Rust you can have an algorithm generic over T, where T is further bound by a trait, which itself is generic over one of several parameters.
A little introduction to workers and their event-loops in the web
The HTML living standard tells us the following with regards to the event-loops of an user agent:
To coordinate events, user interaction, scripts, rendering, networking, and so forth, user agents must use event loops as described in this section. There are two kinds of event loops: those for browsing contexts, and those for workers.
A bit more on the worker event-loops:
WorkerGlobalScopeobject has a distinct event loop, separate from those used by units of related similar-origin browsing contexts. This event loop has no associated browsing context, and its task queues only have events, callbacks, and networking activity as tasks. These event loops are created by the run a worker algorithm.
Furthermore, there are several kinds of workers:
There are two kinds of workers; dedicated workers, and shared workers.
And the W3c also defines a “service worker.”
Lastly, the living standard also writes that “The global scope is the “inside” of a worker”.
Workers in Servo
As a result of the above, Servo ended-up with several worker “global scopes”, each with their own “run the event-loop” algorithm. While working on a related issue, I realized that I was often making the same changes in both of those worker event-loops, which was error prone. So I set to out to turn that code into one “generic worker event-loop”.
This proved harder then I though, because what I needed was a
run_worker_event_loop<T> sort of algorithm, where
T was a type of “Worker Globalscope” and a replacement for
&self. But since
&self was previously used to access some capabilities of the “Worker Globalscope”, I would need
T to implement a trait giving
run_worker_event_loop access to these capabilities of
Yet those capabilities themselves required to be generic because each event-loop consisted of handling different messages on different channels, and then handling those messages as different type of event-loop specific events.
run_event_loop differed in some ways, for example because the “dedicated worker global scope” takes this
worker argument, and handles it as part of each iteration of the event-loop, whereas the “service worker global scope” lacks that concept.
At first, I tried to have some sort of “double generic” setup looking like
run_worker_event_loop<T<TimerMsg, WorkerMsg, Event>> , where I wanted to express something like
run_worker_event_loop is generic over
T, which itself is generic over
<TimerMsg, WorkerMsg, Event> , and the Rust compiler didn’t seem to appreciate that approach a lot.
In the end, the solution came in the form of a
run_worker_event_loop<T, TimerMsg, WorkerMsg, Event>, where
TimerMsg, WorkerMsg, Eventare arguments passed on to the underlying trait bound on
T: WorkerEventLoopMethods<TimerMsg = TimerMsg, WorkerMsg = WorkerMsg, Event = Event>. Let’s take a look into what that exactly means…
So our “worker global scope”
T, will have to implement
The trait itself is generic with regards to three “associated types”
TimerMsg, WorkerMsg, Event .
This gives the generic event-loop a few ‘hooks’ to access the specific capabilities of each “worker global scope”, which are needed for the parts of the event-loop that are specific to the “worker global scope” at hand.
Those ‘hooks’ themselves require a few generic parameters, because each “worker global scope” receives different types of messages, and expects different types of events resulting from those.
Here is what an implementation looks like:
As you can see, all that the implementation does is “make available” that which is specific to the
DedicatedWorkerGlobalScope , it doesn’t contain any event-loop logic, just the stuff that the generic event-loop will need to specific it’s behavior for a given “worker global scope”.
A generic worker event-loop
At the end of the day, we get a generic event-loop logic, using
WorkerEventLoopMethods for the parts that are worker-specific.
Through our use of associated items on the trait, we’re able to express the fact that
T is a struct implementing a generic trait, in a way that the compiler appreciates…