|
tin
1.5.9
|
runtime is the execution layer inside tin. It is the part of the framework that moves typed work through bounded storage, wakes cooperative tasks, advances integer-tick timers, and records simple dispatch metadata.
The runtime layer answers mechanical questions:
Those questions show up in embedded control, host simulation, trading systems, test replay, middleware adapters, and application servers. The runtime APIs do not know the product domain. They operate on C++ types supplied by the application.
Think of runtime as a small set of deterministic building blocks:
The application decides what values mean. runtime decides how those values are stored, moved, woken, and counted.
tsm uses runtime policies when state-machine events need queues, delayed delivery, executors, or resource accounting. The state-machine definition still owns the legal behavior: states, events, guards, actions, history, and transition rules.
tio describes portable I/O-shaped contracts. A tio ADC or GPIO facade can publish samples or events into a runtime channel.
thal binds runtime work to a target. A thal adapter decides how interrupts, threads, OS wakeups, monotonic clocks, or hardware ticks become calls into runtime primitives.
Application and domain layers own product concepts such as drive state, market-data books, simulation models, schemas, exchange connectivity, robot models, or middleware graph policy.
tin/runtime.h for concrete tin runtime primitives and bounded queue policy details.tin::queue<T, StoragePolicy, OverflowPolicy> for explicit FIFO storage.tin::queue_policy<StoragePolicy, OverflowPolicy> for runtime queue configuration.tin::static_storage<N> and tin::target_storage<N> for fixed queue storage policies.tin::channel<T, Capacity, OverflowPolicy> for bounded local value movement.tin::sender<T>, tin::receiver<T>, and tin::latest_reader<T> for non-owning channel views.tin::overflow::reject_newest, tin::overflow::drop_oldest, and tin::overflow::overwrite_latest for explicit full-buffer behavior.tin::dispatch_context for plain tick/sequence metadata.tin::tick_duration, tin::tick_count, and tin::ticks(n) for semantic tick values used by timers, coroutine sleeps, and dispatch metadata.tin::event_sink<Sink, Event> and tin::event_source<Source, Event> for adapter-facing contracts.tin::actor_like<T>, tin::actor_sink<T, Event>, and tin::actor_source<T, Event> for deterministic component composition.tin::input_port<Event, Sink> and tin::output_port<Event, Source> for typed actor wiring.tin::actor_link<Source, Sink> and tin::actor_group<Actors...> for explicit local composition.A queue is the explicit FIFO storage primitive. It combines a storage policy with an overflow policy for code that owns direct push/pop access.
static_storage<N> is portable inline storage. target_storage<N> keeps the same public type while allowing a platform header to select a target-specific queue backend.
A channel is fixed-capacity storage for local typed values. Its capacity and overflow policy are part of the type.
The default overflow policy is reject_newest: when the channel is full, the new value is rejected and existing values stay queued.
Different systems want different full-buffer behavior. Runtime makes the choice visible in the channel type.
drop_oldest keeps accepting new values by discarding the oldest queued value. That can be a reasonable policy for high-rate streams where stale data is less useful than fresh data.
Latest-sample channels use overwrite_latest:
overwrite_latest is for "current value" streams. Consumers that need FIFO history should use reject_newest or drop_oldest with a larger capacity.
Channels can produce non-owning views. This lets one part of a program send without receiving, another part receive without sending, and another part read the latest accepted value without consuming FIFO state.
The channel still owns the storage. The handles only expose narrower authority.
An actor is a component that runtime can advance cooperatively. It may own a queue, wrap an HSM runtime that has a queue, read from a driver adapter, produce replay records, or consume reports.
The actor surface answers one scheduling question: if the caller gives this component a turn, can it make bounded progress now? The actor API does not create threads, register callbacks, own the execution loop, or prescribe middleware names.
The basic actor shape is:
step() performs at most one unit of immediately ready work. drain() repeats that local work until the actor has no more immediate progress to make. empty() and pending_events() expose backlog without requiring instrumentation or heap-backed queues.
Queued HSM runtimes already satisfy this shape:
This matters once a system has more than one independent runtime component. A controller can be an HSM actor, a sensor path can be a channel-backed actor, and a host replay adapter can be another actor. The caller still owns the execution loop. See actor tutorial for a complete source-to-HSM-to-sink example.
Ports are non-owning typed views. An input port can send one event type into a sink. An output port can receive one event type from a source.
The source and sink still own their queues and state. The ports only expose the authority needed by a link. This keeps driver code, replay code, behavior code, and reporting code from receiving more control surface than they need.
An actor link moves typed values from one output port to one input port. It moves at most one value per step().
The link has one pending slot. If it receives a value from the source but the destination rejects it because its bounded queue is full, the link keeps that value and retries it on a later step(). That makes overflow behavior explicit: the sink still rejects admission, and the link prevents accidental loss between the source and sink.
Use link_all(link_a, link_b, ...) when a superloop wants to advance several links in declaration order.
An actor group is a deterministic stepping view over several actors. It does not own actors; it stores references to actors owned by the application.
actor_group::step() visits actors in declaration order and gives each actor at most one unit of work. actor_group::drain() repeats that process until no actor reports progress. This is useful for tests, bare-metal superloops, simulators, and host event loops because the scheduling order is visible in C++ and reviewable.
runtime_group remains available for the narrower case where every component is a runtime object. actor_group is the more general composition surface.
Actor resources aggregate the same bounded storage facts used by runtime resource manifests.
HSM runtimes contribute their existing runtime_resources snapshot. Generic actors can provide:
Actors without a resource snapshot contribute zero. That default keeps the composition API open for small direct actors while allowing production actors to publish precise queue, timer, task, and heap-use accounting.
Use actors when a system has multiple independently stepped components:
The actor layer is the bridge between primitives and applications. Channels store values. HSMs define legal behavior. Executors decide when work runs. Actors describe how several bounded components form one local system.
Runtime also provides cooperative task and timer primitives. Tasks can suspend on runtime operations and resume later through an executor. Timers are driven by integer ticks supplied by the application or platform adapter.
The first-class task guide is coroutines. This runtime reference keeps the mechanical timer and wakeup role visible; coroutine authoring, execution loops, task groups, cancellation, and resource accounting live on the coroutine page.
The runtime timer queue does not read a wall clock. A host test, bare-metal SysTick adapter, RTOS tick hook, or simulator advances ticks explicitly.
Adapters use small concepts for event input and output.
The concept only says that runtime.send_event(event) returns something convertible to bool. Ownership, threading, transport, and retry policy belong to the adapter or application.
dispatch_context is plain metadata for deterministic dispatch, tracing, and replay.
The tick count is a std::chrono::duration type, not a clock. It can count up and down without binding the runtime to wall time, operating-system clocks, or chrono clock sources. The default representation is std::uint32_t and the default period is std::ratio<1>.
Projects with microsecond ticks, long-running simulations, or high-frequency host systems can compile the framework with a wider unsigned representation and an explicit period, for example -DTSM_TICK_REP=std::uint64_t -DTSM_TICK_PERIOD=std::micro. The same tin::tick_duration type is used by tin::tick_count, dispatch metadata, logging, and replay records. Timer queues still operate on integer tick counts and do not read clocks.
It does not identify nodes, topics, files, operating-system handles, or transports. Higher layers can embed it in their own records.
A complete runtime path usually looks like this:
The important point is that the handoff is typed, bounded, and reviewable.