Deterministic discrete-event, multi-agent simulation engine. Stable single-threaded core (aika-st) and experimental optimistic parallel engine (aika-mt-hybrid). Includes a builder API for ergonomic agent wiring.
Goals:
- Reproducible: Same seed => identical execution order (time, seq_no, agent_id).
- Efficient short-term scheduling via hierarchical timing wheel + overflow heap.
- Extensible toward optimistic parallel (Time Warp) execution.
- Small, explicit core types (no macro DSL) with predictable memory layout.
+------------------+ +-------------------+
| aika-core |<-------| user agents |
| types & traits | +-------------------+
+---------+--------+
| (feature mt-hybrid)
v
+---------+---------+ +----------------------+
| aika-st (wheel) | | aika-mt-hybrid |
| deterministic | | optimistic (Time W) |
+-------------------+ +----------------------+
Run an example:
cargo run -p aika-st --example ping_pong
Add to your project (path dependency while unpublished):
[dependencies]
aika-core = { path = "../better.aika/aika-core" }
aika-st = { path = "../better.aika/aika-st" }
Define a Pod + Zeroable message and an Agent implementing step:
#[repr(C)] #[derive(Copy,Clone,bytemuck::Zeroable,bytemuck::Pod)]
struct Msg { x: u32 }
struct Counter { left: u32 }
impl aika_core::Agent<aika_core::DEFAULT_SLOTS, Msg> for Counter {
fn step(&mut self, cx:&mut aika_core::Cx<aika_core::DEFAULT_SLOTS, Msg>, id: aika_core::AgentId) -> aika_core::Action<Msg> {
while let Some(_m)=cx.inbox.pop() {}
if self.left==0 { return aika_core::Action::Sleep(100); }
self.left -=1;
cx.send(id, Msg { x: self.left }, 1); // self message in 1 tick
aika_core::Action::Sleep(1)
}
}Build a world using the builder (avoids manual downcasts / unsafe wiring):
use aika_st::engine::{WorldBuilder};
let mut builder = WorldBuilder::<Msg>::new(10_000, 42);
let a = builder.add(Counter { left: 10 });
let mut world = builder.build();
world.run();
println!("finished at t={} seed=42", world.now());World assigns a monotonically increasing sequence number (per world) to each scheduled event; ordering keys are (time, seq_no, agent_id) ensuring total order and stable replay with same seed. Each agent receives its own RNG seeded by world_seed ^ agent_id.
time: scheduled execution tick.
commit_time: (future) irrevocable time after GVT advancement in optimistic engine. In the single-threaded engine they are identical.
Each agent owns a bounded ring inbox (RingBuf). During a step, an agent drains its inbox, may emit outbound messages via cx.send(to, msg, after). The engine converts these into internal Deliver events and immediately wakes the recipient if appropriate.
Overflow behavior: pushing to a full mailbox returns an error (currently ignored by the engine). Tests assert mailbox length never exceeds capacity so excess messages are safely dropped. Future versions may surface metrics or backpressure hooks.
Time Warp style optimistic processing (feature gated):
- Per-thread local time + event queues
- Straggler detection => rollback via snapshot restore + anti-messages
- Global Virtual Time (GVT) reduction for fossil collection Current implementation is a stub exposing a config and monotonic GVT placeholder.
The phold_st example and Criterion bench implement a configurable PHOLD-like workload stressing scheduling & message delivery. Historically env vars were used; now a lightweight CLI is provided (still honors env defaults):
cargo run -p aika-st --features metrics --example phold_st -- \
32 20000 \ # positional: <agents> <terminal_time>
--remote 0.12 \ # probability remote send
--fanout 6 \ # max fanout per processed message
--stim 0.08 \ # idle self-stim probability
--metrics \ # enable metrics capture (requires 'metrics' feature)
--runs 5 \ # repeat simulation N times aggregating histograms
--top 12 \ # print top N busiest slots
--metrics-json phold_runs.json \ # JSON metrics artifact (auto timestamp if omitted)
--metrics-csv phold_hist.csv # CSV aggregated histogram
When --metrics is active (and the metrics feature enabled) the example collects per-tier 256-slot occupancy histograms (4 tiers) for the hierarchical timing wheel. Across multiple --runs it aggregates:
- Aggregate histogram (sum of per-run counts)
- Busiest slots (top N by count) with percentage of total
- Full descending cumulative distribution (CDF) across all non-zero slots
- Per-slot probability variance across runs (sparse: only non-zero variance entries)
- Peak queued events, processed event count, total delivered messages
Artifacts:
- JSON (
--metrics-jsonor defaultphold_metrics_<epoch>.json): structured runs array + aggregate + busiest + full CDF + prob_variance - CSV (
--metrics-csv): rowstier,slot,count,probfor non-zero counts in aggregate
Example to just view busiest slots once:
cargo run -p aika-st --features metrics --example phold_st -- --metrics 16 10000 --top 10
Per-agent event distribution stats are printed for the first run (or the only run) to gauge load balance.
cargo bench -p aika-st --features metrics --bench phold_bench produces normal Criterion reports plus two sidecar files at repo root:
criterion_engine_metrics.jsonl– JSON lines with selected engine metrics per benchmark iteration (append-only)criterion_engine_metrics_summary.json– overwritten concise summary (processed, peak_queued, busiest slots) suitable for light HTML embedding/post-processing.
Integrate into custom reporting by reading the JSONL after the benchmark run and merging with Criterion's HTML (e.g. inject via a small script that copies summary data into report/index.html).
The single-thread engine batches outbound messages produced in one agent step by (target, delivery_time). Instead of scheduling N individual Deliver events (each immediately followed by a wake Continue), it groups them into a single DeliverBatch where possible, reducing scheduling overhead and total processed events under bursty fanout. A global runtime toggle is exposed for tests: aika_st::engine::set_batching_enabled(bool) (default on). The PHOLD JSON artifacts now include mailbox_drops reporting how many incoming messages were dropped due to full fixed-size inboxes (DEFAULT_SLOTS). This helps identify pathological hotspots or insufficient mailbox capacity.
Benchmark (HTML reports under target/criterion):
cargo bench -p aika-st --bench phold_bench
Dual-licensed MIT OR Apache-2.0.