Initial commit.
This commit is contained in:
176
ecs.odin
Normal file
176
ecs.odin
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
82
ecs_test.odin
Normal file
82
ecs_test.odin
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
package ecs
|
||||||
|
|
||||||
|
import "core:container/queue"
|
||||||
|
import "core:testing"
|
||||||
|
|
||||||
|
@(test) create_world_test :: proc(t: ^testing.T) {
|
||||||
|
A :: struct { v: [3]u32 }
|
||||||
|
B :: struct { v: [3]u32 }
|
||||||
|
C :: struct { v: [3]u32 }
|
||||||
|
|
||||||
|
w, ok := create_world(
|
||||||
|
name = "World",
|
||||||
|
max_entities = 16,
|
||||||
|
components_to_register = { A, B, C, }
|
||||||
|
)
|
||||||
|
assert(ok)
|
||||||
|
defer destroy_world(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test) check_entity_id_queue_test :: proc(t: ^testing.T) {
|
||||||
|
A :: struct { v: [3]u32 }
|
||||||
|
B :: struct { v: [3]u32 }
|
||||||
|
C :: struct { v: [3]u32 }
|
||||||
|
|
||||||
|
MAX_ENTITIES :: 16
|
||||||
|
w, ok := create_world(
|
||||||
|
name = "World",
|
||||||
|
max_entities = MAX_ENTITIES,
|
||||||
|
components_to_register = { A, B, C, }
|
||||||
|
)
|
||||||
|
assert(ok)
|
||||||
|
defer destroy_world(w)
|
||||||
|
|
||||||
|
assert(queue.len(w.entity_id_queue) == MAX_ENTITIES)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test) create_entities_test :: proc(t: ^testing.T) {
|
||||||
|
A :: struct { v: [3]u32 }
|
||||||
|
B :: struct { v: [3]u32 }
|
||||||
|
C :: struct { v: [3]u32 }
|
||||||
|
|
||||||
|
w, ok := create_world(
|
||||||
|
name = "World",
|
||||||
|
max_entities = 1,
|
||||||
|
components_to_register = { A, B, C, }
|
||||||
|
)
|
||||||
|
assert(ok)
|
||||||
|
defer destroy_world(w)
|
||||||
|
|
||||||
|
// This entity should be OK.
|
||||||
|
e1, created_1 := create_entity(w)
|
||||||
|
assert(created_1)
|
||||||
|
assert(e1 != Invalid_Entity)
|
||||||
|
|
||||||
|
// This entity should NOT be OK (because 'max_entities' is 1).
|
||||||
|
e2, created_2 := create_entity(w)
|
||||||
|
assert(!created_2)
|
||||||
|
assert(e2 == Invalid_Entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test) add_component_test :: proc(t: ^testing.T) {
|
||||||
|
A :: struct { v: [3]u32 }
|
||||||
|
B :: struct { v: [3]u32 }
|
||||||
|
C :: struct { v: [3]u32 }
|
||||||
|
|
||||||
|
w, ok := create_world(
|
||||||
|
name = "World",
|
||||||
|
max_entities = 1,
|
||||||
|
components_to_register = { A, B, C, }
|
||||||
|
)
|
||||||
|
assert(ok)
|
||||||
|
defer destroy_world(w)
|
||||||
|
|
||||||
|
e, created := create_entity(w)
|
||||||
|
assert(created)
|
||||||
|
assert(e != Invalid_Entity)
|
||||||
|
|
||||||
|
added_component, component_added := add_component(e, w, A { v = { 1, 2, 3 } })
|
||||||
|
assert(component_added)
|
||||||
|
assert(added_component != nil)
|
||||||
|
assert(added_component.v == { 1, 2, 3})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user