concinnity_core/defaults/mod.rs
1//! Engine defaults: complete a loaded world with the standard components it
2//! does not declare itself.
3//!
4//! [`run`] is the [`SystemTable`](crate::ecs::SystemTable) completion pass, so
5//! it happens before the gates read the world and a HUD or overlay it injects
6//! brings its own system into the schedule. Nothing it adds is compiled: the
7//! chip font and the sky mesh are baked here out of [`crate::bake`], and the
8//! names they cross-reference each other by come from the range
9//! [`AssetId::MINTED_BASE`] reserves.
10//!
11//! A world that renders -- one declaring a
12//! [`GraphicsConfig`] -- receives the HUD,
13//! sky, and loading defaults. The physics default is gated on physics content
14//! instead, so the headless tier, which has no renderer and needs no HUD, gets
15//! the [`PhysicsConfig`](crate::components::PhysicsConfig) its simulation
16//! already runs on and nothing else.
17//!
18//! Each default yields to what the world declares: an authored HUD keeps every
19//! label it names and receives chips only for the slots it leaves unset, and a
20//! world with its own skybox geometry gets no sky mesh. An
21//! [`EngineDefaults`] turns individual
22//! defaults off entirely; the world holds at most one, and its column is
23//! drained here.
24
25mod font;
26mod hud;
27mod loading;
28mod physics;
29mod sky;
30
31use crate::components::{EngineDefaults, GraphicsConfig};
32use crate::ecs::asset_id::{AssetId, MintedIds};
33use crate::ecs::{FontHandle, PipelineContext};
34use crate::result::CnResult;
35
36pub use font::{HUD_FONT_SIZE_PX, HudFont, hud_font};
37
38/// Complete `world`'s content with the engine defaults it does not opt out of.
39///
40/// Errors when the world declares more than one `EngineDefaults` (which one
41/// applies would be arbitrary), or when baking an injected payload fails.
42pub fn run(ctx: &mut PipelineContext) -> Result<(), CnResult> {
43 let toggles = take_toggles(ctx)?;
44 let mut minter = Minter::resume(ctx);
45 let result = inject_defaults(ctx, &toggles, &mut minter);
46 minter.store(ctx);
47 result
48}
49
50fn inject_defaults(
51 ctx: &mut PipelineContext,
52 toggles: &EngineDefaults,
53 minter: &mut Minter,
54) -> Result<(), CnResult> {
55 if toggles.physics_config {
56 physics::inject(ctx);
57 }
58 // The rest exists to be drawn, and a world with no GraphicsConfig has no
59 // renderer to draw it.
60 if ctx.query::<GraphicsConfig>().next().is_none() {
61 return Ok(());
62 }
63 if toggles.sky {
64 sky::inject(ctx, minter)?;
65 }
66 if toggles.hud {
67 hud::complete_stat_hud(ctx, minter)?;
68 }
69 if toggles.debug_hud {
70 hud::inject_debug_hud(ctx, minter)?;
71 }
72 if toggles.loading_overlay {
73 loading::inject(ctx, minter)?;
74 }
75 Ok(())
76}
77
78// Drain the world's EngineDefaults column into the one set of toggles that
79// applies. The type is a build directive rather than something a system reads,
80// so it holds nothing past this pass.
81fn take_toggles(ctx: &mut PipelineContext) -> Result<EngineDefaults, CnResult> {
82 let mut declared = ctx.drain::<EngineDefaults>();
83 if declared.len() > 1 {
84 return Err(CnResult::InvalidState);
85 }
86 Ok(declared.pop().unwrap_or_default())
87}
88
89/// The source of names for what the defaults inject.
90#[derive(Default)]
91pub(crate) struct Minter {
92 ids: MintedIds,
93}
94
95impl Minter {
96 // Continue from the world's own counter, so nothing minted before this
97 // pass (a mesh handed over through `World::add_mesh`) is renamed.
98 fn resume(ctx: &PipelineContext) -> Self {
99 Self {
100 ids: ctx.resource::<MintedIds>().cloned().unwrap_or_default(),
101 }
102 }
103
104 // Hand the counter back to the world once the pass is done with it.
105 fn store(self, ctx: &mut PipelineContext) {
106 ctx.insert_resource(self.ids);
107 }
108
109 // The next name for an injected component.
110 fn id(&mut self) -> AssetId {
111 self.ids.next_id()
112 }
113
114 // The font every injected chip and label draws with. Baked into the world
115 // on first use and shared from there, so the chips of both HUDs and the
116 // loading label land on one atlas.
117 fn hud_font(&mut self, ctx: &mut PipelineContext) -> Result<FontHandle, CnResult> {
118 font::hud_font(ctx)
119 }
120}
121
122#[cfg(test)]
123mod tests;