Synchronization
Several parts of a Met.3D session share common state, such as the currently selected forecast time or ensemble member. When one of these values changes, every dependent actor and data source must update consistently. The synchronization system provides an event-based mechanism for propagating these changes while supporting both synchronous and asynchronous processing.
Overview
Synchronization is built around two classes:
MSyncedControl– The base class for a synced control, which is a widget that owns a synchronized value and notifies registered objects when that value changes.MAbstractSyncEvent– The base class for the events posted to registered objects when a synced control’s value changes. It carries the synchronized value as well as a pointer to the synced control that sent the event.
MTimeControl and MEnsembleControl are the two synced controls.
They synchronize a date-time and an ensemble member / mode respectively.
Both derive from MSyncedControl and post MTimeSyncEvent / MEnsembleSyncEvent instances when their value changes.
Synchronized controls are part of a Met.3D session. Some are created by default by the session, but can also be created by the user if they require more.
The synced control is the owner of the synchronized state, while the synchronization event provides the information required for receivers to react to the change, including the new value and the originating control.
Whenever the synchronized value of a synced control changes, a synchronization event is queued.
If multiple events are generated, they are processed in order, and each event waits for the previous synchronization operation to complete before being delivered.
This prevents a new synchronized state from being propagated before the previous state has been fully applied.
Queuing an event will produce a QFuture<void> that is returned and completes once the synchronization of that change has completed.
The typical flow of a synchronization event is the following:
Fig. 41 Synchronizing a time change.
Synchronization events are normally delivered to registered objects in an unspecified order. Objects may optionally register with a priority, causing higher-priority objects to receive events before lower-priority ones. This is primarily used when one synchronized object depends on another having already updated its state. For example, MTimeSyncProperty always registers with the highest priority so that its cached time value is updated before the owning object receives the synchronization event.
MTimeControl
MTimeControl synchronizes a single QDateTime value across a session. In
addition to the base MSyncedControl interface, it provides:
setTime()/getTime()– Set or read the current synchronized time. Setting a new time queues aMTimeSyncEventand returns aQFuture<void>that completes once every registered object has finished reacting to the change.advanceTime(step, unit)– Advances the current time by a given step and unit (seconds, minutes, hours, …), used for both manual stepping and time animation (see Animation & View Capture). Internally callssetTime()and also returns the future of the queuedMTimeSyncEvent.selectDataSources()– Restricts the times selectable in this control to the time dimension(s) of one or more loaded datasets, so the control only offers times that are actually available in the data currently in use.
User-triggered changes to a time control (setting the time, advancing it,
changing the time step, renaming or removing the control, …) are each
implemented as a nested QUndoCommand (e.g. MTimeControl::SetTimeCommand,
MTimeControl::AdvanceTimeCommand), following the same convention described
in Undo framework.
MEnsembleControl
MEnsembleControl synchronizes the currently selected ensemble member (or an
ensemble statistic mode such as “mean” or “all members”) in the same way
MTimeControl synchronizes time, following the same registration/event
pattern and the same undo-command convention for user-triggered changes.
Registering and receiving synchronization events
An object registers itself with a synced control via
MSyncedControl::registerSyncedObject(QObject*, int priority), and is
automatically deregistered when destroyed.
An object can be registered with multiple synced controls simultaneously.
Once registered, an object receives MAbstractSyncEvent instances and must accept or ignore each one.
Ignoring an event indicates that the object is not affected by the synchronized value.
Events that are neither accepted nor ignored are treated as ignored once the event handler returns.
Accepting cannot be delayed until after asynchronous work completes.
If the change can be processed immediately, the event is accepted synchronously via QEvent::accept().
If processing requires asynchronous work, the event’s MAbstractSyncEvent::acceptAsync(QFuture<void>&, QObject*) must be called instead.
It requires a future that completes with the asynchronous work, and the object responsible for the async operation.
The object is passed for debugging purposes when a future does not complete and the synchronization operation therefore can’t finish.
The future is typically returned by a call to requestData() of the corresponding data source.
The synced control tracks all currently-waiting events and only reports
synchronization as complete (isSynchronizing() returns false) once every
registered object has accepted and completed or ignored every queued event.
Connecting properties to a time or ensemble control
Actors and components should generally use MTimeSyncProperty and MEnsembleSyncProperty rather than interacting with MSyncedControl directly.
They wrap the synchronization mechanism into a single property.
The owning object synchronizes with the property rather than directly with a synced control.
Direct synchronization is mainly intended for objects that provide no user-facing properties.
The property manages registration with the selected synced control on behalf of the object.
This allows the user to change the synced control in the property, or disable synchronization completely, without the developer having to manage that directly.
The object registers itself with the property using synchronize(QObject), which manages the registration with selected synced controls.
Synchronization events can be received as usual via QObject::event(QEvent *event).
1...
2 auto timeSyncProp = MTimeSyncProperty("Base time", session);
3 timeSyncProp.setConfigKey("base_time");
4 timeSyncProp.synchronize(this);
5 timeSyncProp.setTimeControl(session->getTimeControls()[0]); // Synchronize with the sessions base time control by default
6...
7
8// Not valid c++ code, just to make it clear that this goes into the event method.
9QObject::event(QEvent *event) override
10{
11 ...
12 if (auto tEvent = dynamic_cast<MTimeSyncEvent*>(event))
13 {
14 // Synchronize the object's time-dependent state.
15
16 tEvent->accept();
17 QDateTime value = tEvent->getTime(); // Read synchronized value.
18 }
19 ...
20}
Creating new synchronization domains
New synchronization domains can be introduced by deriving from MSyncedControl and providing a matching MAbstractSyncEvent implementation.
Since this requires changes to the synchronization framework itself, it is uncommon.
MEnsembleControl provides a smaller example implementation compared to MTimeControl and can be used as a reference.