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}