Covers Engine 2.0.0
Deterministic Automation
The requirements that shape DARTWIC's prepared periodic path: scheduling, health, ordered composition, fixed channels, and Native C++ DCode.
This page defines the design target for deterministic automation in DARTWIC. It is not a claim that Windows, a driver, a network, or arbitrary user code is hard real-time.
The key promise is narrower and testable:
After activation, DARTWIC-owned work on a prepared periodic path has a declared finite work set, uses pre-provisioned storage, does not wait behind configuration or unknown delivery code, and reports an explicit outcome when capacity or timing assumptions are exceeded.
That promise is why fixed RAPID channels and Native C++ DCode exist. They are not generic performance switches and fixed storage was not created merely because a signal has a known name.
Start here: choose the path that fits the automation
Dynamic channels and Lua are the normal choice for most DARTWIC automation. They are appropriate for operator workflows, sequencing, state machines, changing signal names, tables, imports, prompts, managed waits, and ordinary device integration. Do not choose fixed channels or Native C++ DCode merely because a task has a frequency.
Use the prepared fixed/native path when a control loop has known, repeatedly used numeric inputs and outputs, and measurement shows that it needs a tighter, more predictable DARTWIC-owned execution cost. That is often worth considering for a demanding 100–1,000 Hz acquisition → control → output loop, but the frequency is not the proof: a 100 Hz Lua task can be entirely appropriate, while a 1,000 Hz native task can still run late or overrun.
| Need | Recommended starting point |
|---|---|
| Flexible or evolving automation | Dynamic channels + Lua. |
| Known repeated numeric path with measured execution-cost pressure | Fixed channels + Native C++ DCode, or a plugin with resolved fixed handles and reusable buffers. |
| Hard physical deadline | A real-time-capable, qualified control host plus full driver/device qualification—not only a DARTWIC configuration. |
The engine must run near the control decision and hardware. A remote Interface does not make an engine loop local. Standard Linux is also not automatically a real-time operating system; a real-time OS or deliberately configured real-time-capable environment, such as a qualified Linux PREEMPT_RT deployment where appropriate, is part of a consequential tight-loop deployment. It still does not make unbounded callback or driver work safe.
For the full tradeoff table and deployment checklist, see Choose the simplest path that meets the measured need.
The three elemental requirements
| Requirement | DARTWIC must provide | The application still owns |
|---|---|---|
| 1. Periodic execution | An absolute monotonic release plan, one non-overlapping invocation per release, explicit late and missed-release behavior, and predictable DARTWIC-owned work inside the task. | The deadline required by the physical process, plugin/device behavior, operating-system configuration, and the work in the callback. |
| 2. Health and response | Independent detection and reporting of late, missed, failed, and non-completing work. | The response: inhibit output, hold a controller, alert an operator, transition state, or continue. |
| 3. Deterministic composition | A TaskGroup runs due members in declaration order; one member returns and commits its writes before the next begins; outcomes are recorded. | Whether a later member may act after an earlier member fails, is stale, or was stopped. |
These are independent. A task can start on time and still overrun. A group can preserve read -> control -> write ordering while the read fails. A completed write is not proof that a physical actuator moved.
1. Periodic execution
| Sub-requirement | DARTWIC behavior |
|---|---|
| 1.1 Absolute monotonic release plan | Advance releases from a monotonic epoch instead of sleeping a full period after each callback. |
| 1.2 One non-overlapping invocation | Do not run a task callback concurrently with itself. |
| 1.3 Explicit late/missed-release behavior | Measure late starts, skip already-missed releases rather than replaying a backlog, and report the result. |
| 1.4 Predictable DARTWIC-owned work | Make the framework work inside the released callback prepared, bounded, and observable. |
Periodic execution is not only a scheduler problem.
1. Periodic execution
├─ 1.1–1.3: release timekeeping
│ absolute monotonic plan, no overlapping callback, explicit slip/miss
└─ 1.4: predictable DARTWIC-owned work inside the released callback
fixed inputs → explicit task logic → fixed output staging → fixed commit
└→ bounded handoffCAESAR handles the first branch: it advances from a monotonic release plan, does not overlap a callback with itself, and records skipped releases instead of replaying an unbounded backlog.
The second branch is the reason for the prepared fixed path. Calling a callback every 10 ms is not useful if the framework can turn one release into “look up arbitrary names, grow a container, allocate a publication, wait for configuration, or run an unknown telemetry callback.”
1.4 Predictable DARTWIC-owned work
| Sub-requirement | Meaning on a periodic path |
|---|---|
| 1.4.1 Prepared memory | Inputs, staged outputs, publication records, dependency scratch space, and queues are provisioned before release. The normal path does not allocate or resize them. |
| 1.4.2 Bounded framework work | Framework work is proportional to the task's declared inputs, outputs, and declared inline dependencies—not to all channels, users, or a dynamically growing map. |
| 1.4.3 Controlled synchronization | A task does not wait behind configuration or unknown code. Locks protect small known state, have a defined order, and are not held while calling user, plugin, or observer code. |
| 1.4.4 Explicit dependency execution | A calculation, event, or DCode action caused by a write is either inline and charged to this invocation, or asynchronous and not ordered with this cycle. It is never hidden. |
| 1.4.5 No synchronous external work | UI, history, Share, recording, database, network, and ordinary observers receive a bounded handoff; they cannot stall a CAESAR callback. |
| 1.4.6 Explicit exhaustion outcomes | A pool, queue, or declared-graph capacity exhaustion produces a counter, event, or health status—not allocation fallback or silent loss. |
| 1.4.7 Traceability | At activation, DARTWIC can report the fixed inputs/outputs, inline dependencies, capacities, and whether the task meets the prepared-path contract. |
This is a contract for DARTWIC-owned framework work. It deliberately does not ban a plugin’s device I/O, a user loop, or a user-selected timeout. Those remain permitted and are charged to the callback’s measured duration.
Why fixed RAPID channels exist
Fixed channels exist to remove recurring dynamic-memory and storage-management work from a repeated path. A normal dynamic-channel path is valuable for general-purpose use, but its storage and publication machinery may grow, move, allocate, or require name-based dispatch. Those are the wrong default assumptions for a control loop that has already declared its channel set.
The fixed design moves that work to configuration and activation:
Configuration / activation Every periodic release
-------------------------- ----------------------
reserve fixed slots ─┐
name → {slot, generation} binding │ copy declared input slots
resolve task's fixed handles ├─→ run task logic
reserve snapshots, output staging, │ stage declared output slots
publication records, and scratch buffers │ atomically commit them
configure/seal the fixed layout ─┘ hand off noncritical deliveryWhat each part buys
| Fixed-channel design choice | Requirement it serves | Why it matters |
|---|---|---|
| Preallocated fixed slot array | 1.4.1 | Channel payload storage does not need to be dynamically allocated or relocated in the task cycle. |
Resolved {slot, generation} handle | 1.4.2 | Native DCode and SDK fixed batches resolve the channel name at activation, then use a validated handle instead of repeated channel-name lookup on each release. |
| Task-owned snapshots and output buffers | 1.4.1, 1.4.2 | A release copies only its declared inputs and stages only its declared outputs using reusable storage. Work scales with that declaration. |
| Generation validation | 1.4.3 | If a slot was rebound, an old handle fails cleanly rather than silently writing a different signal. |
| Atomic fixed-output commit | 1.4.2, 1.4.3 | The task’s accepted outputs become one coherent generation after user logic returns; short storage locks protect the copy/commit, not the callback’s arbitrary work. |
| Separate configuration boundary | 1.4.3 | Channel creation, deletion, rebinding, and allocation happen outside the prepared task path. A conflicting prepared release is rejected promptly rather than becoming a configuration waiter. |
Fixed channels also give useful transaction semantics: declared inputs are the task’s coherent view for that release, and accepted outputs are staged before commit. That is important, but it is not their original determinism purpose. It does not make data fresh, coordinate separate devices, or order tasks. A TaskGroup provides local task order.
The precise boundary of the claim
“Fixed” alone does not prove an allocation-free or bounded release. A task meets the prepared-path memory claim only when all of its repeated resources have capacity already provisioned and the path has no fallback allocation.
For example, the ordinary RAPID transaction path currently preserves availability by allocating a FixedPublication when its prepared publication pool is busy. That is a reasonable general-purpose fallback, but it is explicitly not bounded timing. Likewise, a logically bounded queue is not a no-allocation guarantee if its backing container can still grow. A strict periodic profile must report resource exhaustion and follow a declared drop, coalesce, or defer policy instead of allocating on the task thread.
The strongest supported repeated-release path
The most predictable DARTWIC path is a CAESAR-scheduled task with declared RAPID fixed channels and prepared task resources. Use Native C++ DCode for supported repeated numeric logic, or a plugin that resolves fixed handles and reuses its own arrays/buffers during configuration. Keep inline dependencies native and bounded when they are part of the task’s cycle budget.
This is a positive engineering recommendation, not a ban on Lua or drivers. Lua, dynamic channels, arbitrary plugin work, and blocking calls remain valid where their flexibility is appropriate. They are simply outside the qualified prepared-core allocation result.
| Path | What DARTWIC can say |
|---|---|
| Lua, dynamic channels, or an arbitrary plugin callback | General-purpose execution. No allocation-free or timing-bound claim applies. |
| Native C++ DCode or a prepared fixed-channel plugin, with fixed declared inputs and bounded native inline dependencies | DARTWIC removes repeated core lookup/preparation work and reuses prepared transaction storage. The tested Release numeric workload observed zero host C++ allocations/frees in this qualified core transaction. |
| A deployed control path | It still needs its own evidence for callback code, native/plugin-library allocation, locking, OS lateness, driver transport, device feedback, and its actual deadline. |
“Native” by itself is not enough: it does not bound a loop, sleep, driver call, native-library allocator, or transitive calculation chain. Likewise, “no inline Lua” is a useful condition for the strongest path but not a whole-system allocation proof. The allocation probe covers host C++ allocation/free in the measured engine path; it does not observe Lua allocation, direct malloc, or separately linked native/plugin DLL allocators.
Choose the simplest path that meets the measured need
Most DARTWIC automation does not need fixed channels or Native C++ DCode. Dynamic channels and Lua are the normal choice when flexibility, readable automation, changing channel sets, managed waits, tables, prompts, imports, or ordinary operator workflows matter more than a tightly controlled repeated execution cost.
| Situation | Start with | Move to the prepared path when |
|---|---|---|
| UI automation, process sequencing, state machines, ordinary device orchestration, or changing signal names | Dynamic channels + Lua | Measurement shows the callback, repeated lookup/allocation, or its timing variance is a material problem. |
| A known numeric acquisition → control → output cycle | Dynamic/Lua is still valid while developing and at modest, measured rates. | Inputs/outputs are stable, the cycle is repeated often, and a bounded/prepared core cost is useful. |
| A tight 100–1,000 Hz control loop | Start with a small local prototype and measure end-to-end behavior. | Use fixed channels plus Native C++ DCode, or a prepared fixed-channel plugin, when the task has a known bounded shape and qualification shows the general path is insufficient. |
| A hard physical deadline | DARTWIC diagnostics and ordinary desktop/server deployment are not the certification answer. | Run the control logic on a real-time-capable, qualified host and prove the complete device path under load. |
Frequency alone is not the decision. A 100 Hz Lua task can be entirely appropriate; a 1,000 Hz native task can still miss its deadline if it blocks on a driver, waits on a lock, runs an unbounded calculation, or is scheduled late. Move to the prepared path because a measured control contract requires it, not because fixed channels are assumed to be universally better.
The engine process must run where the control decision is made. A remote Interface does not make a remote engine loop local to the hardware. Standard Linux is also not automatically a real-time operating system. For consequential tight-loop work, use a real-time operating system or a deliberately configured real-time-capable environment—such as a qualified Linux PREEMPT_RT deployment where appropriate—then measure scheduling latency, driver/transport latency, CPU contention, and device feedback on the actual target hardware. That host choice improves the deployment boundary; it does not excuse unbounded callback or driver behavior.
Why Native C++ DCode exists
Native C++ DCode is the companion to fixed channels on the prepared path:
DCode source
→ activation/build
→ generated C++ callback with prepared host bindings
→ periodic invocationIts purpose is to remove Lua interpretation and dynamic script dispatch from the repeated automation callback. Static channel references can bind to the same fixed {slot, generation} handles during activation. The generated C++ still calls DARTWIC’s channel API; it does not replace RAPID’s storage or bypass validation, command authority, snapshots, or commit rules.
The required behavior is semantic, not aspirational:
A task configured for Native C++ either builds and activates its native callback, or activation fails. It must not silently fall back to Lua.
Native C++ does not make arbitrary code bounded. A native while loop, allocation, lock wait, sleep, or device operation is still callback work and appears in its duration/overrun health data.
Delivery and dependencies must not be surprises
A channel write can cause more work. The deterministic question is not “does that work exist?” but “where is it accounted for?”
prepared task writes a fixed output
|
+--> inline calculation / event
| runs before the writer returns
| ordered with this cycle and charged to its duration
|
+--> UI / history / Share / recording / ordinary observer
receives bounded asynchronous handoff
not an acknowledgement and not part of control orderAn inline Lua calculation is allowed, but it is not free because the initiating task is Native C++. If the calculation is reached synchronously, its interpreter time and any wait on its execution mutex are part of the writer’s cycle budget. Make it bounded, move it to an explicitly asynchronous design, or treat the resulting overrun as an application fault.
Telemetry, history, Share, and UI delivery have the opposite rule: they must never run arbitrary consumer work on the CAESAR callback. A bounded handoff may drop or coalesce an asynchronous notification under pressure while the accepted live channel value remains committed. The loss/lag counter is part of the health evidence; control code must not wait for a subscriber as acknowledgement.
2. Health and response
| Sub-requirement | DARTWIC behavior | Application responsibility |
|---|---|---|
| 2.1 Independent liveness monitor | Health is sampled outside the callback, so a blocked task is not responsible for reporting its own hang. | Select the monitor/deadline policy appropriate to the hazard. |
| 2.2 Explicit timing and failure observations | Report late start, missed release, callback exception, overrun, and active-overdue state. | Decide which conditions are faults for this automation. |
| 2.3 Observable prepared-path failures | Report configuration rejection, stale fixed binding, resource exhaustion, and asynchronous-delivery loss when those conditions are exposed. | Include them in alarms, logs, and qualification evidence. |
| 2.4 Application-owned response | Provide diagnostics and an observer boundary; do not silently choose an output policy. | Inhibit, hold, transition state, alert, or continue based on the device’s real safety case. |
Health is intentionally separate from scheduling. The task that is blocked cannot be responsible for reporting its own hang.
CAESAR reports release lateness, skipped releases, callback failure, completion duration/overrun, and an active-overdue callback. A health observer must itself remain small and nonblocking. DARTWIC reports the condition; the application chooses whether to inhibit a command, hold state, alert an operator, or apply a hardware-specific safe response.
For a prepared task, health should also make the framework assumptions visible: configuration rejection, fixed-handle failure, publication/queue exhaustion, asynchronous-delivery loss, and the identities/durations of inline dependencies.
3. Deterministic composition
| Sub-requirement | DARTWIC behavior | Application responsibility |
|---|---|---|
| 3.1 Declared execution order | A TaskGroup dispatches members due on the same group release in declaration order. | Put the actual dependency chain in a group. |
| 3.2 Completed-before-next-starts visibility | A member returns and its accepted writes commit before the next group member begins. | Do not mistake completion for a fresh or successful physical device operation. |
| 3.3 Explicit upstream outcome | Record whether a due member completed, failed, was stopped, or was not dispatched. | Define whether a downstream member may run after every possible upstream outcome. |
Task input/output snapshots do not establish a control cycle. They do not prove the producer ran this cycle, succeeded, or published fresh data. Task order does.
task_group device_cycle:
run: device_read
run: control
run: device_writeWhen these members are due together, the group executes device_read, waits for it to return and commit accepted writes, then executes control, then device_write. Members do not overlap inside that group. member_last_outcomes records completed, callback_failed, skipped_stopped, not_dispatched, or not_yet_released.
The group preserves ordering; it does not invent success or freshness. If device_read fails, control can still see the prior committed measurement unless the application checks validity/freshness and makes a different decision. That policy belongs to the automation and device safety design.
Build and qualify a prepared automation
- Declare the release contract. Frequency, allowable start lateness, completion deadline, and missed-release policy.
- Declare the fixed contract. Every input/output, fixed capacity, fixed binding, publication/queue capacity, and inline dependency.
- Prepare before release. Configure modules and channels, resolve fixed batches, reserve buffers and packet layouts, establish connections where possible, build Native C++ DCode, then activate.
- Keep configuration out of the cycle. Rebind/reconfigure in a lifecycle or maintenance phase; treat a rejected prepared release as an explicit result.
- Keep external delivery out of the cycle. Never make telemetry, history, UI, Share, logging, or a subscriber acknowledgement a synchronous control step.
- Measure the entire declared path. Release lateness, callback duration, missed releases, inline dependency time, pool/queue exhaustion, TaskGroup outcomes, transport duration, connection state, and device feedback.
- Exercise failure deliberately. Test allocator pressure, publication exhaustion, telemetry pressure, a slow inline calculation, configuration contention, disconnected hardware, late wakeups, and failed/stale acquisition.
The useful outcome is not a generic “deterministic” label. It is a documented statement such as:
At 100 Hz, control_task uses 6 declared fixed inputs and 2 fixed outputs.
Its Native C++ callback and fixed publication resources are prepared at activation.
It has no synchronous telemetry delivery and declares one inline calculation.
Late release, callback overrun, stale input, failed read, and resource exhaustion
each produce a recorded condition and a defined application response.That is the design DARTWIC is built to make possible: predictable framework work, explicit user work, observed failures, and a deliberate response.
References
CAESAR Tasks, Channels, and Execution and Timing describe the scheduler and channel APIs. F Prime’s rate-group guidance and health component illustrate the same architectural separation between periodic dispatch, ordered synchronous work, and independent health supervision.