Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The freecs Macro

The freecs::ecs! macro generates the entire World struct, component storage, accessors, query methods, and entity management from a declarative definition.

Macro Syntax

#![allow(unused)]
fn main() {
freecs::ecs! {
    World {
        Core {
            local_transform: LocalTransform => LOCAL_TRANSFORM,
            global_transform: GlobalTransform => GLOBAL_TRANSFORM,
            render_mesh: RenderMesh => RENDER_MESH,
            material_ref: MaterialRef => MATERIAL_REF,
            camera: Camera => CAMERA,
            light: Light => LIGHT,
        }
        Ui {
            ui_layout_root: UiLayoutRoot => UI_LAYOUT_ROOT,
            ui_layout_node: UiLayoutNode => UI_LAYOUT_NODE,
        }
        Sprite2d {
            sprite: Sprite => SPRITE,
            sprite_animator: SpriteAnimator => SPRITE_ANIMATOR,
        }
    }
    Resources {
        window: Window,
        input: Input,
        graphics: Graphics,
        active_camera: Option<Entity>,
    }
}
}

Nightshade uses three subsystems: Core (3D engine components), Ui (retained UI), and Sprite2d (2D sprites). Component accessors are scoped to their subsystem: world.core.get_light(entity), world.ui.get_ui_layout_node(entity), world.sprite2d.get_sprite(entity).

Each line in the component block declares:

  1. A field name (snake_case) - used to generate accessor methods
  2. A type - the Rust struct stored for this component
  3. A flag constant (UPPER_SNAKE_CASE) - the bitflag for queries

What Gets Generated

From the macro invocation, freecs generates:

The World Struct

#![allow(unused)]
fn main() {
pub struct World {
    // Internal entity storage (archetype tables, free lists, etc.)
    entities: EntityStorage,
    // All global resources
    pub resources: Resources,
}
}

Per-Component Accessors

For each component foo: Foo => FOO, the macro generates:

#![allow(unused)]
fn main() {
// Immutable access
world.core.get_foo(entity) -> Option<&Foo>

// Mutable access
world.core.get_foo_mut(entity) -> Option<&mut Foo>

// Set value
world.core.set_foo(entity, value: Foo)
}

Entity Management

#![allow(unused)]
fn main() {
// Spawn entities with a component mask
world.spawn_entities(flags: ComponentFlags, count: usize) -> Vec<Entity>

// Despawn entities
world.despawn_entities(entities: &[Entity])

// Check if entity has components
world.core.entity_has_components(entity: Entity, flags: ComponentFlags) -> bool
}

Query Methods

#![allow(unused)]
fn main() {
// Query entities matching a component mask
world.core.query_entities(flags: ComponentFlags) -> impl Iterator<Item = Entity>
}

Component Flag Constants

#![allow(unused)]
fn main() {
pub const LOCAL_TRANSFORM: ComponentFlags = 1 << 0;
pub const GLOBAL_TRANSFORM: ComponentFlags = 1 << 1;
pub const RENDER_MESH: ComponentFlags = 1 << 2;
// ... one per component, powers of 2
}

The Resources Struct

#![allow(unused)]
fn main() {
pub struct Resources {
    pub window: Window,
    pub input: Input,
    pub graphics: Graphics,
    pub active_camera: Option<Entity>,
    // ... all declared resources with Default initialization
}
}

Nightshade's World Definition

Nightshade declares 45 core components, 10 UI components, and 4 sprite components across three subsystems (Core, Ui, Sprite2d), plus 30+ resources. Here is the Core component declaration (simplified paths for readability):

Components

FlagFieldTypeCategory
ANIMATION_PLAYERanimation_playerAnimationPlayerAnimation
NAMEnameNameIdentity
LOCAL_TRANSFORMlocal_transformLocalTransformTransform
GLOBAL_TRANSFORMglobal_transformGlobalTransformTransform
LOCAL_TRANSFORM_DIRTYlocal_transform_dirtyLocalTransformDirtyTransform
PARENTparentParentTransform
IGNORE_PARENT_SCALEignore_parent_scaleIgnoreParentScaleTransform
AUDIO_SOURCEaudio_sourceAudioSourceAudio
AUDIO_LISTENERaudio_listenerAudioListenerAudio
CAMERAcameraCameraCamera
PAN_ORBIT_CAMERApan_orbit_cameraPanOrbitCameraCamera
LIGHTlightLightLighting
LINESlinesLinesDebug
VISIBILITYvisibilityVisibilityRendering
DECALdecalDecalRendering
RENDER_MESHrender_meshRenderMeshRendering
MATERIAL_REFmaterial_refMaterialRefRendering
RENDER_LAYERrender_layerRenderLayerRendering
TEXTtextTextText
TEXT_CHARACTER_COLORStext_character_colorsTextCharacterColorsText
TEXT_CHARACTER_BACKGROUND_COLORStext_character_background_colorsTextCharacterBackgroundColorsText
BOUNDING_VOLUMEbounding_volumeBoundingVolumeSpatial
HOVEREDhoveredHoveredInput
ROTATIONrotationRotationTransform
CASTS_SHADOWcasts_shadowCastsShadowRendering
RIGID_BODYrigid_bodyRigidBodyComponentPhysics
COLLIDERcolliderColliderComponentPhysics
CHARACTER_CONTROLLERcharacter_controllerCharacterControllerComponentPhysics
COLLISION_LISTENERcollision_listenerCollisionListenerPhysics
PHYSICS_INTERPOLATIONphysics_interpolationPhysicsInterpolationPhysics
INSTANCED_MESHinstanced_meshInstancedMeshRendering
PARTICLE_EMITTERparticle_emitterParticleEmitterParticles
PREFAB_SOURCEprefab_sourcePrefabSourcePrefabs
PREFAB_INSTANCEprefab_instancePrefabInstancePrefabs
SCRIPTscriptScriptScripting
SKINskinSkinAnimation
JOINTjointJointAnimation
MORPH_WEIGHTSmorph_weightsMorphWeightsAnimation
NAVMESH_AGENTnavmesh_agentNavMeshAgentNavigation
LATTICElatticeLatticeDeformation
LATTICE_INFLUENCEDlattice_influencedLatticeInfluencedDeformation
WATERwaterWaterRendering
GRASS_REGIONgrass_regionGrassRegionRendering
GRASS_INTERACTORgrass_interactorGrassInteractorRendering
TWEENtweenTweenAnimation

Resources

The Resources block includes:

FieldTypeFeature Gate
windowWindowalways
secondary_windowsSecondaryWindowsalways
user_interfaceUserInterfacealways
graphicsGraphicsalways
inputInputalways
audioAudioEngineaudio
physicsPhysicsWorldphysics
navmeshNavMeshWorldalways
text_cacheTextCachealways
mesh_cacheMeshCachealways
animation_cacheAnimationCachealways
prefab_cachePrefabCachealways
material_registryMaterialRegistryalways
texture_cacheTextureCachealways
active_cameraOption<Entity>always
event_busEventBusalways
command_queueVec<WorldCommand>always
entity_namesHashMap<String, Entity>always

Conditional resources are included only when their feature flag is enabled, using #[cfg(feature = "...")] attributes in the macro.