V-HAB Events and Timer Documentation

In many core classes of V-HAB, the concept of "events" is used, which is an approach to allow for loosely coupled code. Google that, e.g. "Events in Matlab". Also stuff like event-driven architecture (also see Matlab documentation, "Event and Listener Concepts"). Some example of a use-case for events is described in the following section, including an overview of the default event logic in V-HAB. A special case of events in V-HAB is implemented in the global timer object. Its functionality will be described in the second sub-section.




ToDo

Timer

bind → setTimeStep mit/ohne bool. Dependent time step.

store/phase, solver etc sind darauf registriert → fuehren sich eh alleine, selbststaendig aus, wann sie halt ausfuehren muessen, also nach "physik"
→ dadurch bspw. manips, p2ps, f2fs mit ausgefuehrt ("dependent" componenten?)

AKTIVE komponenten (eben neben den default dingern oben) sind dann noch vsys.exec(), monitors, etc → die koennen dann vom user verwendet werden um irgendwas passieren zu lassen
→ vsys.exec() mit setTimeStep; paar Helper machen → darauf verlinken!

Events

In short, the idea behind events is the following: assume you have an object that represents a sensor, and several "other" objects that act depending on the value given by the sensor. Each time the sensor reads a new value, somehow the "other" objects need to be notified about this new value. In a first approach, one might add this functionality to the sensor object: once a new readout comes up, the code/class of the sensor object calls a method .update() in each of the "other" objects. However, this becomes an issue quickly, because each time a new object is included that depends on this sensor value, the code in the sensor class also has to be updated to include this new object when calling all the .update() methods. Also when an object is removed, the sensor class needs to be updated.

Using events, the responsibility for keeping a list of all the "other" objects that need to be updated by the sensor object is reversed. Now, the sensor class does not contain code anymore that explicitly lists all the "other" objects that need to be notified about a new value. Instead, the sensor object contains a collection of function handles (which is initially empty) by adding the event.source class as a parent class. Now, each of the "other" objects calls the bind method on the sensor object, which is also defined by the event.source base class. The first parameter is the name of an "event", and the second parameter is a function handle; in this case, the event name could be "update", and as a function handle, the "other" object would pass the this.update() method (yes, exactly the method that was, see previous paragraph, originally directly and explicitly called by the sensor object). Then, whenever a new readout value comes up, the sensor object would not explicitly call the .update() method on a explicitly list of objects, but would rather just loop through that collection and call all  the function handles that are registered for the event "update". It does this by simply calling this.trigger('update'), a method also defined in the event.source base class.

This has several advantages: if you decide to e.g. remove one of the "other" objects, you will not have to change the sensor class. The deleted object will simply NOT call the sensor .bind() method, and therefore will not be called when the "update" event happens. Also if you change the name of the .update() method within one class, you do not need to switch over to the sensor class and update the code there as well, but you simply scroll up in the same file, likely to the constructor of your method, and change the .bind() call on the sensor object.

Overview:

  • To enable events in a class foo, set the event.source class as a(n additional) parent class.
  • Register for an event on an object of this class, e.g. by calling:

    oObject.bind('some_event', @this.executeThisMethod);
  • Trigger an event from a method within foo by calling:

    this.trigger('some_event', struct('sSome', 'Data'));
  • Output something when this event happened:

    function executeThisMethod(this, oEvent)
        disp(oEvent.tData.sSome)
    end

As a real-world example, you can bind to the 'outdated' on any branch object. This way, you will get notified as soon as the branch will update the flow rate this tick. If you register to the 'setFlowRate' event, you will get notified as soon as the new flow rate was set on the branch.


Timer

Within V-HAB, one shared timer object is used that is responsible for progressing the simulation forward by handling the execution of all scheduled code (callbacks) within different elements of the simulation at a specific "tick". A tick represents a distinct point in time where the simulation does execute, and has a number (xth tick in the simulation) and a time (at tick x, the simulation time was ys) associated. Each callback that is executed can then set a new time step for itself, i.e. tell the timer at what simulation time this callback should be executed next/again. Based on the list of all scheduled callbacks, the timer determines at what simulation time the next tick has to be. The time difference between those two ticks is called the time step. The timer then increases the simulation clock by this time step and executes all callbacks that are scheduled. This means that the timer implements a variable time step that dynamically adapts to the callbacks scheduled by the different simulation elements.

Generally, someone implementing a system in V-HAB does not necessarily have to deal with the timer. Generally, stores/phases and flow rate solvers do decide on their own when it is time to update themselves. If for some of these classes is a derived class is implemented, one can simply overload e.g. the "update" method, and execute code before/after calling the update method on the parent/super class (NEVER forget to do that!). The vsys class implements the "exec" method, which can be used to execute code that e.g. controls the system (i.e. switches components on and off etc.). As a third parameter to the vsys constructor, the user can provide a time step in seconds; this time step will be automatically set, i.e. the exec method will be executed in this time interval. During run-time, the time step can be set using the "setTimeStep" method. For more information on this, see below.

In most default core classes, a shortcut to the timer is available through this.oTimer (e.g.

tick groups? diagramm aktiv aufrufen vs. ueber event?

The setTimeStep timer method

This function handle (returned by the timer bind method) can be used to set the time step for the callback registered in this bind call (e.g. in a vsys exec method, calling "this.setTimeStep()"). There are  two parameters:

  • fNewTimeStep: New time step for the callback in [s]
  • bReset: When to execute next. There are different ways an object method can be called, either through the timer because the simulation time has reached the time of its next execution (case 1), or through another system, possibly at a time before a method in this object would have been called by the timer (case 2).
    1. Parameter has no effect.
    2. See below

The bReset parameter: Assume an object whose "checkTankLevel" method is set to be executed every ten seconds to check the level in a tank and switch on/off a pump or closes a valve or so. The ten seconds interval is based on the tank level change - i.e. if some inflow branch moves mass into the store, the time step interval can be chosen so the tank level does not change by more than a certain percentage. Five seconds after the last "checkTankLevel" execution, the object gets notified about a flow rate change in  the inflow branch of the tank (see above, by binding to the 'setFlowRate' event on the according branch object). The inflow just became much larger. Therefore, a new time interval has to be calculated, as the tank level would now change more than the defined limit between the "checkTankLevel" executions, and set as a time step. If the new time step would be for example seven seconds, bReset can be false or omitted completely. After calling setTimeStep(7), the next "checkTankLevel" callback execution will happen in two seconds from now - and therefore seven seconds since its last execution.

In another case, e.g. when a model parameter is changed (before the actual update of the whole model is triggered by the timer), it is likely that the whole model is updated and e.g. setOutdated is called on an according branch. In this case, if the time step for executing the next whole model update changes, bReset should be set to true. This means that the next update of the whole models happens when the current simulation time plus the new time step is reached, and not when the last execution time plus the new time step is reached.


General execution logic in timer/V-HAB

Each tick, the V-HAB timer basically executes two separate groups of callbacks:

  • Main Tick: callbacks registered to the timer via "bind", executed whenever the simulation time reaches their last execution time plus their time step.
  • Post Tick: callbacks for the post can be registered during the main tick. Using priorities, the executed callbacks are organized into groups.

The main tick should generally only be used for control code, i.e. code that either changes model states or that decides what "physical" calculations will actually have to be executed in this tick.

For example, if a "vsys" exec method calls a method like "setClosed" on a valve, the valve will change that model parameter but then simply call "setOutdated" on its branch. This in turn will mean that the flow rate solver for this branch will register its "calculateFlowRate" method as a post tick.

Another example is the matter.phase objects "massupdate" method, that will bind methods to update its p2p processors and substance manipulators in a post tick, or bind the new time step calculation as a post tick callback.


TODO: sobald post-tick prios -10/10, und bindPostTick logik besser (einmal in CTOR, rueckgabe methode fuer on/off) - hier beschreiben: bind/bindPostTick nur in CTOR (ab seal nich mehr moeglich!), die return values sind fct handles setTimeStep, activatePostTick (beliebig oft aufrufbar, nur einmal an erster position aktiviert!); NAMEN der prios!

TODO: Diagram?

  • Keine Stichwörter