Skip to content

Engine Architecture

jEngine is a Cargo workspace (edition 2024):

CratePurpose
jengine-coreThe engine library: wgpu renderer, ECS, scripting bridge, physics, audio, animation, UI, input, assets
jengine-runtimeNative runtime (Windows desktop, Android): winit + wgpu loop, Luau scripts
jengine-webWASM runtime: wasm-bindgen + WebGPU (WebGL2 fallback), requestAnimationFrame loop, Lua through Fengari
jengine-editor-viewportWASM module that runs the game inside the editor
jengine-builderBuild pipeline and the jengine CLI: Windows, Web and Android builds, project runtime crates, package scaffolding
jengine-relayThe relay server online games connect to
jengine-benchmark, jengine-benchmark-webPerformance harnesses

editor/ is the Tauri 2 + Svelte editor, and packages/ holds the engine’s packages.

CategoryTechnologyPurpose
Renderingwgpu 23One WGSL pipeline over DX12 / Vulkan / Metal / WebGPU / WebGL2 / GLES
Windowingwinit 0.30 (vendored, with an Android controller hook)Native window + surface
ECShecs 0.10Minimal entity-component-system
Scriptingmlua 0.10 (Luau), Fengari, rquickjs 0.8Luau natively; Lua 5.3 in the editor and web; JavaScript on desktop
Mathglam 0.29Vectors and matrices
Physicsrapier2d 0.222D rigid body physics
Audiokira 0.12 natively, Web Audio in the browserWAV / OGG / MP3 playback, buses, effects
Vector shapeslyon 1.0Path tessellation
Serializationserde + serde_jsonScenes, prefabs, project files
EditorTauri 2 + SvelteVisual game editor
jengine-core/src/
├── renderer/ # wgpu renderer: sprites, text, lighting, post-processing,
│ # shader graphs (WGSL codegen), materials, masks, lines, meshes
├── ecs/ # components, systems, the shared motion step, hierarchy, views
├── scripting/ # the script bridge, Luau adapter, JS adapters, namespaces,
│ # host commands shared by the browser hosts, batch scripts
├── behavior/ # behavior graphs: op catalog, compiler, interpreter
├── scene/ # JSON scene format (v1 and v2), prefabs, scene stack
├── package/ # JEnginePackage, component registry, system schedule,
│ # PackageHost, package script commands, editor registry
├── physics/ # rapier2d wrapper, shape queries, character controller
├── audio/ # backends, mixer buses, spatial audio, zones
├── animation/ # sprite sheets, tweens, skeletons, IK, jiggle
├── input/ # keyboard, mouse, gamepads, touch, touch controls, haptics
├── particles/ # CPU emitters and GPU particles
├── tilemap/ # tile maps, autotiling, pathfinding
├── navigation.rs # baked navigation graphs and agents
├── ui/ # entity UI, .gui documents, scaling, navigation
├── locale.rs # string tables and plural rules
├── data/ # data tables (.data / .dataschema)
├── save/ # save system
├── asset/ # asset manager and loaders
└── profiling/ # frame stats and debug overlay

Packages extend the engine through the JEnginePackage trait: components with serialization, systems in execution phases, script commands every runtime can call, and editor metadata. Each runtime owns a PackageHost and runs the phases at matching points of its frame.

PhaseRuns
PreUpdatebefore scripts
ScriptExecafter the script system
Physicsafter the runtime’s physics step
PostPhysicsafter behaviour / trigger flush
Animationafter skeleton, IK and hierarchy resolve
PreRenderjust before the draw
Render, PostRendernot run: the renderer is owned by the runtime

Packages are linked at compile time: a project’s builds are generated crates that depend on the packages it lists. See the Package System guide and All packages.

  • One pass in z order. Sprites, text, meshes, lines, trails, vector paths and skinned meshes are sorted together by z_order (with y_sort and sorting groups) and batched per texture, material and blend mode. Tilemaps draw first, except y_sort layers.
  • Materials: a material names a built-in (default_sprite, additive, multiply, screen, unlit, lit_sprite) or a .material built from a shader graph, compiled to WGSL; per-instance parameters ride in the batch’s uniforms.
  • Lighting is deferred into a float light buffer: point, spot, directional (a sun) and freeform lights, occluder shadows from a shared pool of edges, normal maps and specular, ambient occlusion, toon bands, cookies and lightmaps. renderer::lighting::light_at answers the same lighting on the CPU, so gameplay can read light the player sees.
  • The world is drawn into a float target and through a post chain (bloom, tonemapping, blur, light shafts, pixelate, CRT, …), then the UI is drawn on top in the display format.
  • Colours are linear; the swapchain encodes to sRGB.

Every host — native, editor viewport, web — runs the same shared steps, roughly in this order. The hosts differ in details (the native runner runs package systems after scripts, the editor viewport before them), which is why a package stamps time-critical input with a wall clock.

1. Input: events → InputState (touch controls ticked, focus navigation)
2. Package PreUpdate
3. Scripts: update(entity, dt) against a snapshot; commands queued
4. Apply script commands (deferred mutations)
5. Package ScriptExec
6. Physics at a fixed step (60 Hz by default), bodies blended between steps
7. Motion step (velocity, patrols, path followers, nav agents, ...), behavior graphs
8. Animation, skeletons, hierarchy resolve, trails, cloth
9. Package PreRender, camera update
10. Render the world (views, lights, post chain), then the UI

Entities live in a hecs::World. Scripts interact through deferred commands:

Scripts read → snapshot (start of frame)
Scripts write → command queue (deferred)
Frame → apply commands → world mutated → next frame's snapshot

All scripts see the same world state regardless of execution order.

A script function exists separately in each of four execution paths — native Luau, the editor’s Fengari, the web runtime, and the JavaScript preamble — so the repository guards their agreement with tests (script_global_parity, host_command_parity, and others) and keeps a register of every known difference in docs/parity-gaps.md.