Skip to content

[M1-SYS-02] System scheduler - #25

Merged
offdev merged 2 commits into
masterfrom
m1-sys-02-system-scheduler
Sep 14, 2026
Merged

offdev merged 2 commits into
masterfrom
m1-sys-02-system-scheduler

Conversation

@offdev

@offdev offdev commented Sep 14, 2026

Copy link
Copy Markdown
Owner

Roadmap step M1-SYS-02 (system scheduler, roadmap/M1-heartbeat.md), scope only.

What

The scheduler turns the M1-SYS-01 registry (registration order + declared depends_on + declared component I/O) into the per-tick execution order, validates it once, and runs the systems in that order:

  • src/laige-sim/include/laige/sim/system.h:
    • SystemSchedule — the computed execution order (the systemCount plus the dense SystemId order array);
    • SystemDef gains dependsOn — the raw depends_on spec (comma-separated registration names; nullptr/"" = no dependencies);
    • LAIGE_SYSTEM(Name, budget_ms, Dep..., ...) is now variadic — the optional trailing depends_on names are stringized verbatim into the spec (LAIGE_SYSTEM(Health, 1, Spawner) = spec "Spawner");
    • kMaxSystemDependencies = 16 — the direct-dep bound (CORE-005; a barrier is registration position, not a dependency list);
    • detail::DepSpecParse/DepSpecError/parseDepSpec/depSpecErrorName — the spec parse (no copy, no allocation; the tokens point into the spec literal).
  • World::scheduleSystems(SystemSchedule&) const (setup phase; pure registry read) — the stable topological sort: Kahn's algorithm with a min-id tie-break; a system only moves LATER, behind its dependencies (no dependencies = exactly the registration order). Pre-run validation (first failure wins; every failure one rate-limited structured warn + Status — FR-12.3): unknown dependency name (system/dep_missing), dependency cycle (system/dependency_cycle, one concrete cycle reported: the deterministic walk from the smallest remaining id), two systems writing the same component in one tick (system/double_writer, order-independent); a declared read ordered before a declared write of the same component is warn-only (system/read_before_write) — the schedule succeeds and the reader sees the previous tick's value.
  • World::runSystems(const SystemSchedule&) — one sim tick's system phase: the systems run strictly one at a time, in schedule order, on the world's single owner thread (PRD §10.2); a fresh non-owning SystemContext per system; a stale schedule (system/schedule_stale) and a hand-built malformed schedule (system/schedule_invalid) are rejected.
  • The scheduler lands in systems.cpp (no new source file; the sim CMake comment updated).

Validation (normative order — first failure wins)

failure (phase) result event
malformed depends_on spec (registration: empty token/trailing comma, duplicate name, > kMaxSystemDependencies) InvalidArgument + warn system/dep_spec_invalid
dependency name not registered in this world (scheduling) InvalidArgument + warn system/dep_missing
dependency cycle (scheduling) InvalidArgument + warn system/dependency_cycle
two systems both writing the same component in one tick (scheduling) InvalidArgument + warn system/double_writer
declared read ordered before a declared write of the same component (scheduling) warn only — the schedule succeeds system/read_before_write
schedule.systemCount ≠ current systemCount (running) InvalidArgument + warn system/schedule_stale
order entry 0 / above the count / a duplicate id (running) InvalidArgument + warn system/schedule_invalid

Every warn is rate-limited (LOG-004), subsystem system, with stable identifying fields (system/missing_dep/position, cycle, component_id/first_writer/second_writer, reader/writer/component_id, scheduled_systems/current_systems, slot/id, name/error). The success paths log nothing (LOG-003).

Tests

New SystemScheduler suite — 26 tests, CTest entry scheduler (also in the TSan property list): the order properties (no-deps = registration order, forward/backward deps, the chain, the diamond with a reversed spec list — the dependency set is orderless, the macro spec stringization, the known-answer pin scheduler-order systems=5 fnv1a=0xaef3282f393ab332), running in scheduled order with state flow (writer→reader sees the fresh value; reader→writer observes the stale value), every validation event with sink field checks (incl. the rate_limited summary on shutdown), the spec validation (whitespace trimming legal, the 17-dep bound), the empty + moved-from world, the stale and the malformed hand-built schedules, the order bit-identical across two worlds (ARCH-010), and the zero-alloc window (100 ticks × 3 systems over 4 entities: scheduling + every runSystems allocates nothing — scheduler-zeroalloc ticks=100 allocs=0; test-only operator-new counter, non-sanitizer trees; the sanitizer trees prove it leak-free).

Verify (local)

  • ctest -R scheduler green (26/26, incl. the zero-alloc window); full suite 42/42 on build.
  • Full suite 42/42 on build-asan (leak-free), build-tsan, build-clang, build-release, build-shared; zero new warnings under NFR-8.10.
  • laige-api.json regenerated (496 symbols, +7: kMaxSystemDependencies, SystemDef::dependsOn, SystemSchedule ×3, World::scheduleSystems/runSystems); api-real-tree green.
  • tools/laige-include-lint OK (27 source files, 1/10 vendored deps).

Docs (CORE-006)

  • docs/api/scheduler.md (full contract + Performance section), linked from docs/README.md (the API list + the per-module laige-sim list, which gains the previously missing system_registry.md entry);
  • docs/api/system_registry.md updated (the macro is variadic now, the validation table gains the dep_spec_invalid row, cross-refs to scheduler.md);
  • src/laige-sim/README.md status updated; system.h/entity.h preambles + member docs carry the M1-SYS-02 note.

Notes

  • depends_on names registration names, not function addresses or SystemIds (per-world runtime values): forward dependencies (registered later) are legal — the spec's shape is validated at registration, its names resolved at scheduling time.
  • The order is a pure function of (registration order, dependency edges): pure integer/string bookkeeping — no floating point, no randomness, no addresses in the order or the warning set (ARCH-010).
  • Calling runSystems from inside a system is documented misuse (it breaks the declared order contract; the one-writer-per-component invariant still prevents state corruption).

The system scheduler (M1-SYS-02 scope, nothing else): turns the
M1-SYS-01 registry (registration order + declared depends_on +
declared component I/O) into the per-tick execution order and runs
the systems in it.

- SystemSchedule: the computed execution order (systemCount + dense
  SystemId order array).
- World::scheduleSystems: the stable topological sort of the
  registration order plus the depends_on edges (Kahn, min-id
  tie-break; a system only moves later, behind its dependencies).
  Pre-run validation (first failure wins; every failure one
  rate-limited structured warn + Status, FR-12.3): unknown dependency
  name (system/dep_missing), dependency cycle (system/dependency_cycle,
  one concrete cycle reported), two systems writing the same component
  (system/double_writer); a declared read ordered before a declared
  write WARNs without failing (system/read_before_write).
- World::runSystems: one sim tick's system phase — strictly one system
  at a time, in schedule order, on the owner thread; fresh
  non-owning SystemContext per system; stale (system/schedule_stale)
  and malformed (system/schedule_invalid) schedules rejected.
- SystemDef gains dependsOn (the raw comma-separated registration-name
  spec); LAIGE_SYSTEM becomes variadic over the optional trailing
  depends_on names (stringified verbatim); kMaxSystemDependencies = 16.
- detail::DepSpecParse/DepSpecError/parseDepSpec/depSpecErrorName
  (system.h; defined in systems.cpp): registration validates the spec
  (system/dep_spec_invalid); scheduling resolves the names.
- Determinism (ARCH-010): pure integer/string bookkeeping — no
  floating point, no randomness, no addresses in the order or the
  warning set. No allocation at scheduling or per tick (PERF-003);
  no logging on the success paths (LOG-003).

Docs: docs/api/scheduler.md (full contract + Performance) linked from
docs/README.md; docs/api/system_registry.md updated (macro shape,
dep_spec_invalid row, cross-refs); src/laige-sim/README.md status;
system.h/entity.h preambles carry the M1-SYS-02 note.
Tests: new SystemScheduler suite (26 tests) + CTest entry
'scheduler' (TSan property list). laige-api.json regenerated
(496 symbols; api-real-tree green).
@offdev
offdev merged commit c786747 into master Sep 14, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant