Skip to main content

concinnity_core/components/
mod.rs

1//! Every component type the runtime can store: the ones an authored world
2//! declares and the ones only the runtime mints, plus the resources the cook
3//! compiles into the blob's resource stream.
4//!
5//! An authored component's data schema lives in concinnity-asset, so those
6//! arrive here as re-exports; the modules defined here are the runtime-only
7//! components plus the authored ones that need more than plain data -- a
8//! runtime struct distinct from its authored args, an extension trait, a
9//! build-time `SourceBacked` binding, or a helper the generated `Component`
10//! impl can't express. Most components are pure data whose impl is generated
11//! from the registry (see `cn_impl_components!` in `ecs::registry`).
12//!
13//! The authoring-only vocabulary -- the types a world declares and the cook
14//! expands away, and the authored args schemas that diverge from the component
15//! they bake into -- is not named here. It lives in
16//! [`concinnity_asset::cook`], and the registry half of it in
17//! `concinnity_world::registry::build_only`.
18//!
19//! Systems are not components: every system is internal code (see
20//! `World::start`), driven by the presence of the components defined here. The
21//! client re-exports this module under the historical `crate::components::*`
22//! paths.
23
24// Component data types.
25mod animation;
26mod animation_graph;
27mod animation_params;
28mod app_config;
29mod audio_command;
30mod audio_occlusion_probe;
31mod camera3d;
32mod camera_probe;
33mod character_rig;
34mod contact_event;
35mod controls_command;
36mod despawn_request;
37mod entity_target;
38mod file;
39mod frame_input;
40mod gamepad_button;
41mod gamepad_map;
42mod geometry;
43mod ground_probes;
44mod input_key;
45mod interact_event;
46mod lifetime;
47mod nav_direction;
48mod play_cue;
49mod post_process_config;
50pub mod procedural_mesh;
51mod reparent_request;
52mod room;
53mod root_motion_event;
54mod scene_command;
55mod screen_command;
56mod screen_shown;
57pub mod sdf_volume;
58mod setting_command;
59pub mod shader;
60mod skeleton_pose;
61mod skinned_mesh;
62mod spawn_request;
63mod spawner;
64mod story_command;
65mod visibility_request;
66mod volume_event;
67
68// Per-instance components an entity is composed from: its placement, render
69// description, collision, hierarchy, and gameplay tags.
70mod body_dynamics;
71mod children;
72mod collider;
73mod global_transform;
74mod held;
75mod hidden;
76mod interactable;
77mod mesh_renderer;
78mod model_renderer;
79mod parent;
80mod pickup;
81mod prop_instance;
82mod render_handle;
83mod scene_member;
84mod transform;
85
86pub mod stored;
87
88// Serde / default / round-trip coverage for the generated data-only
89// components, gathered here after their per-type modules were removed.
90#[cfg(test)]
91mod component_tests;
92
93pub use animation::{Animation, AnimationTrack, Keyframe, MorphKey};
94pub use animation_graph::{
95    AnimationBlend, AnimationBlendPoint, AnimationCondition, AnimationGraph, AnimationIkChain,
96    AnimationParam, AnimationState, AnimationTransition,
97};
98pub use animation_params::AnimationParams;
99pub use app_config::AppConfig;
100pub use audio_command::{AudioCommand, AudioTarget};
101pub use audio_occlusion_probe::AudioOcclusionProbe;
102pub use camera_probe::CameraProbe;
103pub use camera3d::Camera3D;
104pub use character_rig::CharacterRig;
105pub use concinnity_asset::AaMode;
106pub use concinnity_asset::AudioBus;
107pub use concinnity_asset::AudioClip;
108pub use concinnity_asset::BlockType;
109pub use concinnity_asset::ColorLut;
110pub use concinnity_asset::CubemapTexture;
111pub use concinnity_asset::Decal;
112pub use concinnity_asset::DirectionalLight;
113pub use concinnity_asset::EnvironmentMap;
114pub use concinnity_asset::FileKind;
115pub use concinnity_asset::Font;
116pub use concinnity_asset::GlassPanel;
117pub use concinnity_asset::GraphicsConfig;
118pub use concinnity_asset::HitRegion;
119pub use concinnity_asset::IndirectLighting;
120pub use concinnity_asset::KeyBinding;
121pub use concinnity_asset::LoadingOverlay;
122pub use concinnity_asset::Material;
123pub use concinnity_asset::ParticleEmitter;
124pub use concinnity_asset::PhysicsConfig;
125pub use concinnity_asset::PointLight;
126pub use concinnity_asset::PostProcessConfig;
127pub use concinnity_asset::ProceduralMesh;
128pub use concinnity_asset::Prop;
129pub use concinnity_asset::PropBody;
130pub use concinnity_asset::PropCollider;
131pub use concinnity_asset::RectAreaLight;
132pub use concinnity_asset::ReflectionBlurResolution;
133pub use concinnity_asset::ReflectionProbe;
134pub use concinnity_asset::RigidBody;
135pub use concinnity_asset::Scene;
136pub use concinnity_asset::ShadowUpdate;
137pub use concinnity_asset::SpotLight;
138pub use concinnity_asset::SsgiResolution;
139pub use concinnity_asset::StreamingConfig;
140pub use concinnity_asset::TextInput;
141pub use concinnity_asset::Texture;
142pub use concinnity_asset::UpscaleQuality;
143pub use concinnity_asset::UpscalerBackend;
144pub use concinnity_asset::VolumetricFog;
145pub use concinnity_asset::VoxelChunk;
146pub use concinnity_asset::VoxelWorld;
147pub use concinnity_asset::{AudioCue, CueKind};
148pub use concinnity_asset::{AudioEmitter, Rolloff};
149pub use concinnity_asset::{
150    Behavior, BehaviorExpr, BehaviorLiteral, BehaviorLocal, BehaviorNode, BehaviorQuery,
151    BehaviorSource, VariableDecl, Variables,
152};
153pub use concinnity_asset::{CameraController, FollowController, FollowDrive};
154pub use concinnity_asset::{
155    CharacterCapsule, MorphDelta, SkeletonJoint, SkinnedMesh, SkinnedVertexData,
156};
157pub use concinnity_asset::{CharacterShape, JointProportion, ResolvedSliders, ShapeSlider};
158pub use concinnity_asset::{InstanceTransform, InstancedProp};
159pub use concinnity_asset::{Justify, LabelBox, LabelPlacement, LayoutContainer, LayoutRow};
160pub use concinnity_asset::{MAX_WATER_WAVES, WaterSurface, WaterWave};
161pub use concinnity_asset::{Mesh, VertexData};
162pub use concinnity_asset::{Model, SubMeshRef};
163pub use concinnity_asset::{PhysicsJoint, PhysicsJointKind};
164pub use concinnity_asset::{Screen, ScreenInput};
165pub use concinnity_asset::{ScrollGroup, ScrollPanel, ScrollRow};
166pub use concinnity_asset::{Sprite, SpriteFit};
167pub use concinnity_asset::{
168    Story, StoryChoice, StoryCompareOp, StoryCondition, StoryGate, StoryImage, StoryNode, StoryOp,
169    StoryPage, StoryPlayback, StoryReload, StoryScaffold, StorySpeaker, StoryStage,
170};
171pub use concinnity_asset::{TextAlign, TextLabel};
172pub use concinnity_asset::{TriggerFilter, TriggerVolume};
173pub use concinnity_asset::{Window, WindowMode};
174pub use contact_event::ContactEvent;
175pub use controls_command::ControlsCommand;
176pub use despawn_request::DespawnRequest;
177pub use entity_target::EntityTarget;
178pub use file::File;
179pub use frame_input::FrameInput;
180pub use gamepad_button::GamepadButton;
181pub use gamepad_map::{GamepadAction, GamepadMap};
182pub use geometry::{
183    GlassPanelGeometry, InstancedPropGeometry, RectAreaLightGeometry, SPOT_MAX_ANGLE_DEG,
184    SpotLightGeometry,
185};
186pub use ground_probes::{GroundProbe, GroundProbes};
187pub use input_key::InputKey;
188pub use interact_event::InteractEvent;
189pub use lifetime::Lifetime;
190pub use nav_direction::NavDirection;
191pub use play_cue::PlayCue;
192pub use post_process_config::PostProcessResolve;
193pub use reparent_request::ReparentRequest;
194pub use room::Room;
195pub use root_motion_event::RootMotionEvent;
196pub use scene_command::SceneCommand;
197pub use screen_command::ScreenCommand;
198pub use screen_shown::ScreenShown;
199pub use sdf_volume::SdfVolume;
200pub use setting_command::{SettingCommand, SettingOp};
201pub use shader::{Shader, ShaderKind, ShaderPayload, StageSource, StageSourceExt};
202pub use skeleton_pose::SkeletonPose;
203pub use skinned_mesh::{SkinnedMeshGeometry, build_skeleton_from_joint_defs};
204pub use spawn_request::SpawnRequest;
205pub use spawner::Spawner;
206pub use story_command::StoryCommand;
207pub use visibility_request::VisibilityRequest;
208pub use volume_event::VolumeEvent;
209
210// Per-instance components an entity is composed from.
211pub use body_dynamics::BodyDynamics;
212pub use children::Children;
213pub use collider::Collider;
214pub use global_transform::GlobalTransform;
215pub use held::Held;
216pub use hidden::Hidden;
217pub use interactable::Interactable;
218pub use mesh_renderer::MeshRenderer;
219pub use model_renderer::ModelRenderer;
220pub use parent::Parent;
221pub use pickup::Pickup;
222pub use prop_instance::PropInstance;
223pub use render_handle::RenderHandle;
224pub use scene_member::SceneMember;
225pub use transform::Transform;
226
227// HUD-overlay request components; their behavior lives in the client crate.
228pub use concinnity_asset::DebugHud;
229pub use concinnity_asset::FpsCounter;
230pub use concinnity_asset::StatHud;
231
232// The file-name extension of a path (the chars after the last `.` of its final
233// component), or `None` when that component has no extension. A pure, no_std
234// stand-in for `Path::new(p).extension().and_then(|e| e.to_str())` over the
235// asset-relative source strings the asset types carry.
236pub(crate) fn path_extension(path: &str) -> Option<&str> {
237    let name = path.rsplit(['/', '\\']).next().unwrap_or(path);
238    match name.rsplit_once('.') {
239        Some((stem, ext)) if !stem.is_empty() => Some(ext),
240        _ => None,
241    }
242}
243
244#[cfg(test)]
245mod tests {
246    // Uniform, low-level checks over the small data-only asset types: their
247    // derive impls, custom Defaults, arg round-trips, injection hooks,
248    // source_path branches, and cross-reference declarations. Kept in one place
249    // because the checks are identical in shape across many one-file components.
250    use super::*;
251    use crate::ecs::asset_id::AssetId;
252    use crate::ecs::{Component, PayloadLocator};
253
254    // Round-trip an asset's defaults through its baked form and the Component
255    // hooks. One call executes the type's Default, serialization, `from_baked`,
256    // `inject_name`, `inject_locator`, and the frame-exactness check.
257    fn exercise<C: Component + Default + serde::Serialize>() {
258        let bytes = postcard::to_allocvec(&C::default()).expect("default serializes");
259        let mut comp = C::from_baked(&bytes).expect("baked bytes deserialize");
260        comp.inject_name(AssetId::default());
261        comp.inject_locator(PayloadLocator {
262            blob_index: 0,
263            offset: 0,
264            len: 0,
265        });
266
267        // A record written by a schema carrying a field this build no longer
268        // reads leaves the tail of its frame unread. `from_baked` takes the
269        // whole frame or fails.
270        let mut widened = bytes.clone();
271        widened.push(0);
272        assert!(
273            C::from_baked(&widened).is_err(),
274            "{} accepted a record with an unread trailing byte",
275            core::any::type_name::<C>()
276        );
277    }
278
279    #[test]
280    fn path_extension_matches_std_path_semantics() {
281        assert_eq!(path_extension("foo.metal"), Some("metal"));
282        assert_eq!(path_extension("shaders/pbr.hlsl"), Some("hlsl"));
283        assert_eq!(path_extension("a.b.glsl"), Some("glsl"));
284        // No extension, a dotfile, and a dotted directory with an extensionless
285        // file all resolve to None, matching `Path::extension`.
286        assert_eq!(path_extension("plain"), None);
287        assert_eq!(path_extension(".bashrc"), None);
288        assert_eq!(path_extension("dir.v2/plain"), None);
289    }
290
291    #[test]
292    fn simple_assets_round_trip_defaults() {
293        exercise::<Scene>();
294        exercise::<Model>();
295        exercise::<ProceduralMesh>();
296        exercise::<WaterSurface>();
297        exercise::<Decal>();
298        exercise::<CharacterShape>();
299        exercise::<ParticleEmitter>();
300        exercise::<VoxelWorld>();
301        exercise::<VoxelChunk>();
302    }
303}