.. _design_events_and_hooks:

Events and Hooks
================

Session management is all about reacting to events and taking necessary
actions. This is why WirePlumber's logic is all built on events and hooks.

Events
------

Events are objects that represent a change that has just happened on a PipeWire
object, or just a trigger for making a decision and potentially taking some
action.

Every event has a source, a subject and some properties, which include the
event type.

* The ``source`` is a reference to the GObject that created this event.
  Typically, this is the ``WpStandardEventSource`` plugin.
* The ``subject`` is an *optional* reference to the object that this event
  is about. For example, in a ``node-added`` event, the ``subject`` would be
  a reference to the ``WpNode`` object that was just added. Some events,
  especially those which are used only to trigger actions, do not have a
  subject.
* The ``properties`` is a dictionary that contains information about the event,
  including the event type, and also includes all the PipeWire properties of the
  ``subject``, if there is one.
* The ``event.type`` property describes the nature of the event, for example
  ``node-added`` or ``metadata-changed`` are some valid event types.

Every event also has a priority. Events with a higher priority are processed
before events with a lower priority. When two or more events have the same
priority, they are processed in a first-in-first-out manner. This logic
is defined in the *event dispatcher*.

Events are short-lived objects. They are created at the time that something is
happening and they are destroyed after they get processed. Processing an event
means executing all the hooks that are associated with it. The next section
explains what hooks are and how they are associated with events.

Hooks
-----

Hooks are objects that represent a runnable action that needs to be executed
when a certain event is processed. Every hook, therefore, consists of a
function - synchronous or asynchronous - that can be executed. Additionally,
every hook has a means to associate itself with specific events. This is
normally done by declaring *interest* to specific event properties or
combinations of them.

There are two main types of hooks: ``SimpleEventHook`` and ``AsyncEventHook``.

* ``SimpleEventHook`` contains a single, synchronous function. As soon as this
  function is executed, the hook is completed.
* ``AsyncEventHook`` contains multiple functions, combined together in a state
  machine using ``WpTransition`` underneath. The hook is completed only after
  the state machine reaches its final state and this can take any amount of time
  necessary.

Every hook also has a name, which can be an arbitrary string of characters.
Additionally, it has two arrays of names, which declare dependencies between
this hook and others. One array is called ``before`` and the other is called
``after``. The hook names in the ``before`` array specify that this hook must
be executed *before* those other hooks. Similarly, the hook names in the
``after`` array specify that this hook must be executed *after* those other
hooks. Using this mechanism, it is possible to define the order in which
hooks will be executed, for a specific event.

Hooks are long-lived objects. They are created once, registered in the
*event dispatcher*, they are attached on events and detached after their
execution. They don't maintain any internal state, so the actions of the hook
depend solely on the event itself.

The Event Dispatcher
--------------------

The event dispatcher is a (per core) singleton object that processes all events
and also maintains a list of all the registered hooks. It has a method to
*push* events on it, which causes them to be scheduled for processing.

Scheduling of events and hooks
------------------------------

The main idea and reasoning behind this architecture is to have everything
execute in a predefined order and always wait for an action to finish before
executing the next one.

Every event has a *priority* and every hook also has an order of execution that
derives from the inter-dependencies between hooks, which are defined with
``before`` and ``after`` (see above). When an event is pushed on the dispatcher,
the dispatcher goes through all the registered hooks and checks which hooks are
configured to run on this event (their event interest matches the event).
It then makes a list of them, sorted by their order of execution, and stores it
on the event. The event is then added on the dispatcher's list of events, which
is sorted by priority.

For example::

  List of events
   |  event1 (prio 99) -> hook1, hook2, hook3
   |  event2 (prio 50) -> hook5, hook2, hook4
   v

The dispatcher has an internal ``GSource`` that is registered with
``G_PRIORITY_HIGH_IDLE`` priority. When there is at least one event in the
list of events, the source is dispatched. Every time it gets dispatched,
it takes the top-most event (the highest priority one) and executes the highest
priority hook in that event. If the hook executes synchronously, it then takes
the next hook and continues until there are no more hooks on this event;
then it goes to the next event, and so on. If the hook, however, executes
asynchronously, processing stops until the hook finishes; after finishing,
processing resumes like before.

It is important to notice here that the list of events may be modified while
events are getting processed. For example, a device is added; that's a
``device-added`` event. Then a hook is executed to set the profile. That creates
nodes, so a couple of ``node-added`` events... But there is also another hook to
set the route, which was attached on the ``device-added`` event for the device.
Suppose that we give the ``node-added`` events lower priority than the
``device-added`` events, then the ``set-route`` hook will execute right after
the ``set-profile`` and before any ``node-added`` events are processed.

Visually, with sample priorities::

  List of events
   |  "device-added" (prio 20) -> set-profile, set-route
   |  "node-added" (prio 10) -> restore-stream, create-session-item
   v

Obviously, there can also be a case where a newly added event has higher
priority than the event that was being processed before. In that case,
processing the hooks of the original event is stopped until all the hooks from
the higher priority event have been processed. For example, a capture stream
node being added may trigger the "bluetooth autoswitch" hook, which will then
change the profile of a device. Changing the profile also has to trigger setting
a new route and also handling the new device nodes, creating session items for
them... After all this is done, processing the original capture stream
``node-added`` event can continue.
