feat(ecs): added a simple prefab system

This commit is contained in:
lisk77 2025-07-14 01:54:53 +02:00
parent fef128f8a7
commit e1597e6fa4
10 changed files with 416 additions and 257 deletions

0
crates/comet_ecs/Cargo.toml Normal file → Executable file
View file

0
crates/comet_ecs/component_derive/Cargo.toml Normal file → Executable file
View file

0
crates/comet_ecs/component_derive/src/lib.rs Normal file → Executable file
View file

0
crates/comet_ecs/src/archetypes.rs Normal file → Executable file
View file

0
crates/comet_ecs/src/entity.rs Normal file → Executable file
View file

0
crates/comet_ecs/src/id.rs Normal file → Executable file
View file

19
crates/comet_ecs/src/lib.rs Normal file → Executable file
View file

@ -1,12 +1,15 @@
pub use entity::*;
pub use component::*;
pub use scene::*;
pub use id::*;
pub use component_derive::*;
pub use comet_math as math; pub use comet_math as math;
pub use component::*;
pub use component_derive::*;
pub use entity::*;
pub use id::*;
pub use prefabs::PrefabFactory;
pub use scene::*;
mod entity; mod archetypes;
mod component; mod component;
mod scene; mod entity;
mod id; mod id;
mod archetypes; mod prefabs;
mod scene;

View file

@ -0,0 +1,46 @@
use comet_structs::FlatMap;
pub type PrefabFactory = fn(&mut crate::Scene) -> usize;
pub(crate) struct PrefabManager {
pub(crate) prefabs: FlatMap<String, PrefabFactory>,
}
impl PrefabManager {
pub fn new() -> Self {
Self {
prefabs: FlatMap::new(),
}
}
pub fn register(&mut self, name: &str, factory: PrefabFactory) {
self.prefabs.insert(name.to_string(), factory);
}
pub fn has_prefab(&self, name: &str) -> bool {
self.prefabs.contains(&name.to_string())
}
}
#[macro_export]
macro_rules! register_prefab {
($scene:expr, $name:expr, $($component:expr),* $(,)?) => {
{
fn prefab_factory(scene: &mut $crate::Scene) -> usize {
let entity = scene.new_entity() as usize;
$(
scene.add_component(entity, $component);
)*
entity
}
$scene.register_prefab($name, prefab_factory);
}
};
}
#[macro_export]
macro_rules! spawn_prefab {
($scene:expr, $name:expr) => {
$scene.spawn_prefab($name)
};
}

View file

@ -1,297 +1,355 @@
use std::any::TypeId; use crate::archetypes::Archetypes;
use crate::{ use crate::prefabs::PrefabManager;
entity, Component, Entity, IdQueue use crate::{Component, Entity, IdQueue};
};
use comet_log::*; use comet_log::*;
use comet_structs::*; use comet_structs::*;
use crate::archetypes::Archetypes; use std::any::TypeId;
pub struct Scene { pub struct Scene {
id_queue: IdQueue, id_queue: IdQueue,
next_id: u32, next_id: u32,
entities: Vec<Option<Entity>>, entities: Vec<Option<Entity>>,
components: ComponentStorage, components: ComponentStorage,
archetypes: Archetypes archetypes: Archetypes,
prefabs: PrefabManager,
} }
impl Scene { impl Scene {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
id_queue: IdQueue::new(), id_queue: IdQueue::new(),
next_id: 0, next_id: 0,
entities: Vec::new(), entities: Vec::new(),
components: ComponentStorage::new(), components: ComponentStorage::new(),
archetypes: Archetypes::new() archetypes: Archetypes::new(),
} prefabs: PrefabManager::new(),
} }
}
/// Returns the number of how many entities exist in the current Scene. /// Returns the number of how many entities exist in the current Scene.
pub fn active_entities(&self) -> u32 { pub fn active_entities(&self) -> u32 {
self.entities.len() as u32 - self.id_queue.size() self.entities.len() as u32 - self.id_queue.size()
} }
fn get_next_id(&mut self) { fn get_next_id(&mut self) {
if self.id_queue.is_empty() { if self.id_queue.is_empty() {
self.next_id = self.entities.len() as u32; self.next_id = self.entities.len() as u32;
return; return;
} }
if self.next_id > self.id_queue.front().unwrap() || self.entities[self.next_id as usize] != None { if self.next_id > self.id_queue.front().unwrap()
self.next_id = self.id_queue.dequeue().unwrap(); || self.entities[self.next_id as usize] != None
} {
} self.next_id = self.id_queue.dequeue().unwrap();
}
}
/// Retuns the `Vec` of `Option<Entity>` which contains all the entities in the current Scene. /// Retuns the `Vec` of `Option<Entity>` which contains all the entities in the current Scene.
pub fn entities(&self) -> &Vec<Option<Entity>> { pub fn entities(&self) -> &Vec<Option<Entity>> {
&self.entities &self.entities
} }
/// Creates a new entity and returns its ID. /// Creates a new entity and returns its ID.
pub fn new_entity(&mut self) -> u32 { pub fn new_entity(&mut self) -> u32 {
let id = self.next_id; let id = self.next_id;
if (self.next_id as usize) >= self.entities.len() { if (self.next_id as usize) >= self.entities.len() {
self.entities.push(Some(Entity::new(self.next_id))); self.entities.push(Some(Entity::new(self.next_id)));
self.get_next_id(); self.get_next_id();
info!("Created entity! ID: {}", id); info!("Created entity! ID: {}", id);
return id; return id;
} }
self.entities[self.next_id as usize] = Some(Entity::new(self.next_id)); self.entities[self.next_id as usize] = Some(Entity::new(self.next_id));
self.get_next_id(); self.get_next_id();
info!("Created entity! ID: {}", id); info!("Created entity! ID: {}", id);
id id
} }
/// Gets an immutable reference to an entity by its ID. /// Gets an immutable reference to an entity by its ID.
pub fn get_entity(&self, entity_id: usize) -> Option<&Entity> { pub fn get_entity(&self, entity_id: usize) -> Option<&Entity> {
self.entities.get(entity_id).unwrap().as_ref() self.entities.get(entity_id).unwrap().as_ref()
} }
/// Gets a mutable reference to an entity by its ID. /// Gets a mutable reference to an entity by its ID.
pub fn get_entity_mut(&mut self, entity_id: usize) -> Option<&mut Entity> { pub fn get_entity_mut(&mut self, entity_id: usize) -> Option<&mut Entity> {
self.entities.get_mut(entity_id).unwrap().as_mut() self.entities.get_mut(entity_id).unwrap().as_mut()
} }
/// Deletes an entity by its ID. /// Deletes an entity by its ID.
pub fn delete_entity(&mut self, entity_id: usize) { pub fn delete_entity(&mut self, entity_id: usize) {
self.remove_entity_from_archetype_subsets(entity_id as u32, self.get_component_set(entity_id)); self.remove_entity_from_archetype_subsets(
self.entities[entity_id] = None; entity_id as u32,
info!("Deleted entity! ID: {}", entity_id); self.get_component_set(entity_id),
for (_, value) in self.components.iter_mut() { );
value.remove::<u8>(entity_id); self.entities[entity_id] = None;
info!("Deleted entity! ID: {}", entity_id);
for (_, value) in self.components.iter_mut() {
value.remove::<u8>(entity_id);
}
self.id_queue.sorted_enqueue(entity_id as u32);
self.get_next_id();
}
} fn create_archetype(&mut self, components: ComponentSet) {
self.id_queue.sorted_enqueue(entity_id as u32); self.archetypes.create_archetype(components.clone());
self.get_next_id();
}
fn create_archetype(&mut self, components: ComponentSet) { let mut matching_entities = Vec::new();
self.archetypes.create_archetype(components.clone()); for (entity_id, entity_option) in self.entities.iter().enumerate() {
if let Some(_entity) = entity_option {
// Collect entities that match the component set first to avoid borrow checker issues let entity_component_set = self.get_component_set(entity_id);
let mut matching_entities = Vec::new();
for (entity_id, entity_option) in self.entities.iter().enumerate() {
if let Some(_entity) = entity_option {
let entity_component_set = self.get_component_set(entity_id);
// If the entity has all the required components (components is subset of entity's components)
if components.is_subset(&entity_component_set) {
matching_entities.push(entity_id as u32);
}
}
}
// Add all matching entities to the archetype
for entity_id in matching_entities {
self.add_entity_to_archetype(entity_id, components.clone());
}
}
fn remove_archetype(&mut self, components: ComponentSet) { if components.is_subset(&entity_component_set) {
self.archetypes.remove_archetype(&components); matching_entities.push(entity_id as u32);
} }
}
}
fn get_keys(&self, components: ComponentSet) -> Vec<ComponentSet> { for entity_id in matching_entities {
let component_sets = self.archetypes.component_sets(); self.add_entity_to_archetype(entity_id, components.clone());
component_sets.iter() }
.enumerate() }
.filter_map(|(i, &ref elem)| if elem.is_subset(&components) { Some(i) } else { None })
.collect::<Vec<usize>>()
.iter()
.map(|index| component_sets[*index].clone())
.collect::<Vec<ComponentSet>>()
}
fn remove_archetype_subsets(&mut self, components: ComponentSet) { fn get_keys(&self, components: ComponentSet) -> Vec<ComponentSet> {
let keys = self.get_keys(components); let component_sets = self.archetypes.component_sets();
component_sets
.iter()
.enumerate()
.filter_map(|(i, &ref elem)| {
if elem.is_subset(&components) {
Some(i)
} else {
None
}
})
.collect::<Vec<usize>>()
.iter()
.map(|index| component_sets[*index].clone())
.collect::<Vec<ComponentSet>>()
}
for key in keys { fn add_entity_to_archetype(&mut self, entity_id: u32, components: ComponentSet) {
self.remove_archetype(key.clone()); self.archetypes
} .add_entity_to_archetype(&components, entity_id);
} }
fn add_entity_to_archetype(&mut self, entity_id: u32, components: ComponentSet) { fn remove_entity_from_archetype(&mut self, entity_id: u32, components: ComponentSet) {
self.archetypes.add_entity_to_archetype(&components, entity_id); self.archetypes
} .remove_entity_from_archetype(&components, entity_id);
}
fn remove_entity_from_archetype(&mut self, entity_id: u32, components: ComponentSet) { fn remove_entity_from_archetype_subsets(&mut self, entity_id: u32, components: ComponentSet) {
self.archetypes.remove_entity_from_archetype(&components, entity_id); let keys = self.get_keys(components);
}
fn remove_entity_from_archetype_subsets(&mut self, entity_id: u32, components: ComponentSet) { for key in keys {
let keys = self.get_keys(components); self.remove_entity_from_archetype(entity_id, key.clone());
if self.archetypes.get_archetype(&key).unwrap().len() == 0 {
self.archetypes.remove_archetype(&key);
}
}
info!("Removed entity {} from all archetypes!", entity_id);
}
for key in keys { fn get_component_set(&self, entity_id: usize) -> ComponentSet {
self.remove_entity_from_archetype(entity_id, key.clone()); let components = match self.entities.get(entity_id) {
if self.archetypes.get_archetype(&key).unwrap().len() == 0 { Some(cmp) => match cmp.as_ref() {
self.archetypes.remove_archetype(&key); Some(e) => e.get_components().iter().collect::<Vec<usize>>(),
} None => {
} error!("This entity ({}) does not have any components!", entity_id);
info!("Removed entity {} from all archetypes!", entity_id); Vec::new()
} }
},
_ => {
error!("This entity ({}) does not exist!", entity_id);
Vec::new()
}
};
fn get_component_set(&self, entity_id: usize) -> ComponentSet { let type_ids = components
let components = match self.entities.get(entity_id) { .iter()
Some(cmp) => match cmp.as_ref() { .map(|index| self.components.keys()[*index])
Some(e) => e.get_components().iter().collect::<Vec<usize>>(), .collect::<Vec<TypeId>>();
None => { ComponentSet::from_ids(type_ids)
error!("This entity ({}) does not have any components!", entity_id); }
Vec::new()
}
},
_ => {
error!("This entity ({}) does not exist!", entity_id);
Vec::new()
}
};
let type_ids = components.iter().map(|index| self.components.keys()[*index]).collect::<Vec<TypeId>>(); /// Registers a new component in the scene.
ComponentSet::from_ids(type_ids) pub fn register_component<C: Component + 'static>(&mut self) {
} if !self.components.contains(&C::type_id()) {
self.components.register_component::<C>(self.entities.len());
self.create_archetype(ComponentSet::from_ids(vec![C::type_id()]));
info!("Registered component: {}", C::type_name());
return;
}
warn!("Component {} is already registered!", C::type_name());
}
/// Registers a new component in the scene. /// Deregisters a component from the scene.
pub fn register_component<C: Component + 'static>(&mut self) { pub fn deregister_component<C: Component + 'static>(&mut self) {
if !self.components.contains(&C::type_id()) { if self.components.contains(&C::type_id()) {
self.components.register_component::<C>(self.entities.len()); self.components.deregister_component::<C>();
self.create_archetype(ComponentSet::from_ids(vec![C::type_id()])); info!("Deregistered component: {}", C::type_name());
info!("Registered component: {}", C::type_name()); return;
return; }
} warn!("Component {} was not registered!", C::type_name());
warn!("Component {} is already registered!", C::type_name()); }
}
/// Deregisters a component from the scene. /// Adds a component to an entity by its ID and an instance of the component.
pub fn deregister_component<C: Component + 'static>(&mut self) { /// Overwrites the previous component if another component of the same type is added.
if self.components.contains(&C::type_id()) { pub fn add_component<C: Component + 'static>(&mut self, entity_id: usize, component: C) {
self.components.deregister_component::<C>(); let old_component_set = self.get_component_set(entity_id);
info!("Deregistered component: {}", C::type_name()); if !old_component_set.to_vec().is_empty() {
return; self.remove_entity_from_archetype_subsets(entity_id as u32, old_component_set);
} }
warn!("Component {} was not registered!", C::type_name());
}
/// Adds a component to an entity by its ID and an instance of the component. self.components.set_component(entity_id, component);
/// Overwrites the previous component if another component of the same type is added. let component_index = self
pub fn add_component<C: Component + 'static>(&mut self, entity_id: usize, component: C) { .components
let old_component_set = self.get_component_set(entity_id); .keys()
if !old_component_set.to_vec().is_empty() { .iter_mut()
self.remove_entity_from_archetype_subsets(entity_id as u32, old_component_set); .position(|x| *x == C::type_id())
} .unwrap();
self.get_entity_mut(entity_id)
.unwrap()
.add_component(component_index);
self.components.set_component(entity_id, component); let new_component_set = self.get_component_set(entity_id);
let component_index = self.components.keys().iter_mut().position(|x| *x == C::type_id()).unwrap();
self.get_entity_mut(entity_id).unwrap().add_component(component_index);
let new_component_set = self.get_component_set(entity_id); if !self.archetypes.contains_archetype(&new_component_set) {
self.create_archetype(new_component_set.clone());
}
if !self.archetypes.contains_archetype(&new_component_set) { let powerset = ComponentSet::powerset(new_component_set.to_vec());
self.create_archetype(new_component_set.clone());
}
let powerset = ComponentSet::powerset(new_component_set.to_vec()); for subset in powerset {
let component_set = ComponentSet::from_ids(subset.iter().cloned().collect());
for subset in powerset {
let component_set = ComponentSet::from_ids(subset.iter().cloned().collect());
if !self.archetypes.contains_archetype(&component_set) {
self.create_archetype(component_set.clone());
}
self.add_entity_to_archetype(entity_id as u32, component_set);
}
info!("Added component {} to entity {}!", C::type_name(), entity_id); if !self.archetypes.contains_archetype(&component_set) {
} self.create_archetype(component_set.clone());
}
pub fn remove_component<C: Component + 'static>(&mut self, entity_id: usize) { self.add_entity_to_archetype(entity_id as u32, component_set);
let old_component_set = self.get_component_set(entity_id); }
self.remove_entity_from_archetype_subsets(entity_id as u32, old_component_set);
self.components.remove_component::<C>(entity_id); info!(
let component_index = self.components.keys().iter().position(|x| *x == C::type_id()).unwrap(); "Added component {} to entity {}!",
self.get_entity_mut(entity_id).unwrap().remove_component(component_index); C::type_name(),
entity_id
);
}
let new_component_set = self.get_component_set(entity_id); pub fn remove_component<C: Component + 'static>(&mut self, entity_id: usize) {
let old_component_set = self.get_component_set(entity_id);
self.remove_entity_from_archetype_subsets(entity_id as u32, old_component_set);
if !new_component_set.to_vec().is_empty() { self.components.remove_component::<C>(entity_id);
if !self.archetypes.contains_archetype(&new_component_set) { let component_index = self
self.create_archetype(new_component_set.clone()); .components
} .keys()
.iter()
.position(|x| *x == C::type_id())
.unwrap();
self.get_entity_mut(entity_id)
.unwrap()
.remove_component(component_index);
let powerset = ComponentSet::powerset(new_component_set.to_vec()); let new_component_set = self.get_component_set(entity_id);
for subset in powerset {
let component_set = ComponentSet::from_ids(subset.iter().cloned().collect());
if !self.archetypes.contains_archetype(&component_set) {
self.create_archetype(component_set.clone());
}
self.add_entity_to_archetype(entity_id as u32, component_set);
}
}
info!("Removed component {} from entity {}!", C::type_name(), entity_id); if !new_component_set.to_vec().is_empty() {
} if !self.archetypes.contains_archetype(&new_component_set) {
self.create_archetype(new_component_set.clone());
}
/// Returns a reference to a component of an entity by its ID. let powerset = ComponentSet::powerset(new_component_set.to_vec());
pub fn get_component<C: Component + 'static>(&self, entity_id: usize) -> Option<&C> {
self.components.get_component::<C>(entity_id)
}
pub fn get_component_mut<C: Component + 'static>(&mut self, entity_id: usize) -> Option<&mut C> { for subset in powerset {
self.components.get_component_mut::<C>(entity_id) let component_set = ComponentSet::from_ids(subset.iter().cloned().collect());
}
pub fn has<C: Component + 'static>(&self, entity_id: usize) -> bool { if !self.archetypes.contains_archetype(&component_set) {
self.components.get_component::<C>(entity_id).is_some() self.create_archetype(component_set.clone());
} }
/// Returns a list of entities that have the given components. self.add_entity_to_archetype(entity_id as u32, component_set);
pub fn get_entities_with(&self, components: Vec<TypeId>) -> Vec<usize> { }
let component_set = ComponentSet::from_ids(components); }
if self.archetypes.contains_archetype(&component_set) {
return self.archetypes.get_archetype(&component_set).unwrap().clone().iter().map(|x| *x as usize).collect();
}
Vec::new()
}
/// Deletes all entities that have the given components. info!(
pub fn delete_entities_with(&mut self, components: Vec<TypeId>) { "Removed component {} from entity {}!",
let entities = self.get_entities_with(components); C::type_name(),
for entity in entities { entity_id
self.delete_entity(entity); );
} }
}
/// Iterates over all entities that have the given components and calls the given function. /// Returns a reference to a component of an entity by its ID.
pub fn foreach<C: Component, K: Component>(&mut self, func: fn(&mut C,&mut K)) { pub fn get_component<C: Component + 'static>(&self, entity_id: usize) -> Option<&C> {
let entities = self.get_entities_with(vec![C::type_id(), K::type_id()]); self.components.get_component::<C>(entity_id)
for entity in entities { }
let c_ptr = self.get_component_mut::<C>(entity).unwrap() as *mut C;
let k_ptr = self.get_component_mut::<K>(entity).unwrap() as *mut K;
unsafe { pub fn get_component_mut<C: Component + 'static>(
func(&mut *c_ptr, &mut *k_ptr); &mut self,
} entity_id: usize,
} ) -> Option<&mut C> {
} self.components.get_component_mut::<C>(entity_id)
}
pub fn has<C: Component + 'static>(&self, entity_id: usize) -> bool {
self.components.get_component::<C>(entity_id).is_some()
}
/// Returns a list of entities that have the given components.
pub fn get_entities_with(&self, components: Vec<TypeId>) -> Vec<usize> {
let component_set = ComponentSet::from_ids(components);
if self.archetypes.contains_archetype(&component_set) {
return self
.archetypes
.get_archetype(&component_set)
.unwrap()
.clone()
.iter()
.map(|x| *x as usize)
.collect();
}
Vec::new()
}
/// Deletes all entities that have the given components.
pub fn delete_entities_with(&mut self, components: Vec<TypeId>) {
let entities = self.get_entities_with(components);
for entity in entities {
self.delete_entity(entity);
}
}
/// Iterates over all entities that have the given components and calls the given function.
pub fn foreach<C: Component, K: Component>(&mut self, func: fn(&mut C, &mut K)) {
let entities = self.get_entities_with(vec![C::type_id(), K::type_id()]);
for entity in entities {
let c_ptr = self.get_component_mut::<C>(entity).unwrap() as *mut C;
let k_ptr = self.get_component_mut::<K>(entity).unwrap() as *mut K;
unsafe {
func(&mut *c_ptr, &mut *k_ptr);
}
}
}
/// Registers a prefab with the given name and factory function.
pub fn register_prefab(&mut self, name: &str, factory: crate::prefabs::PrefabFactory) {
self.prefabs.register(name, factory);
}
/// Spawns a prefab with the given name.
pub fn spawn_prefab(&mut self, name: &str) -> Option<usize> {
if self.prefabs.has_prefab(name) {
if let Some(factory) = self.prefabs.prefabs.get(&name.to_string()) {
let factory = *factory; // Copy the function pointer
Some(factory(self))
} else {
None
}
} else {
None
}
}
/// Checks if a prefab with the given name exists.
pub fn has_prefab(&self, name: &str) -> bool {
self.prefabs.has_prefab(name)
}
} }

52
examples/prefabs.rs Normal file
View file

@ -0,0 +1,52 @@
use comet::prelude::*;
fn setup(app: &mut App, renderer: &mut Renderer2D) {
// Initialize the texture atlas
renderer.initialize_atlas();
// Register components
app.register_component::<Position2D>();
app.register_component::<Color>();
// Register prefabs
register_prefab!(
app,
"player",
Position2D::from_vec(v2::new(0.0, 0.0)),
Color::new(0.0, 1.0, 0.0, 1.0) // Green player
);
register_prefab!(
app,
"enemy",
Position2D::from_vec(v2::new(5.0, 5.0)),
Color::new(1.0, 0.0, 0.0, 1.0) // Red enemy
);
register_prefab!(
app,
"pickup",
Position2D::from_vec(v2::new(-5.0, -5.0)),
Color::new(1.0, 1.0, 0.0, 1.0) // Yellow pickup
);
if let Some(player_id) = app.spawn_prefab("player") {
debug!("Spawned player with ID: {}", player_id);
}
if let Some(enemy_id) = app.spawn_prefab("enemy") {
debug!("Spawned enemy with ID: {}", enemy_id);
}
if let Some(pickup_id) = app.spawn_prefab("pickup") {
debug!("Spawned pickup with ID: {}", pickup_id);
}
}
fn update(app: &mut App, renderer: &mut Renderer2D, dt: f32) {}
fn main() {
App::new()
.with_title("Prefabs Example")
.run::<Renderer2D>(setup, update);
}