Skip to main content

unlit3dx/
lib.rs

1//! The multi-world runtime: one `unlit_ecs` world per thread, jobs in,
2//! subscriptions back.
3//!
4//! `unlit_ecs`'s world is `!Send`: components live behind `RefCell` and
5//! structural changes need `&mut World`, so a world cannot cross a thread.
6//! This crate turns that constraint into the shape of a program:
7//!
8//! - A **threaded world** runs its own world on a fresh thread, reached
9//!   through the [`handle::WorldHandle`] its spawn returns. Jobs move in through a
10//!   bounded channel and are drained between ticks.
11//! - A **sub-world** runs a nested world on the host's thread, drained
12//!   with [`host::drain_sub_world`] between frames.
13//! - A **subscription** is the way back, and it is opt-in: a world runs a
14//!   collector for every subscription it carries and publishes what each
15//!   returns, so nothing is collected until someone asks. A snapshot
16//!   subscription keeps the newest `T`; a stream subscription keeps every
17//!   one, folded into the reader's tick. Neither blocks; dropping the reader
18//!   unsubscribes. `unlit3d::relay` holds the two primitives, so a
19//!   world that never ticks — a render sub-world publishing the size it
20//!   draws with — publishes through the same handle a collector does.
21//!
22//! The runtime layer is small and complete: [`job`], [`channel`],
23//! [`thread`], [`host`], [`world`], and [`handle`] together are the whole
24//! low-level API, and a caller can build its own worlds on them. The
25//! feature-gated modules are the convenience layer: `physics`, `audio`,
26//! and `synth` wrap rapier, cpal, and a soundfont synthesizer in the same
27//! shape, while `gpu` and `present` hold the window-facing half of a
28//! multi-world app.
29//!
30//! Nothing here holds a lock across a job: a world is owned by one thread,
31//! and every cross-world exchange is a channel. Reach for the low-level
32//! modules directly when a world needs a shape the convenience layer does
33//! not offer — `unlit3d`'s `MeshSource` is the generic pipeline registry
34//! a caller can build its own render bridge on, for instance.
35#![doc = include_str!("../README.md")]
36#![forbid(unsafe_code)]
37
38#[cfg(feature = "audio")]
39pub mod audio;
40/// The snapshot subscriptions between worlds.
41pub mod channel;
42/// A single-threaded executor for a world's async tasks.
43pub mod executor;
44#[cfg(feature = "render")]
45pub mod gpu;
46/// The uniform handle to a spawned world.
47pub mod handle;
48/// Sub-worlds, which share the host's thread.
49pub mod host;
50/// Jobs, the work handed to another world.
51pub mod job;
52#[cfg(all(feature = "mcp", not(target_arch = "wasm32")))]
53pub mod mcp;
54#[cfg(feature = "physics")]
55pub mod physics;
56#[cfg(feature = "render")]
57pub mod present;
58/// Requests a world answers, and the waiting for their answers.
59pub mod request;
60// The recorder exists for the MCP audio tools and for the demo's interactive
61// run, which captures a mix on the web too, where the tools are not built. Its
62// format is public wherever it is compiled: the headless run encodes its mix
63// through it rather than carrying an encoder of its own.
64#[cfg(feature = "audio")]
65pub mod record;
66#[cfg(feature = "synth")]
67pub mod synth;
68/// World threads and their pacing.
69pub mod thread;
70#[cfg(feature = "render")]
71/// UI panels laid out in one world and drawn in another.
72pub mod ui;
73/// Threaded worlds, which run on their own thread.
74pub mod world;
75
76/// The duration of a frame at 60 Hz, in seconds.
77///
78/// A stepped world takes the duration of the frame it is stepped with, so a
79/// caller driving one at the display's rate passes the real frame time; this
80/// is the round number the tests and the runs without a window use.
81pub const FRAME_SECONDS: f32 = 1.0 / 60.0;
82
83/// The types most callers of the multi-world model need.
84pub mod prelude {
85    #[cfg(feature = "audio")]
86    pub use crate::audio::{
87        AudioChunk, AudioConfig, AudioFillOrder, AudioFormat, AudioOutputMode, AudioOutputOpen,
88        AudioOutputRequest, GlobalAudioGain, OnAudioFill, dispatch_audio_fill,
89        request_audio_output, spawn_audio_world_with,
90    };
91    #[cfg(feature = "render")]
92    pub use crate::gpu::{Gpu, request_gpu};
93    #[cfg(all(feature = "mcp", not(target_arch = "wasm32")))]
94    pub use crate::mcp::serve_mcp;
95    #[cfg(feature = "physics")]
96    pub use crate::physics::{
97        BodyDesc, BodyShape, PHYSICS_HZ, Physics, PhysicsConfig, PhysicsContacts, RigidBodyHandle,
98        despawn_body, init_physics_world, spawn_body, spawn_fixed_cuboid, spawn_physics_world_with,
99        step_once,
100    };
101    #[cfg(feature = "render")]
102    pub use crate::present::{draw_presented, pump_presented, setup_presenter};
103    #[cfg(feature = "audio")]
104    pub use crate::record::RecordingFormat;
105    #[cfg(feature = "synth")]
106    pub use crate::synth::{
107        MidiEvent, MidiEventKind, MidiSequencer, SynthVoice, VoiceError,
108        load_midi_file_with_loop_type, load_soundfont, midi_file, midi_job,
109    };
110    pub use crate::{
111        FRAME_SECONDS,
112        channel::{Snapshot, SnapshotReader, Stream, StreamReader, publish_collected},
113        executor::{LocalPool, LocalSpawnExt, LocalTasks},
114        handle::WorldHandle,
115        host::{drain_sub_world, spawn_sub_world},
116        job::{Job, JobSender, SendError},
117        request::{Pending, Settled},
118        thread::{
119            FixedClock, MAX_CATCH_UP_STEPS, ThreadControl, WorldState, WorldThread, fixed_steps,
120        },
121        world::{
122            StepError, StepOutcome, Stepped, Threaded, WorldName, WorldRunner,
123            spawn_stepped_world_with_init, spawn_threaded_world, spawn_threaded_world_with_init,
124            step_stepped_world, with_stepped_world, with_stepped_world_mut,
125        },
126    };
127    #[cfg(feature = "render")]
128    pub use unlit3d::winit::presenter::{
129        BLIT_PASS_ORDER, Presenter, PresenterError, SCENE_PASS_ORDER, resize_presented,
130    };
131}