===============
Managing Events
===============

In interactive systems, many interesting actions happen at specific moments in time: a performer
presses a button, a sensor crosses a threshold, a timer runs out, a metronome ticks. These fleeting
moments are distinct from continuous signals flows like the brightness of a light or the position of a
knob: they happen in an instant and are then gone.

In Plaquette, an **event** is an instantaneous condition that occurs on a unit for exactly one
step: the push of a button, the tick of a metronome, the end of a timer, or a peak detection.
Plaquette offers two different and complementary ways to work with events: **trigger conditions**
and **callback functions**.

**Trigger conditions** are ``true`` for the single step when the event occurs. They are typically
polled continuously inside the ``step()`` function using ``if`` statements. We have already been
working with some of them in the :doc:`inputs_outputs` and :doc:`timing_basics` sections:

  .. code-block:: cpp

    void step() {
      if (button.rose()) { ... }     // true only on the step when button is pressed
      if (metro) { ... }             // true only on the step when the metronome ticks
    }

**Callback functions** are functions that you register once in ``begin()``. They are called
automatically when the event occurs, without needing to write ``step()`` logic.

  .. code-block:: cpp

    void doSomething() { ... }     // user-defined function

    void begin() {
      button.onRise(doSomething);  // function doSomething() will be called on button press
      metro.onBang(doSomething);   // same, but triggered by metronome tick
    }

.. note::

  Registering a callback function is like giving someone instructions on what to do when a
  specific event happens. For example, imagine you are baking cookies and you set a timer. Instead of
  constantly watching the oven, you set the timer to "call you back" (in other words, alert you) when
  time is up, so you can take the cookies out.

Use **trigger conditions** when you have few events to manage and want to keep things simple: all your
logic stays in ``step()``, easy to read top-to-bottom, and you can combine events with other conditions
(e.g. ``if (button.rose() && sensor > 0.7)``).

Use **callback functions** when you have many events to manage or want to reuse the same action across
different events: register them once in ``begin()`` and they will be called automatically so you don't
have to think about polling them.

Supported Events
----------------

This table lists all supported events, their trigger condition and callback form, and which units support
them.


.. list-table::
  :header-rows: 1
  :widths: 30 18 18 22

  * - When
    - Trigger
    - Callback
    - Units
  * - Unit emits a pulse
    - unit itself
    - ``onBang(c)``
    - :doc:`Metronome`, :doc:`PeakDetector`
  * - Digital value toggles (on/off)
    - ``changed()``
    - ``onChange(c)``
    - :doc:`Alarm`, :doc:`DigitalIn`
  * - Digital value goes from on to off
    - ``fell()``
    - ``onFall(c)``
    - :doc:`Alarm`, :doc:`DigitalIn`
  * - Timed process completes
    - ``finished()``
    - ``onFinish(c)``
    - :doc:`Alarm`, :doc:`Ramp`
  * - Wave completes a full cycle
    - ``passedPeriod()``
    - ``onPassPeriod(c)``
    - :doc:`Wave`
  * - Wave passes its skew point
    - ``passedSkew()``
    - ``onPassSkew(c)``
    - :doc:`Wave`
  * - Digital value goes from off to on
    - ``rose()``
    - ``onRise(c)``
    - :doc:`Alarm`, :doc:`DigitalIn`
  * - New data is available
    - ``updated()``
    - ``onUpdate(c)``
    - :doc:`TimeSliceField`

Using Trigger Conditions
------------------------

Trigger functions are the simplest way to react to events. Call them inside ``step()``
with an ``if`` statement:

.. code-block:: cpp

   void step() {
     if (button.rose())          // button was just pressed
       led.toggle();

     if (alarm.finished())       // alarm just timed out
       println("Time is up!");

     if (metro)                  // metronome ticked
       println("Tick...");
   }

Since triggers are only ``true`` for one step, they naturally detect transitions rather than
ongoing states. You can combine them with logical operators and other conditions to create
more complex behaviors:

.. code-block:: cpp

   void step() {
     // Only react to button press when sensor value is high enough.
     if (button.rose() && sensor > 0.7)
       led.toggle();

     // React to either button press or metronome tick.
     if (button.rose() || metro)
       println("Something happened!");
   }

.. warning::
   Since triggers are ``true`` for only one step, **they must be checked at every call to ``step()``**
   or they will be missed. A common mistake comes when nesting a trigger inside another trigger's ``if`` block:

   .. code-block:: cpp

      void step() {
        // WRONG: button.rose() is only checked when metro fires,
        // so most button presses will be missed
        if (metro) {
          if (button.rose())
            led.toggle();
        }
      }

   In this example, ``button.rose()`` is only evaluated on the rare step when ``metro`` is ``true``.
   On all other steps, the button press goes undetected. Instead, check both triggers independently.

Using Callback Functions
------------------------

As your project grows and you start managing more events, the ``step()`` function can become
cluttered with ``if`` statements, making event management confusing and error-prone. Callback functions offer
a different approach: instead of checking for events yourself, you tell each unit what to do when an
event happens, and Plaquette automatically takes care of the rest. This keeps your code organized and
ensures no event is ever missed.

Let us take an example where we want to react to the push of a button by switching an LED on and off.

First, let us create the units we will be working with:

.. code-block:: cpp

   #include <Plaquette.h>

   DigitalOut led(LED_BUILTIN); // LED connected to built-in pin
   DigitalIn button(2, INTERNAL_PULLUP); // Button connected to pin 2

In order to react to an event, we first need to create a callback function which will be called when
the event will happen:

.. code-block:: cpp

   // Callback function to toggle the LED.
   void toggleLed() {
     led.toggle();
   }

Then, we need to register our callback to an event. In this case, we will register our function ``toggleLed()``
to the ``onRise()`` event of our button unit, which will trigger at the instant the button is pressed.

.. code-block:: cpp

   void begin() {
     button.debounce(); // Enable debouncing to avoid multiple events

     // Register callbacks for button events.
     button.onRise(toggleLed); // Toggle the LED on button press
   }

In this case, since the callback will take care of all the logic, we do not even need to declare a ``step()``
function!

Here is the final code for this example:

.. code-block:: cpp

   #include <Plaquette.h>

   DigitalOut led(LED_BUILTIN); // LED connected to built-in pin
   DigitalIn button(2, INTERNAL_PULLUP); // Button connected to pin 2

   // Callback function to toggle the LED.
   void toggleLed() {
     led.toggle();
   }

   void begin() {
     button.debounce(); // Enable debouncing to avoid multiple events

     // Register callbacks for button events.
     button.onRise(toggleLed); // Toggle the LED on button press
   }

   // Notice that we haven't invoked step(){} because it isn't necessary

Now, try changing ``onRise()`` to ``onFall()`` or to ``onChange()``. How does that affect the interaction
between the button and the LED?

Managing Multiple Events
------------------------

It is possible to register multiple callbacks with the same event. Likewise, a single callback can be registered
with many events.

Example: Launch both ``toggleLed()`` and ``printButton()`` on button press, registering ``printButton()`` to both
press and release events.

.. code-block:: cpp

   #include <Plaquette.h>

   DigitalOut led(LED_BUILTIN); // LED connected to built-in pin
   DigitalIn button(2, INTERNAL_PULLUP); // Button connected to pin 2

   Monitor monitor(115200); // Serial monitor.

   // Callback function to toggle the LED.
   void toggleLed() {
     led.toggle();
   }

   // Callback function to print button state.
   void printButton() {
     print("Button ");
     println(button ? "pressed" : "released");
   }

   void begin() {
     button.debounce(); // Enable debouncing to avoid multiple events

     // Register callbacks for button events.
     button.onRise(toggleLed); // Toggle the LED on button press

     button.onRise(printButton); // Print button state
     button.onFall(printButton); // Same here
   }

Coordinating Parallel Events with Metronomes
--------------------------------------------

There are many applications for which things happen concurrently at different pace, making
one wish there could be multiple looping functions similar to ``step()`` running in parallel at different
rates. This is easy to achieve in Plaquette using event-driven coding. Metronomes tick at a specific
period, generating "bang" events which can trigger callbacks by registering them to the ``onBang()`` event.

In this example, two metronomes control two LEDs, one digital and one analog, each at a different
interval. A ramp is used to fade the analog LED.

.. code-block:: cpp

   #include <Plaquette.h>

   DigitalOut led1(LED_BUILTIN); // First LED (digital) connected to built-in pin
   AnalogOut led2(9);            // Second LED (PWM) connected to pin 9
   Metronome metro1(1.0);        // Metronome with a 1 second period
   Metronome metro2(2.0);        // Metronome with a 2 seconds period
   Ramp rampLed(0.5);            // Short ramp to control LED 2

   // Function to toggle the first LED.
   void pingLed1() {
     led1.toggle();
   }

   // Function to start the ramp on second LED.
   void pingLed2() {
     rampLed.start();
   }

   void begin() {
     // Register callbacks for the metronomes.
     metro1.onBang(pingLed1); // Toggle LED 1 every second
     metro2.onBang(pingLed2); // Fade in LED 2 every 2 seconds
   }

   void step() {
     rampLed >> led2; // Ramp second LED from 100% to 0%
   }

Creating On-the-fly Callbacks
-----------------------------

For simple, localized actions, you can define callback functions directly inline using
an **anonymous function** (also called **lambda function**) which can be created with
the following syntax:

.. code-block:: cpp

  []() {
    // Function content goes here.
  }

It allows you to write concise code without defining separate named functions and is
thus especially useful for short, self-contained actions, keeping the code clean and readable.

For example, we could rewrite the callback registration from the example above in a shorter
way, like this:

.. code-block:: cpp

  void begin() {
    // Register callbacks for the metronomes.
    metro1.onBang([]() { led1.toggle(); }); // Toggle LED 1 every second
    metro2.onBang([]() { rampLed.start(); });  // Fade in LED 2 every 2 seconds
  }

Conclusion
----------

Event-driven programming in Plaquette simplifies the process of reacting to changes and scheduling
actions, allowing you to write modular, expressive, and efficient code. By using callbacks and event
sources like buttons and metronomes, you can manage complex behaviors that happen concurrently and at
different rates.
