title
| title |
|---|
| Dealing with Time in C++ |
Dealing with Time in C++
Many nodes respond to input changes immediately, but some nodes control processes lasting for long time spans. They force themselves to be re-evaluated after some delay to repeat a task or complete the job.
XOD C++ node API provides scheduling functions to deal with these cases. In this article, we’ll learn them by example.
The task
We’re a going to implement a tick node which when triggered by SET pin
starts sending pulses at equal time intervals T. A user should be able to
cancel the series in progress by sending a pulse on RST input.
Although you could trivially express such node with a combination of
flip-flop and clock
without touching C++ at all, let’s ignore it for now.
Prepare the node
As always, when you make a C++ node, start with a
new patch, add required terminals, and the not-implemented-in-xod node.
Don’t forget to provide a resonable default value for T. 1 second is fine.
Double-click on not-implemented-in-xod node to open the code editor.
Set timeout
First, we should handle pulses on SET input. When set, we’ll use
setTimeout function to ask XOD
engine to call evaluate again after given timeout:
struct State { };
\{{ GENERATED_CODE }}
void evaluate(Context ctx) {
if (isInputDirty<input_SET>(ctx)) {
// Get T-input value. Conventionally it should be expressed in seconds
Number t = getValue<input_T>(ctx);
// However, XOD API works with millisecond values, so convert
TimeMs milliseconds = t * 1000;
// Schedule re-evaluation after calculated number of milliseconds
setTimeout(ctx, milliseconds);
}
}
Handle timeout
Good. We scheduled ourselves. Now we need to react. Use
isTimedOut function for this. We
need an explicit check of whether the current evaluation caused by the timeout
because the reasons for evaluate calls differ. It could be an input value
update before the time interval elapsed.
struct State { };
\{{ GENERATED_CODE }}
// Note, we extracted a function to read `T` input and set timeout
// with that value. The function helps us to avoid code duplication
// in `evaluate` since we need the code twice.
void charge(Context ctx) {
Number t = getValue<input_T>(ctx);
TimeMs milliseconds = t * 1000;
setTimeout(ctx, milliseconds);
}
void evaluate(Context ctx) {
if (isInputDirty<input_SET>(ctx)) {
charge(ctx);
}
if (isTimedOut(ctx)) {
// Timeout has been elapsed, emit an output pulse
emitValue<output_OUT>(ctx, true);
// To be re-evaluated next time we need to set timeout again
charge(ctx);
}
}
Cancel timeout
The only thing left to be done is reset handling. When a pulse is sent to RST
we’ll use clearTimeout function
to stop counting.
struct State { };
\{{ GENERATED_CODE }}
void charge(Context ctx) {
Number t = getValue<input_T>(ctx);
TimeMs milliseconds = t * 1000;
setTimeout(ctx, milliseconds);
}
void evaluate(Context ctx) {
if (isInputDirty<input_RST>(ctx)) {
// When pulsed on `RST` we cancel the timeout countdown regardless
// whether it was set or not
clearTimeout(ctx);
// Return from `evaluate` early giving priority to `RST` so that
// pulse on `SET` and timeout will not be even checked at this
// evaluation pass
return;
}
if (isInputDirty<input_SET>(ctx)) {
charge(ctx);
}
if (isTimedOut(ctx)) {
emitValue<output_OUT>(ctx, true);
charge(ctx);
}
}
Test
That’s all. Our node is ready. Test it with two buttons connected to SET and
RST and a flip-flop with LED on another side
Conclusion
XOD provides quite a basic API to manage time. Although it is simple, you get all tools you need to control lengthy processes. Main principles are:
- Use
setTimeoutto schedule re-evaluation of self. Remember, the timeout is expressed in milliseconds. - Always use
isTimedOutto be sure you’re evaluating because the time has passed. - If you want to run a task periodically, call
setTimeoutagain manually whenisTimedOut. - Use
clearTimeoutto ensure no timeout countdown is in progress.

