Event loop output order

Write codeHard40 min

Your task

Predict what a page logs. program maps each callback name to the steps that callback runs, in order. The callback main runs first, as a task, at time 0. A step is one of:

  • ["log", text]: append text to the output.
  • ["busy", ms]: keep the main thread busy, moving the clock forward ms milliseconds.
  • ["timeout", name, delay]: schedule a timer for callback name, due at the current time plus delay (at least 0).
  • ["micro", name]: queue a microtask for callback name.
  • ["raf", name]: request an animation frame callback for name.

The event loop follows these rules:

  1. Microtask checkpoint. After every task and after every animation frame callback, run queued microtasks first in, first out, until the queue is empty. A microtask queued during the checkpoint runs in the same checkpoint.
  2. What runs next. Whenever the main thread is free (after main, after each timer task and after each frame, checkpoints included), look at the earliest timer (smallest due time, ties going to the timer scheduled first) and, if any animation frame callback is waiting, at the next frame. Frames happen only at multiples of 16 ms: the next frame is at the smallest multiple of 16 that is at least the current time and later than the previous frame. There is no frame at time 0.
    • If there is a timer and it is due at or before the next frame (or no frame is waiting), its callback runs as a task. The clock moves forward to its due time if that is later than the current time.
    • Otherwise the frame runs. The clock moves to the frame's time, and the animation frame callbacks requested before the frame began run in request order, each followed by a microtask checkpoint. Callbacks requested during the frame wait for a later frame.
  3. The end. The loop stops when no timer and no animation frame callback is waiting.

Every name a step mentions is a key of program, and every test program finishes. Return the output.

Examples, each showing the program passed to event_loop_log and the list it returns:

  • {"main": [["log", "A"], ["timeout", "t", 0], ["micro", "m"], ["raf", "r"], ["log", "B"]], "t": [["log", "T"]], "m": [["log", "M"]], "r": [["log", "R"]]} returns ["A", "B", "M", "T", "R"].
  • {"main": [["raf", "r"], ["timeout", "t", 16], ["log", "main"]], "r": [["log", "frame"], ["micro", "m"], ["raf", "r2"]], "m": [["log", "micro in frame"]], "r2": [["log", "next frame"]], "t": [["log", "timer"]]} returns ["main", "timer", "frame", "micro in frame", "next frame"]: the timer due at 16 runs before the frame at 16, and r2 waits for the frame at 32.
  • {"main": [["raf", "r"], ["timeout", "t", 5], ["busy", 40], ["log", "done"]], "t": [["log", "T"]], "r": [["log", "R"]]} returns ["done", "T", "R"]: main ends at 40, the overdue timer runs at once, and the frame at 16 was missed, so r runs at 48.

Examples

event_loop_log(program: dict) -> list[str]
Example 1
Inputevent_loop_log({"main": [["log", "A"], ["timeout", "t", 0], ["micro", "m"], ["raf", "r"], ["log", "B"]], "t": [["log", "T"]], "m": [["log", "M"]], "r": [["log", "R"]]})
Output["A", "B", "M", "T", "R"]
Example 2
Inputevent_loop_log({"main": [["raf", "r"], ["timeout", "t", 16], ["log", "main"]], "r": [["log", "frame"], ["micro", "m"], ["raf", "r2"]], "m": [["log", "micro in frame"]], "r2": [["log", "next frame"]], "t": [["log", "timer"]]})
Output["main", "timer", "frame", "micro in frame", "next frame"]
Example 3
Inputevent_loop_log({"main": [["raf", "r"], ["timeout", "t", 5], ["busy", 40], ["log", "done"]], "t": [["log", "T"]], "r": [["log", "R"]]})
Output["done", "T", "R"]