Skip to content
hikari
RenderingEditorAIToolchainDocumentation
Download

© 2026 Flying Rat Studio.
All rights reserved.

Explore the engineDocumentationContributorsDownloadLicenseBack to top
Documentation / Guides
Browse docs
Overview
Tutorials17
OverviewPlay, Edit, and scenesFirst UI documentFirst game projectFirst entityComponent properties and the inspectorFirst mesh and materialFirst physics body and triggerFirst character controllerFirst session servicesFirst UIFirst input actionFirst messagesFirst runtime spawnFirst motionFirst skeletal animationAssets in Play (soft refs and hot reload)Dynamic editor recompile
Guides25
OverviewWorking in the editorDevelopment guideGameplay APIActor and component lifecycleThreads, jobs, events, and routinesGameplay AI tasksGame AI: perception and movement playgroundBrain assetsBrain execution for swarm populationsLocalizationGame cursorsChoosing component storageBuild and packagingProject filePluginsHikari Plugin APIData-driven content, JSON, and pathsScripting with KawaShader authoringChoosing and scaling shadowsTime of dayUser interfaceUI layoutUI widgetsMigration from Unity / Unreal
Systems32
OverviewArchitectureApplication lifecycleFrontends and driversPlatforms and supportSession services and cross-scene stateScenes and gameplayActor communication (hi.actors)Game-facing refsSave / replication wire versionCoordinate space and camera conventionsRenderingRenderer architecture mapFrame governorPipeline compilationSkeletal animationGPU particlesVisual ZonesVolumetric mediaInputAudioPhysicsMotion KitTemporal KitUI documentsAssets and ShinraPrefabsAsset residencyAsset formats (Shinra pipeline)Document schemasUI and editorEditor asset hot reloadEditor Project Selector
Language reference3
OverviewAkari GPU ABIAkari language referenceKawa language reference
Engine overview
Start exploring
  • No matching sections. Try fewer words or another topic.
NavigateEnter Openesc Close
Overview
Tutorials17
OverviewPlay, Edit, and scenesFirst UI documentFirst game projectFirst entityComponent properties and the inspectorFirst mesh and materialFirst physics body and triggerFirst character controllerFirst session servicesFirst UIFirst input actionFirst messagesFirst runtime spawnFirst motionFirst skeletal animationAssets in Play (soft refs and hot reload)Dynamic editor recompile
Guides25
OverviewWorking in the editorDevelopment guideGameplay APIActor and component lifecycleThreads, jobs, events, and routinesGameplay AI tasksGame AI: perception and movement playgroundBrain assetsBrain execution for swarm populationsLocalizationGame cursorsChoosing component storageBuild and packagingProject filePluginsHikari Plugin APIData-driven content, JSON, and pathsScripting with KawaShader authoringChoosing and scaling shadowsTime of dayUser interfaceUI layoutUI widgetsMigration from Unity / Unreal
Systems32
OverviewArchitectureApplication lifecycleFrontends and driversPlatforms and supportSession services and cross-scene stateScenes and gameplayActor communication (hi.actors)Game-facing refsSave / replication wire versionCoordinate space and camera conventionsRenderingRenderer architecture mapFrame governorPipeline compilationSkeletal animationGPU particlesVisual ZonesVolumetric mediaInputAudioPhysicsMotion KitTemporal KitUI documentsAssets and ShinraPrefabsAsset residencyAsset formats (Shinra pipeline)Document schemasUI and editorEditor asset hot reloadEditor Project Selector
Language reference3
OverviewAkari GPU ABIAkari language referenceKawa language reference
Engine overview
Guides4 min read

Threads, jobs, events, and routines

On this page
On this pageStart, wait, cancelTiming and lifetimeCapacity and scaling Back to top
ToolUse it forExecution
hi.std.ThreadExplicit OS threads and long-lived native servicesActual parallelism; you own synchronization and shutdown
hi.jobsBounded CPU work on game-owned dataExisting worker pool; no World/render/UI access from jobs; join within the owning frame
Actor events/messagesNotify another actor or componentWorld dispatcher delivery; messaging itself is not concurrency
hi.RoutineStraight-line gameplay spread over simulation ticksCooperative fibers on the World thread; no worker thread is occupied while waiting

Use Flow/Timer for tiny caller-driven state and delays. Use routines when keeping ordinary locals, loops, and nested calls makes gameplay code easier to write. Events can update state that a routine observes; they do not create threads or fibers. Thread/job APIs retain their existing contracts.

Start, wait, cancel

zig
const hi = @import("hikari_game");

fn pulse(routine: *hi.Routine) !void {
    const actor = routine.actor(); // Weak handle; safe to keep across waits.
    const original = hi.world().scale(actor);
    defer hi.world().setScale(actor, original);

    hi.world().setScale(actor, .{ 2, 2, 2 });
    try routine.wait(0.5);
    hi.world().setScale(actor, .{ 0.5, 0.5, 0.5 });
    try routine.wait(0.5);
}

// Inside component Data:
state: struct { pulse: hi.RoutineHandle = .invalid } = .{},

pub fn onEnable(self: *@This(), actor: hi.ActorContext) void {
    self.state.pulse = actor.startRoutine(pulse) catch |err| {
        hi.std.log.warn("pulse could not start: {s}", .{@errorName(err)});
        return;
    };
}

try routine.nextTick() suspends until a later simulation tick. Conditions use ordinary loops: while (!ready(routine.actor())) try routine.nextTick();. Nested helpers can take *hi.Routine and wait too. Coordinate exclusive ownership when multiple routines write the same property; the pulse example assumes that it owns scale until its cleanup runs.

hi.world().cancelRoutine(handle) requests cancellation; routineStatus(handle) reports queued/running/sleeping/cancelling/completed/cancelled/failed/expired. Handles are weak: a completed status remains readable until its pool slot is reused, then the old handle expires. No retained result allocation or release is required. hi.world().startRoutine(.invalid, function) explicitly starts work without an actor owner; full scene unload still cancels it.

Timing and lifetime

  • Start queues work; it never calls the function inline. Execution is after the World's .update group and before animation/.post_update.
  • wait(seconds) arms a deadline at suspension. wait(0) means next tick; negative/non-finite values return error.InvalidWait. Sim pause freezes both time and execution. Routine cadence is independent of component tick LOD.
  • Cancellation wakes a sleeping routine; its wait returns error.Cancelled. Propagate that error with try so defer runs. Do not catch and retry it. An unstarted routine can be cancelled without entering its function.
  • Actor disable/destruction cancels actor-owned routines. Starts are actor-owned, even from a component: cancel the stored handle in onDestroy if removal of that component should also stop its routine. Re-enable does not restart work automatically; the example authors that behavior in onEnable.
  • Full scene unload, World teardown, and module reload drain cleanup before releasing the affected state/code. Play Stop drains in bounded retirement passes. An unload cannot safely discard a suspended stack containing defer.
  • Never retain ActorContext, sibling pointers, tick-arena memory, or held locks across a wait. Keep copied values, owned resources, and weak actor handles. Cancelled cleanup must tolerate a missing actor. Do not switch threads, block on I/O, or wait for a job while holding a gameplay borrow.

Routines are cooperative: a long loop without a wait still stalls the World thread, and swallowing cancellation can prevent teardown. Fixed-update waits, unscaled waits, child joins, and direct event/operation subscriptions are not part of this initial API. Running stacks are transient, not scene/save data.

Capacity and scaling

Configure on the World owner thread before starting work (for example in onWorldAttach, with that World bound):

zig
try hi.world().configureRoutines(.{
    .capacity = 10_000,
    .stack_bytes = 64 * 1024,
    .resumes_per_tick = 1024,
});

The default pool is created lazily on first start: 256 slots, 64 KiB stack budget per slot, 1,024 resumptions per tick. Explicit configuration avoids that setup cost in a gameplay callback. Pools cannot be reconfigured while active. Starts beyond capacity return error.RoutineCapacity; they never silently grow the pool.

After reservation, start/wait/cancel use pooled slots and intrusive ready queues without heap allocation. An indexed deadline heap parks sleepers and removes cancelled timers without leaving tombstones. Ready work rotates under the budget; 100,000 continuously ready routines with a 1,024-resume budget need roughly 98 simulation ticks to service everyone once. This is not 100,000 updates per frame.

routineStats() exposes active/sleeping/ready counts, last-pass resumptions, capacity, and configured stack bytes. Stack bytes exclude guard pages, metadata, and platform rounding; they are not resident RAM. Each suspended fiber retains its stack. macOS arm64 uses guarded virtual mappings; Windows x86-64 uses native Win32 fibers. Linux routines currently report unavailable.

Validation from src/hikari: zig build test-routines -Dgame-src=<abs>, zig build check-routines-windows -Dgame-src=<abs>, and zig build routines-bench -Dgame-src=<abs> -Doptimize=fast. See the benchmark results for measured limits.

PreviousActor and component lifecycleNext Gameplay AI tasks

Documentation follows the current engine checkout.

Snapshot 86f12d68Source docs/guides/concurrency.md
On this pageStart, wait, cancelTiming and lifetimeCapacity and scaling Back to top