TDL extends the C++ programming language to include Tasks. Tasks must be of type:
Inside these Tasks, C++ is extended to include certain TDL-specific statements. Specifically:
Some of these statements, in turn, support a number of Constraints.
Outside of a TDL Task, in C++ functions or methods, only the SPAWN Statement and the Functional Invocation of a Task are supported, and then only with certain restrictions upon their constraints.
GOAL | task-name | ( | task-arguments | ) | [ | [ WITH ] simple-constraints | ] | compound-statement | |
COMMAND | task-name | ( | task-arguments | ) | [ | [ WITH ] simple-constraints | ] | compound-statement | |
MONITOR | task-name | ( | task-arguments | ) | [ | [ WITH ] monitor-constraints | ] | compound-statement | |
EXCEPTION | task-name | ( | task-arguments | ) | [ | : | base-exception-task-name ( base-exception-arguments ) ] ; | ||
[ EXCEPTION ] HANDLER | task-name | ( | task-arguments | ) | [ | [ WITH ] exception-handler-constraints , | ] | ||
HANDLES exception-task-name | |||||||||
[ | , | exception-handler-constraints | ] | compound-statement | |||||
RESUME | task-name | ( | task-arguments | ) | compound-statement |
[ labels ] WITH ( one-or-more-constraints ) statement | |
/* Typically,
statement is a
compound-statement
*/
/* Labels have special significance. See comments. */ |
[ labels ] SPAWN task-name ( task-invocation-arguments ) [ WITH one-or-more-constraints ] ; | |
/* Labels have special significance. See comments. */ |
task-name ( task-invocation-arguments ) ; | |
/* Equivalent to 'SPAWN
task-name
(
task-invocation-arguments
) WITH WAIT;' */
/* This is a special case. See notes. PREVIOUS does not apply. */ |
constraint-statement-task-tag constraint ; | |
/* Some constraints cannot be applied after a Task has been SPAWN'ed. See notes. */ |
jump-statement ; | ||
Pure C/C++ features: | ||
/* Note TDL Task-Internal Expression Extensions and Restrictions */ |
FAIL [ exception-task-name ( task-invocation-arguments ) ] ; | ||
/* * |
Other traditional jump-statements (e.g. goto) are prohibited inside TDL Tasks, but can be utilized outside of TDL Tasks in C/C++ functions or methods. See TDL Task-Internal Expression Restrictions. | |
*/ |
Hours : Minutes : Seconds [ . Fraction-of-a-second ] | |||||||
/* | Hours | is an integer constant. | 0 <= Hours | <= 23. | */ | ||
/* | Minutes | is an integer constant. | 0 <= Minutes | <= 59. | */ | ||
/* | Seconds | is an integer constant. | 0 <= Seconds | <= 59. | */ | ||
/* | Fraction-of-a-second is an unrestricted non-negative integer constant. | */ |
Hours : Minutes : Seconds [ . Fraction-of-a-second ] | ||||
/* | Hours | is an unrestricted non-negative integer constant. | */ | |
/* | Minutes | is an unrestricted non-negative integer constant. | */ | |
/* | Seconds | is an unrestricted non-negative integer constant. | */ | |
/* | Fraction-of-a-second | is an unrestricted non-negative integer constant. | */ |
[ any-valid-C++-numeric-integer-expression ] | |||
[ . ] | |||
/* | The bracket characters ('[' and ']') here are required. | */ | |
/* | This is not an "optional" element. | */ |
Goal Tasks are intended to represent planning and decision making. They are intended to be branch nodes in the Task-Tree, where a course of action is decided upon, but not actually invoked.
Goal Tasks are run during the EXPANSION phase of a Task-Tree.
GOAL Alpha() { cout << "Test Goal Alpha" << endl; SPAWN Bravo(); } // This constraint serves no purpose here // other than to demonstrate syntax. Goal Bravo() WITH EXPAND FIRST { cout << "Test Goal Bravo" << endl; } int main() { TCM_Initialize(); Alpha(); }
Command Tasks are intended to represent the implementation of a course of action. They are intended to be the leaves in a Task-Tree, where the decisions made in Goal nodes are actually implemented.
Command Tasks are run during the EXECUTION phase of a Task-Tree.
COMMAND Alpha() { cout << "Test Command Alpha" << endl; SPAWN Bravo(); } // This constraint serves no purpose here // other than to demonstrate syntax. Command Bravo() WITH EXPAND FIRST { cout << "Test Command Bravo" << endl; } int main() { TCM_Initialize(); Alpha(); }
Monitors are Tasks that repeat. Their reoccurrence can be periodic, based upon a prescribed period of time. Or they can be interrupt-driven, via the ACTIVATE constraint. This distinction is made based upon the presence (or absence) of the PERIOD constraint.
Monitor Tasks are unique in the TDL/TCM world in that they create multiple entries into the Task-Tree. The first is an abstract Private-Monitor-Task entry which creates children Monitor-Child-Instance Tasks whenever it is appropriate. The rest are the Monitor-Children-Instances of this Private-Monitor-Task.
The actual code inside a Monitor Task is run over and over again inside each Monitor-Child-Instance Task.
As a consequence, as covered in the FAQ, to TERMINATE a Monitor from inside that Monitor's code, you must TERMINATE the Monitor-Instance's parent, that abstract Private-Monitor-Task. E.g.:
Monitor Alpha() { // "THIS TERMINATE;" Won't work here. TDL_BIND ( TCM_Parent(TDL_REF(THIS)), realMonitor ); realMonitor TERMINATE; }
Because of their repetitive nature, a Monitor Task only completes when one of three situations arises:
The Monitor has a MAXIMUM ACTIVATE constraint, and has been activated that many times through the various ACTIVATE constraint mechanisms.
The Monitor has a MAXIMUM TRIGGER constraint, and has been TRIGGER()'ed that many times.
The Monitor itself, and not a Monitor-Child-Instance is terminated through one of the TERMINATE constraints.
So the code up above could also have been accomplished through:
monitor Bravo() WITH MAXIMUM TRIGGER 1 { TRIGGER(); }
The current number of activations a Monitor has experienced can be determined through the GetNumberOfActivates() function. Similarly, the current number of triggers a Monitor has experienced can be determined through the GetNumberOfTriggers() function. Both of these are only available from inside the Monitor Task's compound-statement. (Which corresponds to inside each Monitor-Child-Instance.) One should be wary of relying on these numbers to always being unique for each Monitor-Child-Instance. If multiple Monitor-Child-Instance's have been created, but not yet Handled, GetNumberOfActivates() will reflect the same total number in each pending Monitor-Child-Instance.
As TDL/TCM is not a real-time system, periodic Monitors are not spawned at precise, accurate time intervals. Rather, the PERIOD constraint specifies a minimum time period the system must wait between successive creations of children Monitor-Child-Instances, as measured from the creation-time of the last (previous) Monitor-Child-Instance.
As TDL/TCM has an asynchronous scheduling policy, it is possible for children Monitor-Child-Instances, and the Tasks in the Task-Tree's SPAWN'ed by these children Monitor-Child-Instances, to run in what is, for all practical purposes, a random order. To impose order upon these Tasks, monitor-constraints can be applied between children Monitor-Child-Instances. Specifically, the constraints SERIAL, SEQUENTIAL EXPANSION, SEQUENTIAL EXECUTION, SEQUENTIAL PLANNING, and SEQUENTIAL ACHIEVEMENT, when attached to the Monitor at the Monitor Task Declaration level, will bind between successive children Monitor-Child-Instances.
Each Monitor-Child-Instance gets a full copy of the Monitor Task's task-arguments on its local stack. As such, any changes to these values will not be seen by successive Monitor-Child-Instances. To propagate data to successive Monitor-Child-Instances, one needs to employ the PERSISTENT constraint. E.g.:
MONITOR Charlie() WITH MAXIMUM TRIGGER 5, PERIOD 0:0:1.0, PERSISTENT int count = 0 { cout << "Activates: " << getNumberOfActivates() << " Triggers: " << getNumberOfTriggers() << " count++: " << (count ++) << endl; TRIGGER(); } int main() { TCM_Initialize(); Charlie(); } /* Output: * Activates: 1 Triggers: 0 count++: 0 * Activates: 2 Triggers: 1 count++: 1 * Activates: 3 Triggers: 2 count++: 2 * Activates: 4 Triggers: 3 count++: 3 * Activates: 5 Triggers: 4 count++: 4 */
Exception Tasks are intended to represent a data structure corresponding to a particular problem or situation.
Exception Tasks have no constraints. Nor do they have a compound-statement. (There is no Task body.) Instead, they consist solely of the data included in the task-arguments at the Exception Task Declaration level. Inheritance is encouraged. Exception Tasks can inherit from one another, refining the concept of what they represent, and providing additional details. Multiple inheritance is not permitted. Exception Tasks can be generated [thrown] from any Task via the FAIL jump-statement. Once so generated, they travel upwards through the Task-Tree, searching for any Task with a suitable Exception Handler Task attached to it that can receive [catch] this Exception.Exception Handler Tasks exist solely to receive [catch] Exception Tasks that are generated [thrown] by Tasks in the Task-subTree to which they are attached.
Exception Handler Tasks are run completely independently of the Task to which they attached, as well as completely independently of whatever Task generated [threw] the corresponding Exception Task. All local variables in these Tasks(s) are unavailable to the Exception Handler Task.
Information from where the Exception Handler Task is attached to the Task-Tree can be provided to the Exception Handler Task via the Exception Handler Task's task-arguments. The Exception Task itself provides information from where the FAIL jump-statement occurred.
An Exception Handler Task can choose to re-generate [re-throw] its exception via the FAIL jump-statement. In this case, no Exception Task argument is supplied to the FAIL jump-statement.
An Exception Handler can support a Task-level MAXIMUM ACTIVATE constraint. If this constraint is present, after the Exception Handler has been received [caught] that many Exception Tasks, it will cease to receive [catch] any more.
An Exception Handler can only handle ONE Exception Task type. However, it will also implicitly handle any Exception Task's that are derived from that Exception Task type. E.g.:
Exception HardwareFailure( const char * theFailureLocation); EXCEPTION HardwareOnFire ( const char * theFireLocation, const char * theHardwareFailureLocation ) : HardwareFailure ( theHardwareFailureLocation ); Exception Handler FailureHandler ( const char * theFailureHandlerLocation ) WITH HANDLES HardwareFailure { cout << "Test FailureHandler" << endl // Argument to Exception Handler FailureHandler << " theFailureHandlerLocation..........: " << theFailureHandlerLocation << endl // Argument from Exception HardwareFailure << " HardwareFailure.theFailureLocation.: " << HardwareFailure.theFailureLocation << endl; // At this point, stop the HardwareFailure Exception Task. } EXCEPTION HANDLER FireHandler ( const char * theFireHandlerLocation ) HANDLES HardwareOnFire { cout << "Test FireHandler" << endl // Argument to Exception Handler FireHandler << " theFireHandlerLocation.....................: " << theFireHandlerLocation << endl // Arguments from Exception HardwareOnFire << " HardwareOnFire.theFireLocation.............: " << HardwareOnFire.theFireLocation << endl << " HardwareOnFire.theHardwareFailureLocation..: " << HardwareOnFire.theHardwareFailureLocation << endl // Argument from base Exception HardwareFailure << " HardwareOnFire.theFailureLocation..........: " << HardwareOnFire.theFailureLocation << endl; FAIL; // Re-FAIL [re-throw] the current Exception Task. } Goal Alpha() WITH EXCEPTION HANDLER FailureHandler ( "Goal Alpha" ) { cout << "Test Goal Alpha" << endl; SPAWN Bravo(); } Command Bravo() WITH EXCEPTION HANDLER FireHandler ( "Command Bravo" ) { cout << "Test Command Bravo" << endl; // Generate [throw] our Exception Task FAIL HardwareOnFire ( "Command Bravo [fire location]", "Command Bravo [hardware failure location]" ); // This code will never get executed. cout << "Test Command Bravo -- this code never gets executed!" << endl; } int main() { TCM_Initialize(); // Disable all TCM logging messages. TCM_SetFileLoggingOptions ( TCM_TerminalLoggingOptions() ); TCM_SetTerminalLoggingOptions ( Log_None ); // Disable all TDL logging messages. TDL::setLogStream ( "/dev/null" ); // Run Task Alpha as a Functional Invocation of a Task. Alpha(); } /* Output: * Test Goal Alpha * Test Command Bravo * Test FireHandler * theFireHandlerLocation.....................: Command Bravo * HardwareOnFire.theFireLocation.............: Command Bravo [fire location] * HardwareOnFire.theHardwareFailureLocation..: Command Bravo [hardware failure location] * HardwareOnFire.theFailureLocation..........: Command Bravo [hardware failure location] * Test FailureHandler * theFailureHandlerLocation..........: Goal Alpha * HardwareFailure.theFailureLocation.: Command Bravo [hardware failure location] */
Resume Tasks are intended to simplify the resumption, and eventual completion of POSTPONE'd Tasks.
Unlike all other Tasks, the task-name of a Resume Task must be non-unique. It must match the task-name of anther Task, a counterpart that (presumably) can be POSTPONE'd. Resume Tasks are not invoked in the traditional ways (SPAWN, et al). WITH Statements and constraints are not applied to them. Instead, they are invoked solely through a functional interface. One uses TDL_RESUME_task-name as the function name, and the first argument must be the TCM_Task_Tree_Ref for the corresponding counterpart Task instance that is to be resumed. It is important that this counterpart Task has already been POSTPONE'd prior to attempts to resume it. No automated synchronization mechanisms exist here. Rather, the counterpart task, during its HANDLING, must take steps to initiate its own resumption, or otherwise indicate that it can now be resumed. Resume Tasks can be POSTPONE'd, which indicates that the task is still uncompleted, and must be resumed again at a later point in time. Resume Tasks do not have access to the local variables of their counterpart POSTPONE'd Task. However, they can access their task-arguments, as well as the task-arguments of their counterpart POSTPONE'd Task. These latter task-arguments are replicated on the local stack frame in both the Resume and the counterpart Tasks, so changes made by the counterpart will not be visible to the Resume task. If you wish to pass data between the counterpart and Resume tasks, you should employ the PERSISTENT constraint. E.g.:// We want to RESUME Alpha AFTER it has started its handling, but before // it completes its handling. Therefore, for this example, we will // exploit the single-threaded nature of TCM/TDL, and poll-check to see // if Alpha has been started by checking the alphaRef value, knowing full // well that once started nothing else will run until Alpha is POSTPONE'd. Monitor RunAlpha() WITH PERIOD 0:0:0.1, MAXIMUM TRIGGER 3, PERSISTENT TCM_Task_Tree_Ref alphaRef = NULL { switch ( getNumberOfTriggers() ) { case 0: SPAWN Alpha ( 1, & alphaRef ); TRIGGER(); break; case 1: if ( alphaRef != NULL ) { TDL_RESUME_Alpha ( alphaRef, 3, FALSE ); TRIGGER(); } // else /* if alphaRef == NULL */ // try again later on... break; case 2: TDL_RESUME_Alpha ( alphaRef, 4, TRUE ); TRIGGER(); break; default: cerr << "[Monitor RunAlpha] Impossible error!" << endl; break; } } Goal Alpha ( int4 theGoalAlphaInt, TCM_Task_Tree_Ref * theAlphaRefToSet ) WITH PERSISTENT int4 thePersistentGoalAlphaInt = 1 { /* Save this for later use during RESUME */ (*theAlphaRefToSet) = TDL_REF ( THIS ); cout << "Test Goal Alpha, initial values:" << endl << " theGoalAlphaInt...........: " << theGoalAlphaInt << endl << " thePersistentGoalAlphaInt.: " << thePersistentGoalAlphaInt << endl << endl; theGoalAlphaInt = 2; // This will still be 1 in the Resume Task. thePersistentGoalAlphaInt = 2; // This will now be 2 in the Resume Task. cout << "Test Goal Alpha, modified values:" << endl << " theGoalAlphaInt...........: " << theGoalAlphaInt << endl << " thePersistentGoalAlphaInt.: " << thePersistentGoalAlphaInt << endl << endl; POSTPONE; } Resume Alpha ( int4 theResumeAlphaInt, BOOLEAN theShouldResumeCompleteAlphaTask ) { cout << "Test Resume Alpha:" << endl << " theResumeAlphaInt.........: " << theResumeAlphaInt << endl << " theGoalAlphaInt...........: " << theGoalAlphaInt << endl << " thePersistentGoalAlphaInt.: " << thePersistentGoalAlphaInt << endl << endl; if ( theShouldResumeCompleteAlphaTask == FALSE ) { cout << "Resume Alpha: continuing to POSTPONE Alpha" << endl << endl; POSTPONE; } else { cout << "Resume Alpha: Task Alpha Completing." << endl << endl; // SUCCESS; // One way to indicate Task Completion. // return; // Another way to indicate Task Completion. // Or we can do nothing, and just let the Resume Task end to // indicate Task Completion. } } int main() { TCM_Initialize(); // Disable all TCM logging messages. TCM_SetFileLoggingOptions ( TCM_TerminalLoggingOptions() ); TCM_SetTerminalLoggingOptions ( Log_None ); // Disable all TDL logging messages. TDL::setLogStream ( "/dev/null" ); // Run Task RunAlpha as a Functional Invocation of a Task. RunAlpha(); } /* Output: * Test Goal Alpha, initial values: * theGoalAlphaInt...........: 1 * thePersistentGoalAlphaInt.: 1 * * Test Goal Alpha, modified values: * theGoalAlphaInt...........: 2 * thePersistentGoalAlphaInt.: 2 * * Test Resume Alpha: * theResumeAlphaInt.........: 3 * theGoalAlphaInt...........: 1 * thePersistentGoalAlphaInt.: 2 * * Resume Alpha: continuing to POSTPONE Alpha * * Test Resume Alpha: * theResumeAlphaInt.........: 4 * theGoalAlphaInt...........: 1 * thePersistentGoalAlphaInt.: 2 * * Resume Alpha: Task Alpha Completing. */
Constrains this Task's Expansion to complete before this Task's Execution can start.
Meaningless for Command Tasks.
For Goal Tasks, it implies this Goal Task, and all Goal descendants in this Goal's Task-subTree, must complete their Handling prior to any Command descendants being started.
Constrains this Task's Expansion from starting until this Task's Execution is permitted to start.
By itself, DELAY EXPANSION has no effect. However, when combined with other constraints that limit when this Task's Execution may start, DELAY EXPANSION acts to limit when this subtask's Expansion may start.
Creates and attaches an instance of the specified EXCEPTION HANDLER Task to the Task being constrained.
Any expressions in the task-invocation-arguments are evaluated at the location of this constraint in the TDL code.
In the interests of preventing confusion for inexperienced users, TDL explicitly prohibits the use of EXCEPTION HANDLER constraints in a WITH Statement.
In the event of being attached to multiple SPAWN statements anyway via an insufficient iteration-index specification, or via a WITH Statement (through the Constraint Statement and the WITH Statement label mechanism), the same Exception Handler instance is attached to all the subtasks via a 1-N binding. This impacts both PERSISTENT and MAXIMUM ACTIVATE constraints.
When the Task being constrained is TERMINATE'd, the task-name Task is SPAWN'ed.
If that Task is not TERMINATE'd, this constraint has no effect.
Constrains this Task's Handling from starting until the reference-task-tag's Handling has completed.
This constraint has no effect between the Task being constrained and any subtasks SPAWN'ed by the reference-task-tag
If the reference-task-tag is not specified, it defaults to PREVIOUS. If there is no PREVIOUS task, this constraint has no effect.
With respect to Monitor Task-declaration-level monitor-constraints, SEQUENTIAL HANDLING is not available.
SEQUENTIAL HANDLING is equivalent to:
DISABLE HANDLING UNTIL
reference-task-tag HANDLING COMPLETED.
SEQUENTIAL EXPANSION and SEQUENTIAL PLANNING are synonymous. EXPANSION is merely an updated form of the old, outdated PLANNING nomenclature.
SEQUENTIAL EXPANSION/PLANNING acts to constrain this Task's Expansion (Planning) from starting until the reference-task-tag's Expansion (Planning) has completed.
If it's a Goal Task thats being constrained, that Goal Task may not start its Handling, nor may any of its descendant subtasks start their Handling until the reference-task-tag has completed its Handling, and all Goal descendant subtasks of that reference-task-tag have completed their Handling.
If it's a Command Task thats being constrained, this constraint has no effect.
If the reference-task-tag is not specified, it defaults to PREVIOUS. If there is no PREVIOUS task, this constraint has no effect.
With respect to Monitor Task-declaration-level monitor-constraints, a reference-task-tag must not be present. Instead, the default PREVIOUS is implied, and refers to the previous Monitor-Child-Instance Monitor activation. See Monitor Constraint Notes.
SEQUENTIAL EXPANSION/PLANNING is equivalent to:
DISABLE EXPANSION UNTIL
reference-task-tag EXPANSION COMPLETED.
SEQUENTIAL EXECUTION and SEQUENTIAL ACHIEVEMENT are synonymous. EXECUTION is merely an updated form of the old, outdated ACHIEVEMENT nomenclature.
SEQUENTIAL EXECUTION/ACHIEVEMENT acts to constrain the Task's Execution (Achievement) from starting until the reference-task-tag's Execution (Achievement) has completed.
If it's a Goal Task thats being constrained, then there is no effect upon that Goal Task's starting (Handling). Nor is there any effect upon the starting (Handling) of any descendant Goal Tasks. However, any Command Task descendants from that Goal Task will be constrained from starting their Handling until the reference-task-tag, and all the reference-task-tag's descendant subtasks, have completed their Handling.
If it's a Command Task thats being constrained, that Command Task may not start its Handling, nor may any of its descendant subtasks start their Handling until the reference-task-tag has completed its Handling, and all descendant subtasks of that reference-task-tag have completed their Handling.
If the reference-task-tag is not specified, it defaults to PREVIOUS. If there is no PREVIOUS task, this constraint has no effect.
With respect to Monitor Task-declaration-level monitor-constraints, a reference-task-tag must not be present. Instead, the default PREVIOUS is implied, and refers to the previous Monitor-Child-Instance Monitor activation. See Monitor Constraint Notes.
SEQUENTIAL EXECUTION/ACHIEVEMENT is equivalent to:
DISABLE EXECUTION UNTIL
reference-task-tag EXECUTION COMPLETED.
Constrains this Task's Handling from starting until the reference-task-tag's Execution has completed.
That is to say that this Task, and all descendant subtasks of this Task, may not start their Handling until the reference-task-tag, and all of the reference-task-tag's descendant subtasks, have completed their Handling.
If the reference-task-tag is not specified, it defaults to PREVIOUS. If there is no PREVIOUS task, this constraint has no effect.
With respect to Monitor Task-declaration-level monitor-constraints, a reference-task-tag must not be present. Instead, the default PREVIOUS is implied, and refers to the previous Monitor-Child-Instance Monitor activation. See Monitor Constraint Notes.
SERIAL is equivalent to:
DISABLE UNTIL
reference-task-tag EXECUTION COMPLETED.
Constrains this Task to complete Execution, cease functioning, and, unless TCM_SetPersistence has been invoked, to delete itself after it has been Activated the specified maximum number of times.
This constraint may only be applied to Monitor and Exception Handler Tasks.
Constrains this Monitor Task to complete Execution, cease functioning, and, unless TCM_SetPersistence has been invoked, to delete itself after it has been Triggered the specified maximum number of times.
This constraint may only be applied to Monitor Tasks.
Constrains this Monitor Task to automatically Activate itself whenever at least relative-time has passed since the last Activation.
This is not a real-time system. Relative-time forms a lower bound, not an absolute or upper bound on the time between Monitor Activations.
It is possible that, although Activated, a Monitor-Child-Instance may not be run (due to scheduling issues) before that Monitor is re-Activated. In such situations, either Monitor-Child-Instance could be run next. Monitor-constraints, applied at the Monitor Task-declaration-level, can resolve this ambiguity. See Monitor Constraint Notes.
A Periodic Monitor Task will re-Activate indefinitely, unless:
This constraint may only be applied to Monitor Tasks.
PARALLEL is the default, null constraint. It implies that there are no other constraints, which is the default condition.
PARALLEL is chiefly used in With Statements. Particularly for Nested With Statements. (See Advanced Topics.) It is meaningless in SPAWN and Constraint Statements.
PARALLEL refers only to constraints between SPAWN'ed subtasks. It never refers to means by which embedded C or C++ code will be executed. (See Advanced Topics: Embedded C/C++ code.)
WAIT is an unusual constraint. Rather than affecting the subtask being SPAWN'ed, it acts upon the current Task/function/method. It causes the code in the current Task/function/method that handles the SPAWN'ing of the subtask to block (not return) until the SPAWN'ed subtask completes its Execution. That is to say, until the SPAWN'ed subtask completes its Handling, and all descendants of that SPAWN'ed subtask complete their Handling.
In many ways WAIT can be considered analogous to the standard C/C++ function/method invocation. There are, however, two notable distinctions:
All other constraints acting upon this SPAWN'ed subtask (and its descendants) will need to be resolved prior to this SPAWN'ed subtask (or its descendants) starting, as appropriate. This can result in additional delays.
The NESTING bug: As detailed in the FAQ, the NESTING bug occurs when, while one is WAIT'ing on a particular subtask, other, unrelated Tasks are SPAWN'ed. (Perhaps through periodic Monitor Tasks, or perhaps through other mechanisms.)
One then winds up, via the NESTING bug, WAIT'ing on these other Tasks as well. Which, if they don't complete promptly, can delay the current Task/function/method from continuing its Handling for a very long time.
Constrains this Task's Handling, Expansion, or Execution from starting until:
The event has transpired.
See Event constraint
notes.
The current time is now at or past
absolute-time.
See Absolute-Time
constraint notes.
A period of
relative-time or longer
has elapsed since the event
transpired.
See
Relative-Time After Event constraint notes.
If the task-subset (Handling, Expansion, or Execution) is not specified, it is considered to be a union of all three. Neither this Task, or nor any descendants of this Task, will be permitted to start their Handling until the specified limitation has been resolved.
Forces this Task, and all descendants of this Task, to be terminated when:
Immediately.
Unlike the other forms of
TERMINATE, which queue the termination to
occur at some point in the future, this form alone
is implemented as a direct function call. The
termination occurs at this very point in the code.
The event has transpired.
See Event constraint
notes.
The current time is now at or past
absolute-time.
See Absolute-Time
constraint notes.
A period of
relative-time or longer
has elapsed since the event
transpired.
See
Relative-Time After Event constraint notes.
Note that the Task
may be terminated before it is Handled (run).
Termination has the effect of putting a Task, and all descendants of that Task, into a state similar to "Completed". Basically:
The Task, and all descendants of that Task, that have not yet been Handled, will never be run.
Any constraints dependent upon that Task's Completion, or upon the Completion of descendants of that Task, are permitted to proceed.
Any On-Terminate-Spawn constraints on that Task, or on descendants of that Task, are activated.
A small exception exists if a Task is terminated while it is currently being Handled, an occurrence which is only possible via the immediate termination option. In this unusual situation, the current Handling is not affected. However, any Tasks SPAWN'ed during the terminated Task's Handling, even if they were SPAWN'ed after the termination occurred, will be terminated.
Terminating a POSTPONED Task is somewhat more complex. The POSTPONED Task still needs to be RESUMED. This is a known issue with TCM. The employment of an On-Terminate-Spawn constraint is the recommended work-around.
Activate constraints may only be applied to Monitor Tasks.
Activate signals to create a new Monitor-Child-Instance of this Monitor Task when:
Immediately.
Unlike Immediate Termination, Immediate Activation is not implemented as a function call. There is an issue with TCM wherein a Monitor cannot be Activated properly before its (the Private-Monitor-Task's, not the Monitor-Child-Instance's) Handling has started.
Therefore, Immediate Activation is implemented in a manner equivalent to:
ACTIVATE AT theMonitor Handling Active
If the Monitor is already running (Handling Active), Activate will take effect the next time signals are resolved. Typically when the current Task's Handling concludes. Though it is possible for signals to be processed during an unrelated WAIT constraint via the Nesting Bug.
The event has transpired.
See Event constraint
notes.
The current time is now at or past
absolute-time.
See Absolute-Time
constraint notes.
A period of
relative-time or longer
has elapsed since the event
transpired.
See
Relative-Time After Event constraint notes.
PERSISTENT allows one to declare an extra variable for a Task. Multiple PERSISTENT constraints may be employed to declare multiple variables.
Unlike traditional variables, PERSISTENT declarations are dynamically allocated off the heap. They are never copied onto the local stack frame.
As a direct consequence, PERSISTENT declarations retain their values across multiple Task invocations. Such as the re-invocations that naturally reoccur with Monitors or Exception Handlers.
They are also useful with respect to passing data between a POSTPONED Task and its corresponding Resume Task.
Naturally, and conveniently, the heap memory utilized by PERSISTENT declarations is deallocated when the Task itself is deallocated.
Inside a TDL Task, return should not take an argument. Any argument provided is ignored, and any expression will not be evaluated.
return is equivalent to SUCCESS.
return, unlike most TDL keywords, is case-sensitive.
Success indicates that the Task has completed its Handling.
Much as return operates within C/C++ functions/methods, Success prevents any further processing of the Task's code. The current Task's Handling is marked as completed, and ceases at this point. Any additional code within this Task's body is not executed.
Any subtasks SPAWNED by this Task are unaffected, and will continue to run. This Task will not complete its Expansion / Execution until those subtasks, if any, have completed their Handling.
Postpone is the analogous counterpart of Success.
Like Success, Postpone prevents any further processing of the Task's code. Any additional code within this Task's body is not executed.
And like Success, subtasks are completely unaffected.
Unlike Success, a Postponed Task's Handling is not marked as completed. Rather, it remains in an uncompleted state. At a later point in time, this Task will need its Handling manually marked as completed (via the TCM interface), or it will need to be Resumed with a Resume Task that eventually invokes return or Success. A Resume Task can, of course, choose to continue the uncompleted postponed state by re-invoking Postpone.
Fail allocates an Exception Task, typically a data-containing construct, which is then passed upwards through the Task-Tree until an appropriate Exception Handler Task is located that can handle it. This Exception Handler Task is then invoked through the usual [internal] mechanisms. That is to say, it is invoked asynchronously, and other Tasks may be Handled prior to the Exception Handler Task being Handled.
Fail also acts to stop the Handling of the current Task. It prevents any further processing of this Task's code. Any additional code within this Task's body is not executed.
As with Success and Postpone, SPAWN'ed subtasks are completely unaffected.
TDL_REF(...) returns an instance of a TDL-internal data class. This class is guaranteed to support the (const TCM_Task_Tree_Ref &) casting operator, allowing the user direct access to the underlying TCM functionality.
THIS is a TDL keyword that refers to the current Task. TDL_REF(THIS) is used to access the current Task's TCM_Task_Tree_Ref.
TDL_REF(...) is most frequently used to pass Tasks around as arguments to other Tasks. The receiving Task must take a TCM_Task_Tree_Ref argument, not a reference to a TCM_Task_Tree_Ref. (TCM_Task_Tree_Ref is actually a tcmHandle, or a smart-pointer, that keeps track of when its data should be deleted.) The receiving Task can then utilize TDL_BIND to access the Task within TDL statements and constraints.
TDL_REF(...) is also frequently used to obtain the necessary TCM_Task_Tree_Ref argument for Resume Tasks.
TDL_BIND(...) acts to record, in the internal TDL bookkeeping, a new Task entry for the specified TCM_Task_Tree_Ref. It does not create the Task. Rather, it binds a name to that Task. This task-name may then be utilized within TDL constraints.
If the TCM_Task_Tree_Ref is an identifier, the shorter form will use that identifier as the task-name. Alternatively, the longer form may be employed to directly specify the task-name.
TDL_BIND(...) is most often utilized with TDL_REF(...) to provide a mechanism for passing Tasks around as arguments.
getNumberOfActivates() is only available inside Monitor Tasks. It returns the total number of times this Monitor has been Activated so far.
Generally, this value starts with 1, since one can't access getNumberOfActivates() prior to the first activation. It is incremented by 1 each time the Monitor Task is Activated.
It is possible for a Monitor Task to be Activated multiple times before the newly created Monitor-Child-Instances are Handled. In this situation getNumberOfActivates() may return an initial value larger than 1, or increment its previous value by more than 1. This holds true even if a Serial constraint is attached at the Monitor Task Declaration level.
If you require a unique integer index for each Monitor Activation, you should utilize a Persistent constraint to create a private index counter.
getNumberOfTriggers() is only available inside Monitor Tasks. It returns the total number of times TRIGGER() has been invoked for this Monitor.
getNumberOfTriggers() initially returns a value of 0. The value increases by 1 each time TRIGGER() is invoked.
TRIGGER() is only available inside Monitor Tasks. It increments a counter internal to the Monitor, the value of which can be accessed with getNumberOfTriggers().
TRIGGER() returns a value of type TCM_Return_Type. At the present time, the only actual value that TRIGGER() is capable of returning is TCM_Ok.
THIS is a TDL keyword that refers to the current enclosing Task. Not a child Task that might be in the process of being SPAWNED, but the current Task node you are presently inside of.
Outside of TDL Tasks, THIS can still be utilized. In such situations, it refers to the root node of the TCM Task-Tree.
CHILD refers to the current subtask that is in the process of being SPAWNED.
CHILD may be used in both TDL Tasks and outside of Tasks in C/C++ functions/methods.
PREVIOUS refers to the last successfully SPAWNED subtask, or the last set of successfully SPAWNED subtasks contained within a WITH Statement. (See Advanced Topics: Previous.)
PREVIOUS may be used in both TDL Tasks and outside of Tasks in C/C++ functions/methods.
Handling corresponds to the period of time when the TDL code that makes up a Task is actually running. Handling starts when the Task's code first starts running, and finishes when the Task's code ends with:
Handling does not conclude if POSTPONE is invoked. That is a special case wherein the Task's code stops running without concluding Handling.
Handling has no relationship with any subtasks that are SPAWNED, other than the obvious: That a Task's Handling must start before any subtasks can be SPAWNED, let alone Handled.
Expansion reflects the period of time from when the first Goal Task in a Task-Tree starts its Handling until the last Goal Task of that Task-Tree finishes its Handling.
For a specific Goal Task, Expansion starts as soon as its Handling starts. Its Expansion finishes as soon as its Handling has finished, and the Handling of all Goal descendants has finished.
For Command Tasks, Expansion is meaningless. Therefore, for Command Tasks, Expansion is considered to both start and finish as soon as that Command Task is created, before it ever begins its Handling.
In the unlikely event that a Command Task SPAWNS a Goal Task, that Goal descendant is considered part of the Execution phase, not part of the Expansion phase.
Execution reflects the period of time from when the first Command Task in a Task-Tree starts its Handling until the last Command Task of that Task-Tree finishes its Handling.
For a specific Goal Task, Execution starts as soon as the first Command Task descendant of that Goal Task starts its Handling.
For a specific Command Task, Execution starts as soon as it starts its Handling.
In both cases, Execution finishes when all descendant Tasks, regardless of Task-type (Goal, Command, Monitor), have finished their Handling.
In the degenerate case in which a Goal Task has no Command Task descendants, Execution is considered to both start and finish at the moment Expansion finishes. The reasoning being that one cannot determine generically beforehand that no Command Tasks will be created by any Goal descendants until they have all finished their Handling.
ENABLED refers to the point in time when a particular Task is permitted to start its Handling, Expansion, or Execution.
This is not necessarily when the Task does start its Handling/Expansion/Execution. But rather when all constraints and limitations preventing it from starting its Handling/Expansion/Execution are resolved. Other delays, stemming from resource-contention, the operating system, performance, or TCM implementation limitations can still delay the Task from becoming ACTIVE.
There is a small limitation in the present implementation of TCM wherein both ENABLED and ACTIVE refer to the Start_Point of the corresponding Interval (Handling, Planning [Expansion], Achieving [Execution]), and Completed refers to the End_Point of that corresponding Interval.
ACTIVE refers to the point in time when a particular Task actually starts its Handling, Expansion, or Execution.
There is a small limitation in the present implementation of TCM wherein both ENABLED and ACTIVE refer to the Start_Point of the corresponding Interval (Handling, Planning [Expansion], Achieving [Execution]), and Completed refers to the End_Point of that corresponding Interval.
COMPLETED refers to the point in time when a particular Task actually finishes its Handling, Expansion, or Execution.
There is a small limitation in the present implementation of TCM wherein both ENABLED and ACTIVE refer to the Start_Point of the corresponding Interval (Handling, Planning [Expansion], Achieving [Execution]), and Completed refers to the End_Point of that corresponding Interval.
event:
reference-task-tag [ task-subset ] state-boundary
An event corresponds to another Task's Handling, Expansion, or Execution becoming Enabled, Active or Completed.
If the optional task-subset is not present, it is considered to refer to the entire set of Handling, Expansion, and Execution of the reference-task-tag. That is to say:
With respect to
Enabled:The event occurs when the reference-task-tag has its Handling Enabled. With respect to
Active:The event occurs when the reference-task-tag has its Handling Active. With respect to
Completed:The event occurs when the reference-task-tag, and all of its descendant subtasks, have completed their Handling. (E.g. Completion of Execution.)
As TCM/TDL is not a real-time system, it does not guarantee that anything will happen precisely at absolute-time. Rather, it guarantees that nothing will happen before absolute-time, providing a minimum rather than a maximum or exact boundary.
As with the absolute-time based constraints, as TCM/TDL is not a real-time system, it does not guarantee that anything will happen precisely at relative-time. Rather, it guarantees that nothing will happen before relative-time, providing a minimum rather than a maximum or exact boundary.
If the optional AFTER event clause is not present on a SPAWN or WITH Statement based constraint, the event is considered to be the creation (SPAWN'ing) of each individual Task, and the constraint will not resolve until at least relative-time after the moment that Task was created (SPAWN'ed).
If the optional AFTER event clause is not present on a Constraint Statement based constraint, the event is considered to be that Constraint Statement itself. For each Task affected by this Constraint Statement, the constraint will not resolve until at least relative-time after the moment that Constraint Statement, was invoked.
E.g.:
Task foo() is permitted to
start running in 5 [3+2] seconds.
[SPAWN] sleep(3); SPAWN foo() WITH DISABLE FOR 0:0:2.0;Task foo() is permitted to
start running in 5 [3+2] seconds.
[WITH] WITH ( DISABLE FOR 0:0:2.0 ) { sleep(3); SPAWN foo(); }Task foo() is permitted to
start running in 3 [MAX(3,2)] seconds.
The DISABLE-FOR constraint
runs concurrently with the sleep(3);
[Constraint Statement] foo DISABLE FOR 0:0:2.0; sleep(3); SPAWN foo();
It is possible, as demonstrated above, for a relative-time delay to expire prior to a Task being SPAWNED.
It is also possible, with respect to the Terminate-In constraint, for a Task's relative-time delay to expire prior to that Task actually being Handled. (Thus TERMINATING the Task prior to it ever being run.)
If one prefers the TERMINATION to occur a relative-time period after the Task has started running, as opposed to after the SPAWN / Constraint Statement is invoked, one should utilize a more appropriate event. e.g:
SPAWN foo() WITH TERMINATE IN 0:0:1.0 AFTER foo ACTIVE;
TODO: C++ function/method bootstrapping invocation of a Task restrictions.
TODO: The Special significance of labels.
TODO: Notes: Invoking a Task as a Function. Previous is not applied.
TODO: Notes: Constraints that can NOT be applied after a Task has been SPAWN'ed.
TODO: Iteration: any-valid-C++-numeric-integer-expression.
TODO: Iteration: iteration indexes.
TODO: Nested WITH Statements (and parallel constraint)
TODO: Advanced Topics (Chapter 6).
TODO: Advanced Topics (Chapter 6): Embedded C/C++ code.
TODO: Advanced Topics (Chapter 6): Previous
MISC TODO: WITH, SPAWN, functional-invocation, constraint statement.
The SPAWN statement can be utilized outside of Tasks, inside C++ functions. In such a situation, any constraints on that SPAWN statement are limited to utilizing just the reference-task's PREVIOUS, PARENT, and SELF. Such tasks are always attached to the TCM_RootNode(), which forms their parent.
Tasks that are invoked as a function, without the SPAWN keyword, do not receive any constraints from enclosing WITH statements. They are run with the sole constraint of WAIT. And they are not considered when deciding the value of PREVIOUS.
The [EXCEPTION] HANDLER constraint may not be utilized inside a WITH statements constraint list.
TRIGGER(), getNumberOfTriggers(), and getNumberOfActivates() are expressions. They can be used anywhere a normal C++ expression can be used, including in the predicate of if and switch statements. However, they can only be used inside Monitor Tasks.
All TDL keywords are case-insensitive. Capitalization does not matter. "SERIAL", "serial", and "SeRiAl" are all equivalent in TDL.
When two keywords occur adjacently, they can frequently be combined with an underscore. Underscores are optional in these locations. Moreover, there can be zero, one, or many underscores. Examples include: EXPAND_FIRST, DELAY_EXPANSION, SEQUENTIAL_HANDLING, SEQUENTIAL_EXPANSION, SEQUENTIAL_EXECUTION, SEQUENTIAL_PLANNING, SEQUENTIAL_ACHIEVEMENT, DISABLE_UNTIL, DISABLE_FOR, TERMINATE_AT, TERMINATE_IN, ACTIVATE_AT, ACTIVATE_IN, MAXIMUM_ACTIVATE, and MAXIMUM_TRIGGER.
TDL_REF also shares this ability. It can be accessed as TDLREF, TDL_REF, TDL__REF, TDL________REF, etc...
Future references are permitted. Past references for constraint-statements are permitted. However, depending on the constraint, the constraint may no longer be applicable. ("Disable", "sequential", and "serial" constraints in particular do not work very well after a Task has been started.)
The following C++ expressions, statements, and function calls are illegal in TDL: "goto", "throw", "catch", "finally", "setjmp", "longjmp", "_setjmp", "_longjmp", "sigsetjmp", "siglongjmp", and "this".
If a reference-task is not specified for a constraint, PREVIOUS is assumed.
Macros may not exist inside TDL Tasks. They may, however, be defined outside of Tasks, and used as constants or functions inside Tasks.
Utilization of a future-reference that is a WITH statement inside a constraint is currently unsupported. This is a known bug. TDLC will generate an error when it detects this situation.
Array-Indexes are currently ignored, and will trigger a TDLC-generated warning if utilized.