Files
ecs/ecs.odin
2025-11-21 03:41:29 +01:00

176 lines
4.8 KiB
Odin

package ecs
import "core:container/queue"
import "core:log"
Entity :: struct {
id: Entity_ID,
generation: Entity_Generation,
}
Entity_ID :: distinct u32
Entity_Generation :: distinct u32
Invalid_Entity :: Entity { id = 0, generation = 0 }
@(private="file") Start_Entity_ID :: 1
entity_valid :: proc(entity: Entity) -> bool {
return entity != Invalid_Entity
}
entity_old :: proc(world: ^World, entity: Entity) -> bool {
if !entity_valid(entity) {
return false;
}
current_gen := world.entity_generations[entity.id - Start_Entity_ID]
return entity.generation < current_gen
}
Component_Storage :: struct {
data: []typeid,
alive: []bool,
}
Component_Registry :: map[typeid]Component_Storage
World :: struct {
name: string,
component_registry: Component_Registry,
entity_id_queue: queue.Queue(Entity_ID),
entity_generations: []Entity_Generation
}
create_world :: proc(name: string, max_entities: u32, components_to_register: []typeid) -> (^World, bool) {
world := new(World)
registry, err := make(Component_Registry, max_entities)
if err != .None {
free(world)
return nil, false
}
world.component_registry = registry
for c in components_to_register {
// log.infof("Registered component '%v'.", c)
if c in world.component_registry {
// log.warnf("Component '%v' already registered.", c)
continue
}
world.component_registry[c] = Component_Storage {
data = make([]typeid, max_entities),
alive = make([]bool, max_entities)
}
}
{ // Set up the world's queue of available entity-ids.
assert(u64(max_entities) <= u64(max(int)))
queue.init(&world.entity_id_queue, capacity = int(max_entities))
for i in 0..<max_entities {
queue.push_back(&world.entity_id_queue, Entity_ID(Start_Entity_ID + i))
}
}
{ // Set up the world's array of entity-generations.
world.entity_generations = make([]Entity_Generation, max_entities)
}
return world, true
}
destroy_world :: proc(world: ^World) {
assert(world != nil)
for c, e in &world.component_registry {
delete(e.data)
delete(e.alive)
delete_key(&world.component_registry, c)
}
clear(&world.component_registry)
delete(world.component_registry)
queue.destroy(&world.entity_id_queue)
delete(world.entity_generations)
free(world)
}
create_entity :: proc(world: ^World) -> (Entity, bool) {
// Try to get a free entity.
id, ok := queue.pop_front_safe(&world.entity_id_queue)
if !ok {
return Invalid_Entity, false
}
// Grow a generation.
gen := &world.entity_generations[id]
gen^ += 1;
return Entity {
id = id,
generation = gen^,
}, true
}
add_component :: proc(entity: Entity, world: ^World, component: $T) -> (^T, bool) {
if !entity_valid(entity) {
log.warnf("Failed to add component %v - entity is invalid.", typeid_of(T), entity)
return nil, false
}
_, has_component := get_component(T, entity, world)
if has_component {
log.warnf("Failed to add component %v - entity already has this component on it.", typeid_of(T), entity)
return nil, false
}
if entity_old(world, entity) {
log.warnf("Failed to add component %v - entity %v is old.", typeid_of(T), entity)
return nil, false
}
component_storage := &world.component_registry[T]
comp := transmute(^T)(&component_storage.data[entity.id - Start_Entity_ID])
comp^ = component
component_storage.alive[entity.id - Start_Entity_ID] = true
return comp, true
}
get_component :: proc($T: typeid, entity: Entity, world: ^World) -> (^T, bool) {
if !entity_valid(entity) {
log.warnf("Failed to get component %v - entity is invalid.", typeid_of(T), entity)
return nil, false
}
if entity_old(world, entity) {
log.warnf("Failed to get component %v - entity %v is old.", typeid_of(T), entity)
return nil, false
}
component_storage := &world.component_registry[T]
if !component_storage.alive[entity.id - Start_Entity_ID] {
return nil, false
}
comp := transmute(^T)(&component_storage.data[entity.id - Start_Entity_ID])
return comp, true
}
remove_component :: proc($T: typeid, entity: Entity, world: ^World) -> (bool) {
if comp, ok := get_component(T, entity, world); ok {
component_storage := &world.component_registry[T]
comp^ = {}
component_storage.alive[entity.id - Start_Entity_ID] = false
return true
}
return false
}