Skip to content
hikari
RenderingEditorAIToolchainDocumentation
Download

© 2026 Flying Rat Studio.
All rights reserved.

Explore the engineDocumentationContributorsDownloadLicenseBack to top
Documentation / Systems
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
Systems6 min read

Editor asset hot reload

On this page
On this pageDaemon-to-editor event queueEditor tick processingRuntime hot reloadRender-thread synchronizationScene reload behaviorFirst cook is not a changeInvariants for maintainers Back to top

The editor implements live visual asset swapping through a thread-safe, daemon-to-edit event queue and a render-thread-paused re-instantiation path. This page describes the current flow and the invariants that keep it safe.

The queue separates background cooking from world mutation. GPU-visible changes happen only inside the frame-boundary pause:

Diagram
Diagram source
sequenceDiagram
    participant D as Shinra
    participant E as Editor / session
    participant R as Renderer
    D->>E: Queue asset events
    Note over E: Drain in Edit only<br/>Play keeps events queued
    E->>R: Pause at frame boundary
    R-->>E: Paused
    Note over E: Release holds → evict<br/>Rebind → flush GPU releases
    Note over E: Queue scene replacement<br/>only if reconstruction is needed
    E->>R: Resume

Daemon-to-editor event queue

  • editor/asset_pipeline/daemon.zig runs Shinra in watch mode and emits Event values: pending, available, rebuilt, failed, removed, catalog, disconnect.
  • editor/editor_app/app.zig owns the asset_pipeline_daemon and receives events through onAssetPipelineEvent, which pushes them into EditorApp.asset_events.
  • editor/editor_app/asset_events.zig defines the Queue: path-coalesced, capacity-bounded (max_paths = 8192), and generation-aware. It keeps the last terminal state per path and ignores stale generations. catalog and disconnect are flags, not per-path events.
  • Pipeline callbacks wake the durable main-thread bus so backgrounded editors can drain events. During Play, reload work is deferred: the durable pump parks the non-empty queue without re-waking, and the editor-state coordinator releases it when the mode returns to Edit.

Editor tick processing

  • editor/editor_app/hot_reload.zig:processPending runs each edit tick when editor_state.allows(.asset_reload).
  • It drains asset_events into asset_event_drain using Queue.drainBoundedInto, up to 48 distinct paths per tick without splitting a stem.
  • If events were dropped, it requests a rebuild-all from the daemon once per overflow burst (debounced until the daemon has gone busy→idle, or a catalog/disconnect arrives). Stacking rebuild-all while the queue is still overflowing re-floods Shinra.
  • asset_reload_refs.collect counts affected actors for the entire batch in one document walk. It indexes event stems, matches script, geometry, surface-material and fog-material references, and resolves shader dependencies once per distinct surface material. Primary and per-slot materials share the same memo. An actor counts once per event even when several fields reference it.
  • For each per-path event it:
    • classifies the path as texture / material / shader / mesh / script;
    • decides whether a full scene reload is needed (requiresSceneReload is false only for texture / material / shader);
    • invalidates the editor thumbnail cache for that path;
    • tracks .shinparticle rebuilds for preview rebind;
    • builds a Session.AssetChange list.
  • Scene rebuild requests coalesce until the cook and any existing scene load settle. They remain deferred during Play and boot module compilation. The editor rechecks consumers before serializing the authored SceneDocument; a later scene load can supersede a deferred request.
  • It calls session.hotReloadAssets(applied, scene).

Runtime hot reload

  • The editor drains reload events in Edit. runtime_session/scene_ops/hot_reload.zig:hotReloadAssets updates owned-world holders and live-world render queues.
  • It pauses the render thread with session.render_thread.pauseAtFrameBoundary() and resumes it in a defer with resumeFromFrameBoundary().
  • For each change:
    • it deduplicates by asset_deps.ReloadFamily + stem (texture / model / audio / font / blob);
    • stops audio streamers before eviction for .audio families;
    • calls forceEvictFamily(world, store, stem, family), which abandons world holders (skybox, visual-zone LUTs, bound meshes, animation skeleton/clip binds, material texture slots; marks animation-graph actors stale) then calls store.evictFamilyForReload(stem, family). Abandoning releases each hold rather than nulling it: eviction defers to the last borrower, so a hold dropped without its release strands the entry reload_pending and the reload never arrives;
    • if the store returns a GPU texture handle, enqueues RenderCommand.release_texture on world.render_command_queue;
    • updates the AssetStore state to pending, failed, or ready;
    • refreshes texture bindings in world.materials for texture assets;
    • reloads materials/shaders via world.materials.reloadForAssetChange and patches primitive references;
    • invalidates compiled Kawa scripts for .kawa/.kawabc changes;
    • re-arms looping audio beds for .audio changes.
  • After all changes it drains pending texture frees, flushes the render command queue, and (if a scene snapshot was provided) queues requestSceneReplaceFromUtf8(session, session.scene_path, scene, .preserve).

Render-thread synchronization

  • graphics/renderer/render_thread.zig implements pauseAtFrameBoundary/resumeFromFrameBoundary using pause_requested and paused flags protected by the existing mutex/cond. Pauses nest: pause_depth counts host-side holders and only the outermost resume lets the thread run.
  • graphics/renderer/render_command_queue.zig has a release_texture: RenderHandle command variant.
  • graphics/renderer/renderer_shared/commands.zig:applyRenderCommands drains release_texture via resources.deferRemoveTexture(handle), which ages the GPU resource before freeing.

Scene reload behavior

  • Full scene re-instantiation runs only when requiresSceneReload is true and the batch’s affected-actor count is positive (authored script / model / mesh / material, or a material that references a changed shader). The SceneDocument is serialized to UTF-8 and queued through runtime_session/scene_ops/load.zig:requestSceneReplaceFromUtf8 with EditViewSeed.preserve.
  • requestSceneReplaceFromUtf8 queues a budgeted replace SceneLoadJob; the outgoing scene remains visible until the replacement commits. World.unloadScene / unloadSceneForSwap do not clear render_command_queue, so release_primitive commands emitted during entity teardown are preserved and drained before the new scene renders.
  • Texture, material, and shader changes rebind in place. Prefab / particle / animgraph / audio / font evict and rebind holders but do not contribute authored geometry/script/material references, so they do not by themselves serialize the document.

First cook is not a change

Content hashes are tracked by full logical path, so sibling artifacts cannot inherit one another’s change state. First arrivals and byte-identical rebuilds do not request a scene replacement. Changed bytes request one only when live consumers still hold the old content. Stub worlds and worlds still loading use the existing load and residency wake.

Without a content hash, firstCookBindsInPlace uses the consumer’s pending intent and store state:

CaseReload?Why
mesh/script, .pending → .rebuiltnopending records bind the arrival in place
mesh with pending or matching bound partsnoconsumer state overrides shared-stem store state
mesh/script, previously .ready or .failedyesno pending record will rebind changed content
prefabyesexpansion happens at spawn without retained pending intent

Authoring JSON mirrors (*.material.json, *.model.json, …) publish as available, not rebuilt: the cooked binary is the runtime change, and treating the JSON copy as a change would evict and re-parse the same material twice per cook.

Fog-volume materials parse through loadDesc, which pins the blob. Five lanterns sharing lantern_glow decode once. They cannot MaterialCache.retain — that path rejects non-surface domains and would install a pink stand-in.

Engine shader acquire pins the blob after the first decode, so PSOs sharing one metallib do not re-read it on every acquire/release.

Cook progress logs one line per source (relative path, duration). Extracted companions with no actor field still hot-reload; they log at debug, with an info summary when a batch has eight or more.

Invariants for maintainers

  • Route asset change notifications through the path-coalesced queue; do not call hotReloadAssets directly from daemon threads.
  • A durable pump may re-wake only for work that is runnable now. Play-deferred asset events stay queued until the Edit transition publishes work_ready; treating a merely non-empty queue as runnable can starve the native message loop.
  • Pause the render thread at a frame boundary before evicting or reloading assets; resume only after flushing the render command queue.
  • Preserve the render command queue across scene unload/load so GPU release commands are not lost.
  • Force-evict by ReloadFamily only; a same stem with a different kind (for example, texture vs model) must not be wiped by a single event.
  • Abandon world holders before AssetStore eviction to avoid use-after-free on same-stem rebind.
PreviousUI and editorNext Editor Project Selector

Documentation follows the current engine checkout.

Snapshot 86f12d68Source docs/systems/editor-asset-hot-reload.md
On this pageDaemon-to-editor event queueEditor tick processingRuntime hot reloadRender-thread synchronizationScene reload behaviorFirst cook is not a changeInvariants for maintainers Back to top