Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/euca-ecs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ categories = ["game-development", "data-structures"]

[dependencies]
euca-math = { path = "../euca-math" }
euca-reflect = { path = "../euca-reflect" }
log = "0.4"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Expand Down
2 changes: 1 addition & 1 deletion crates/euca-ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,4 @@ pub use shared::SharedWorld;
pub use snapshot::{EntitySnapshot, WorldSnapshot};
pub use system::{AccessSystem, IntoSystem, LabeledSystem, System};
pub use system_param::{Res, ResMut, SystemAccess};
pub use world::World;
pub use world::{ReflectComponentFns, World};
92 changes: 92 additions & 0 deletions crates/euca-ecs/src/world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,31 @@ use crate::entity::{Entity, EntityAllocator};
use crate::event::Events;
use crate::query::QueryCache;
use crate::resource::Resources;
use euca_reflect::Reflect;

#[derive(Clone, Copy, Debug)]
pub(crate) struct EntityLocation {
pub archetype_id: ArchetypeId,
pub row: usize,
}

/// Type-erased accessor for getting/inserting a component via [`Reflect`].
///
/// Registered once per component type via [`World::register_reflect`]. Stores
/// function pointers that close over the concrete type, allowing the world to
/// read and write components without knowing `T` at the call site.
#[derive(Clone, Copy)]
pub struct ReflectComponentFns {
/// The `Reflect::type_name()` of this component.
pub type_name: &'static str,
/// The ECS component id for this type.
pub component_id: ComponentId,
/// Clone the component out of the world as a boxed `Reflect` value.
get_fn: fn(&World, Entity) -> Option<Box<dyn Reflect>>,
/// Insert a component from a boxed `Reflect` value.
insert_fn: fn(&mut World, Entity, Box<dyn Reflect>),
}

/// The ECS world: owns all entities, components, archetypes, resources, and events.
///
/// The `World` is the central data structure of the ECS. All entity and
Expand Down Expand Up @@ -58,6 +76,8 @@ pub struct World {
/// `Query::new_cached(&World)` can update the cache through a shared reference.
/// `RwLock` (unlike `RefCell`) is `Sync`, required for `par_for_each`.
pub(crate) query_cache: RwLock<QueryCache>,
/// Reflection bridge: type-erased accessors keyed by `Reflect::type_name()`.
reflect_components: HashMap<&'static str, ReflectComponentFns>,
}

impl World {
Expand All @@ -75,6 +95,7 @@ impl World {
resources: Resources::new(),
events: Events::new(),
query_cache: crate::query::new_query_cache_lock(),
reflect_components: HashMap::new(),
}
}

Expand Down Expand Up @@ -307,6 +328,17 @@ impl World {
self.entities.alive_count()
}

/// Returns all currently alive entities in the world.
///
/// Collects entities from all archetypes. The order is deterministic
/// for a given world state but should not be relied upon.
pub fn all_entities(&self) -> Vec<Entity> {
self.archetypes
.iter()
.flat_map(|arch| arch.entities.iter().copied())
.collect()
}

/// Returns the number of distinct archetypes in the world.
#[inline]
pub fn archetype_count(&self) -> usize {
Expand Down Expand Up @@ -450,6 +482,66 @@ impl World {
self.events.update();
}

// ── Reflection bridge ──

/// Register a component type for reflection-based (type-erased) access.
///
/// After registration, [`get_reflect`](Self::get_reflect) and
/// [`insert_reflect`](Self::insert_reflect) can read/write this component
/// using only the type name string (no generic parameter needed).
///
/// The type must implement [`Reflect`] and [`Clone`] so that values can be
/// extracted from the world without moving them.
pub fn register_reflect<T: 'static + Send + Sync + Reflect + Clone + Default>(&mut self) {
let comp_id = self.components.register::<T>();
let sample = T::default();
let name = sample.type_name();
self.reflect_components.insert(
name,
ReflectComponentFns {
type_name: name,
component_id: comp_id,
get_fn: |world, entity| world.get::<T>(entity).map(|c| c.clone_reflect()),
insert_fn: |world, entity, val| {
if let Some(concrete) = val.as_any().downcast_ref::<T>() {
world.insert(entity, concrete.clone());
}
},
},
);
}

/// Iterate the type names of all reflection-registered component types.
pub fn reflect_component_names(&self) -> impl Iterator<Item = &str> {
self.reflect_components.keys().copied()
}

/// Get a component from an entity by type name, returning a cloned
/// boxed [`Reflect`] value. Returns `None` if the entity does not
/// have the component or the type name is not registered.
pub fn get_reflect(&self, entity: Entity, type_name: &str) -> Option<Box<dyn Reflect>> {
let fns = self.reflect_components.get(type_name)?;
(fns.get_fn)(self, entity)
}

/// Insert a component into an entity by type name. The boxed value
/// must be the correct concrete type (matching what was registered).
///
/// Returns `false` if the type name is not registered.
pub fn insert_reflect(
&mut self,
entity: Entity,
type_name: &str,
value: Box<dyn Reflect>,
) -> bool {
let fns = match self.reflect_components.get(type_name) {
Some(f) => *f,
None => return false,
};
(fns.insert_fn)(self, entity, value);
true
}

// ── Internals ──

fn locate(&self, entity: Entity) -> Option<EntityLocation> {
Expand Down
2 changes: 1 addition & 1 deletion crates/euca-editor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ categories = ["game-development"]
[dependencies]
euca-ecs = { path = "../euca-ecs" }
euca-math = { path = "../euca-math" }
euca-reflect = { path = "../euca-reflect" }
euca-reflect = { path = "../euca-reflect", features = ["json"] }
euca-scene = { path = "../euca-scene" }
euca-core = { path = "../euca-core" }
euca-render = { path = "../euca-render" }
Expand Down
3 changes: 2 additions & 1 deletion crates/euca-editor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ pub use panels::{
hierarchy_panel, inspector_panel, terrain_panel, toolbar_panel,
};
pub use scene_file::{
PrefabRegistry, SCENE_VERSION, SceneEntity, SceneFile, load_scene_into_world,
PrefabRegistry, ReflectSceneEntity, SCENE_VERSION, SceneEntity, SceneFile, SceneFileV3,
load_scene_into_world, load_scene_v3_into_world,
};
pub use undo::UndoHistory;

Expand Down
Loading
Loading