Skip to main content

concinnity_world/registry/
build_only.rs

1//! The authoring-only group of the asset registry: the types a world declares
2//! and the cook expands into the components they stand for, gone before a blob
3//! is written.
4//!
5//! Their list lives here rather than in concinnity-core's `for_each_component!`
6//! because nothing in the runtime can hold one: they have no `ComponentTag`, no
7//! `ComponentAsset` variant, no column, and no `Component` impl. What they do
8//! have is a [`RegisteredType`](super::RegisteredType) variant, so the
9//! authoring registry composes this group with core's two.
10//!
11//! Being in the group is the origin, which is why the entries carry no origin
12//! flag; the schemas themselves are [`crate::schema`].
13
14pub use crate::schema::camera_shot::CameraShot;
15pub use crate::schema::character_model::CharacterModel;
16pub use crate::schema::character_schema::{
17    CharacterSchema, KeyPolarity, PanelSection, ProportionGroup, SchemaJoint, SchemaKey,
18    SchemaRegion, ShapePreset, SynthParams, SynthesizedTarget,
19};
20pub use crate::schema::engine_defaults::EngineDefaults;
21pub use crate::schema::light_rig::LightRig;
22pub use crate::schema::main_menu::{MainMenu, MainMenuItem, SettingsProfile};
23pub use crate::schema::material_palette::{MaterialPalette, PaletteEntry};
24pub use crate::schema::option_select::OptionSelect;
25pub use crate::schema::panel::Panel;
26pub use crate::schema::prefab::{Prefab, PrefabEntry, PrefabKind};
27pub use crate::schema::scene_import::SceneImport;
28pub use crate::schema::slider::Slider;
29pub use crate::schema::story_import::StoryImport;
30
31/// An asset the cook consumes and never hands to the runtime.
32///
33/// A world declares one of these, cook expands it into the components it stands
34/// for, and nothing of it reaches a blob. Exactly the list below: no tag, no
35/// `ComponentAsset` variant, no column, no `Component` impl. Carries no methods
36/// -- it exists so the registry's groups are checkable at compile time and so
37/// the list is discoverable in the docs. The stored group carries
38/// `concinnity_core::ecs::RuntimeComponent` instead, and the resource group
39/// `concinnity_core::ecs::ResourceAsset`.
40pub trait BuildOnlyAsset {}
41
42/// The authoring-only list. `$cb` receives it as a `build_only:` group shaped
43/// exactly like concinnity-core's groups, so one callback serves both.
44///
45/// The `$cb, $prefix` form prepends tokens to what `$cb` receives, which is how
46/// `for_each_authored_type!` hands a callback core's groups and this one
47/// together.
48#[macro_export]
49macro_rules! for_each_build_only_type {
50    ($cb:ident) => { $crate::for_each_build_only_type!($cb,); };
51    ($cb:ident, $($prefix:tt)*) => {
52        $cb! {
53            $($prefix)*
54            build_only: {
55                LightRig          => $crate::registry::build_only::LightRig { },
56                MaterialPalette   => $crate::registry::build_only::MaterialPalette { },
57                CameraShot        => $crate::registry::build_only::CameraShot { },
58                Prefab            => $crate::registry::build_only::Prefab { },
59                SceneImport       => $crate::registry::build_only::SceneImport { },
60                MainMenu          => $crate::registry::build_only::MainMenu { renders },
61                OptionSelect      => $crate::registry::build_only::OptionSelect { },
62                Slider            => $crate::registry::build_only::Slider { },
63                EngineDefaults    => $crate::registry::build_only::EngineDefaults { },
64                StoryImport       => $crate::registry::build_only::StoryImport { },
65                Panel             => $crate::registry::build_only::Panel { },
66                CharacterSchema   => $crate::registry::build_only::CharacterSchema { },
67                CharacterModel    => $crate::registry::build_only::CharacterModel { },
68            },
69        }
70    };
71}
72
73// The marker impls, generated from the list so the group and the trait cannot
74// disagree.
75macro_rules! __impl_build_only {
76    (build_only: { $( $variant:ident => $ty:path { $($meta:tt)* } ),+ $(,)? } $(,)?) => {
77        $( impl BuildOnlyAsset for $ty {} )+
78    };
79}
80
81crate::for_each_build_only_type!(__impl_build_only);
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86    use crate::registry::{AssetOrigin, RegisteredType, ScopeResolution};
87
88    // The composition is what keeps this group in the one authoring registry:
89    // the list lives in this crate while the other two come from
90    // concinnity-core, and a callback that saw only core's would silently drop
91    // every name below. Derived from the list, so an entry added or removed
92    // updates the check with it.
93    macro_rules! assert_registered_as_build_only {
94        (build_only: { $( $variant:ident => $ty:path { $($meta:tt)* } ),+ $(,)? } $(,)?) => {
95            #[test]
96            fn every_build_only_type_reaches_the_authoring_registry() {
97                let mut count = 0;
98                $(
99                    let name = stringify!($variant);
100                    let ty = RegisteredType::parse(name)
101                        .unwrap_or_else(|| panic!("{name} is not a registered type"));
102                    assert_eq!(ty, RegisteredType::$variant);
103                    assert!(
104                        RegisteredType::all().contains(&ty),
105                        "{name} is missing from all()"
106                    );
107                    // A world declares it and the cook expands it, so no
108                    // record of it is written: no tag, no column. `addable` is
109                    // the External-origin predicate behind the authoring
110                    // tools' add list, which offers these through their own
111                    // flow instead, so it stays false here.
112                    assert_eq!(ty.registration().origin, AssetOrigin::BuildOnly);
113                    assert!(!ty.addable(), "{name} is not externally addable");
114                    assert_eq!(ty.discriminant(), None, "{name} carries a blob tag");
115                    assert_eq!(ty.scope_resolution(), ScopeResolution::Expanded);
116                    assert!(!ty.is_resource(), "{name} is not a resource");
117                    count += 1;
118                )+
119                // The group is exactly this list: nothing else in the registry
120                // reports the build-only origin.
121                let registered = RegisteredType::all()
122                    .iter()
123                    .filter(|t| t.registration().origin == AssetOrigin::BuildOnly)
124                    .count();
125                assert_eq!(registered, count);
126            }
127        };
128    }
129
130    crate::for_each_build_only_type!(assert_registered_as_build_only);
131
132    // The marker is the compile-time half of the same fact.
133    fn expanded_by_the_cook<T: BuildOnlyAsset>() {}
134
135    #[test]
136    fn the_group_carries_its_marker() {
137        expanded_by_the_cook::<Prefab>();
138        expanded_by_the_cook::<MainMenu>();
139        expanded_by_the_cook::<CharacterSchema>();
140    }
141}