Signalling the Model
Now that we can control the model with persistent data, this chapter exposes how to control the model with non-persistent data, that is data that is not going to be saved. There multiple ways to achieve that in flip, with slightly different meaning, exposed here.
This chapter will use the model declared in the chapter Declaring the Model.
Signal
and Message
Flip has two signal types : Signal
and Message
. They operate in a relative similar ways, but have different meanings.
Message
Message
is a flip type. It does not store data, but allows to share non-persitent data between clients. For this reason, it is part of the document model.
When a Message
is signalled, the content of the data is shared with every documents and clients connected to the same document, but the data itself is not saved within the document.
A Message
describes a transient change of state of a flip Object. Receiving the data sent is done through the observer, as a Message
is part of the model, which means that the data from a message is in a transaction where over members might be changed. This allows to receive synchronously changes of the model, that would be hard to manage with a Signal
.
Let's take an example to illustrate this point. Let's consider a play head in a audio or video application. From a feature point of view, the play head has a position and a state (playing or not playing). The position expresses the change in the model, not the actual position as shown in the view.
If suddenly the user want to change both the position and the state of the play head, then using a Message
ensure that those two properties are going to be changed synchronously and transactionally into the model.
This is not the case with Signal
where those changes would come one after the other, possibly in a different order.
Example :
class PlayHead : public Object |
{ |
public: |
enum State |
{ |
State_STOPPED = 0, |
State_PLAYING, |
}; |
Float position; |
Message <State> state; |
}; |
// controlling the model, jumping to position 2.0 |
// and playing at the same time |
auto & playhead = song.playhead; |
playhead.position = 2.0; |
playhead.state.send (PlayHead::State_PLAYING); |
playhead.document ().commit (); |
// observing the model, this allow to see the |
// changes as a transaction |
void document_changed (PlayHead & playhead) |
{ |
if (playhead.position.changed ()) |
{ |
// ... |
} |
if (playhead.state.changed ()) |
{ |
playhead.state.get ([&](bool forward_flag, State state) { |
// ... |
}); |
} |
} |
Finally, because it is part of the document model, Message
has a forward_flag
as part of its function definition, to allow for the observer to take the direction of execution of the transaction to be taken into account. The semantic of backward for a Message
data depends on the semantic of the model. The only rule is to have the proper behavior if the transaction containing the Message
is refused by the server so rollbacked locally, or the meaning of undoing a transaction when the transaction contains operations over a Message
.
Signal
On the other hand, Signal
is not a flip type. It also does not store data, and the data is not shared between other clients. It is not part of the document model.
When a Signal
is signalled, the content of the data is shared only with every documents connected through a Hub
on the local machine, and the data itself is not saved within the document.
A Signal
describes a signal directed to a flip Object.
Receiving the data sent is done with the help of a signal connection which is outside the model, and is not received through the document_changed
virtual method of the observer. Therefore it is not synchronous to changes of the model.
Because of the way it internally works, Signal
is also preferred when the amount of data to exchange per second can be enormous.
Let's take the previous example and let's say that the realtime position of the playhead (not the one reflected in the model) is going to be signaled from the audio engine to the GUI using a Signal
.
When the play head is constructed, the observer of the GUI will set up a signal connection to receive the realtime position, as illustrated below.
class PlayHead : public Object |
{ |
public: |
enum |
{ |
Signal_RT_POSITION = 0, |
}; |
PlayHead () : signal_rt_position (Signal_RT_POSITION, *this) {} |
Signal <double> signal_rt_position; |
}; |
void document_changed (PlayHead & playhead) |
{ |
if (playhead.added ()) |
{ |
// Store the SignalConnection in an entity |
playhead.entity ().emplace <SignalConnection> ( |
playhead.signal_rt_position.connect ([](double position){ |
// update realtime position in the GUI |
}); |
); |
} |
if (playhead.removed ()) |
{ |
playhead.entity ().erase <SignalConnection> (); |
} |
} |
// From audio engine : |
double position = audio_engine.get_rt_position (); |
playhead.signal_rt_position (position); |
The signal also decouple the sender from the receiver, in the sense that the sender does not need to know who is going to receive the data, and even is the data is going to be received at all.
This is also a safe mechanism, if an object is already deleted in a document, while still present in another document : if the signal does not have a recipient anymore, it is silently dropped.
The next chapter, Working with a Remote Server will guide you through setting up a collaboration network.