Querying, systems, wow!
This commit is contained in:
297
ecs.odin
297
ecs.odin
@@ -1,6 +1,7 @@
|
||||
package ecs
|
||||
|
||||
import "core:container/queue"
|
||||
import "core:fmt"
|
||||
import "core:log"
|
||||
|
||||
Entity :: struct {
|
||||
@@ -11,11 +12,10 @@ Entity :: struct {
|
||||
Entity_ID :: distinct u32
|
||||
Entity_Generation :: distinct u32
|
||||
|
||||
Invalid_Entity :: Entity { id = 0, generation = 0 }
|
||||
@(private="file") Start_Entity_ID :: 1
|
||||
INVALID_ENTITY :: Entity { id = 0, generation = 0 }
|
||||
|
||||
entity_valid :: proc(entity: Entity) -> bool {
|
||||
return entity != Invalid_Entity
|
||||
return entity != INVALID_ENTITY
|
||||
}
|
||||
|
||||
entity_old :: proc(world: ^World, entity: Entity) -> bool {
|
||||
@@ -23,7 +23,7 @@ entity_old :: proc(world: ^World, entity: Entity) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
current_gen := world.entity_generations[entity.id - Start_Entity_ID]
|
||||
current_gen := world.entity_generations[entity.id]
|
||||
return entity.generation < current_gen
|
||||
}
|
||||
|
||||
@@ -33,15 +33,19 @@ Component_Storage :: struct {
|
||||
}
|
||||
Component_Registry :: map[typeid]Component_Storage
|
||||
|
||||
// IDEA: SS - Add a 'World_State' variable that contains the 'tick' variable and entities? Idk. All the things that are "dynamic".
|
||||
World :: struct {
|
||||
name: string,
|
||||
component_registry: Component_Registry,
|
||||
|
||||
entity_id_queue: queue.Queue(Entity_ID),
|
||||
entity_generations: []Entity_Generation
|
||||
entity_generations: []Entity_Generation,
|
||||
|
||||
tick: Tick,
|
||||
systems: []System,
|
||||
}
|
||||
|
||||
create_world :: proc(name: string, max_entities: u32, components_to_register: []typeid) -> (^World, bool) {
|
||||
create_world :: proc(name: string, max_entities: u32, components_to_register: []typeid, systems: []System) -> (^World, bool) {
|
||||
world := new(World)
|
||||
|
||||
registry, err := make(Component_Registry, max_entities)
|
||||
@@ -70,7 +74,7 @@ create_world :: proc(name: string, max_entities: u32, components_to_register: []
|
||||
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))
|
||||
queue.push_back(&world.entity_id_queue, Entity_ID(i))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +82,17 @@ create_world :: proc(name: string, max_entities: u32, components_to_register: []
|
||||
world.entity_generations = make([]Entity_Generation, max_entities)
|
||||
}
|
||||
|
||||
{ // Systems.
|
||||
// Add the systems to the world and set a reference to this world.
|
||||
world.systems = systems
|
||||
for &s in &world.systems {
|
||||
s.world = world
|
||||
}
|
||||
|
||||
// Then initialize them.
|
||||
init_systems(world)
|
||||
}
|
||||
|
||||
return world, true
|
||||
}
|
||||
|
||||
@@ -98,14 +113,21 @@ destroy_world :: proc(world: ^World) {
|
||||
|
||||
delete(world.entity_generations)
|
||||
|
||||
{ // Systems.
|
||||
// Dispose the world's systems.
|
||||
dispose_systems(world)
|
||||
world.systems = nil
|
||||
}
|
||||
|
||||
free(world)
|
||||
}
|
||||
|
||||
create_entity :: proc(world: ^World) -> (Entity, bool) {
|
||||
create_entity :: proc(world: ^World, loc := #caller_location) -> (Entity, bool) {
|
||||
// Try to get a free entity.
|
||||
id, ok := queue.pop_front_safe(&world.entity_id_queue)
|
||||
if !ok {
|
||||
return Invalid_Entity, false
|
||||
log.warnf("Failed to create entity - no id to pop from the world's (%v) queue of IDs. Location: %v", world.name, loc)
|
||||
return INVALID_ENTITY, false
|
||||
}
|
||||
|
||||
// Grow a generation.
|
||||
@@ -118,59 +140,280 @@ create_entity :: proc(world: ^World) -> (Entity, bool) {
|
||||
}, true
|
||||
}
|
||||
|
||||
add_component :: proc(entity: Entity, world: ^World, component: $T) -> (^T, bool) {
|
||||
destroy_entity :: proc(entity: ^Entity, world: ^World, loc := #caller_location) -> bool {
|
||||
assert(entity != nil)
|
||||
|
||||
if !entity_valid(entity^) {
|
||||
log.warnf("Failed to destroy entity %v - entity is invalid. Location: %v", entity, loc)
|
||||
return false
|
||||
}
|
||||
|
||||
// Return the entity's ID to the world's queue.
|
||||
queue.push_back(&world.entity_id_queue, entity.id)
|
||||
|
||||
// Reset the entity's components.
|
||||
for c, v in &world.component_registry {
|
||||
if v.alive[entity.id] {
|
||||
v.alive[entity.id] = false
|
||||
v.data[entity.id] = {}
|
||||
}
|
||||
}
|
||||
|
||||
entity^ = INVALID_ENTITY
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
add_component :: proc(entity: Entity, world: ^World, component: $T, loc := #caller_location) -> (^T, bool) {
|
||||
if !entity_valid(entity) {
|
||||
log.warnf("Failed to add component %v - entity is invalid.", typeid_of(T), entity)
|
||||
log.warnf("Failed to add component %v - entity is invalid. Location: %v", typeid_of(T), entity, loc)
|
||||
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)
|
||||
log.warnf("Failed to add component %v - entity already has this component on it. Location: %v", typeid_of(T), entity, loc)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if entity_old(world, entity) {
|
||||
log.warnf("Failed to add component %v - entity %v is old.", typeid_of(T), entity)
|
||||
log.warnf("Failed to add component %v - entity %v is old. Location: %v", typeid_of(T), entity, loc)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
component_storage := &world.component_registry[T]
|
||||
comp := transmute(^T)(&component_storage.data[entity.id - Start_Entity_ID])
|
||||
if component_storage == nil {
|
||||
log.warnf("Failed to add component %v - component does not exist in the registry. Location: %v", typeid_of(T), loc)
|
||||
return nil, false
|
||||
}
|
||||
comp := transmute(^T)(&component_storage.data[entity.id])
|
||||
comp^ = component
|
||||
|
||||
component_storage.alive[entity.id - Start_Entity_ID] = true
|
||||
component_storage.alive[entity.id] = true
|
||||
|
||||
return comp, true
|
||||
}
|
||||
|
||||
get_component :: proc($T: typeid, entity: Entity, world: ^World) -> (^T, bool) {
|
||||
has_component :: proc(t: typeid, entity: Entity, world: ^World, loc := #caller_location) -> bool {
|
||||
if !entity_valid(entity) {
|
||||
log.warnf("Failed to get component %v - entity is invalid.", typeid_of(T), entity)
|
||||
return nil, false
|
||||
log.warnf("Failed to check if entity has component %v - entity %v is invalid. Location: %v", t, entity, loc)
|
||||
return false
|
||||
}
|
||||
|
||||
if entity_old(world, entity) {
|
||||
log.warnf("Failed to get component %v - entity %v is old.", typeid_of(T), entity)
|
||||
log.warnf("Failed to check if entity has component %v - entity %v is old. Location: %v", t, entity, loc)
|
||||
return false
|
||||
}
|
||||
|
||||
component_storage := &world.component_registry[t]
|
||||
if component_storage == nil {
|
||||
log.warnf("Failed to get component %v - component does not exist in the registry. Location: %v", t, loc)
|
||||
return false
|
||||
}
|
||||
|
||||
if !component_storage.alive[entity.id] {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
get_component :: proc($T: typeid, entity: Entity, world: ^World, loc := #caller_location) -> (^T, bool) {
|
||||
if !has_component(T, entity, world) {
|
||||
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])
|
||||
comp := transmute(^T)(&component_storage.data[entity.id])
|
||||
return comp, true
|
||||
}
|
||||
|
||||
remove_component :: proc($T: typeid, entity: Entity, world: ^World) -> (bool) {
|
||||
remove_component :: proc($T: typeid, entity: Entity, world: ^World, loc := #caller_location) -> (bool) {
|
||||
component_storage := &world.component_registry[T]
|
||||
if component_storage == nil {
|
||||
log.warnf("Failed to remove component %v from entity %v - component does not exist in the registry. Location: %v", typeid_of(T), entity, loc)
|
||||
return false
|
||||
}
|
||||
|
||||
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
|
||||
component_storage.alive[offset_entity_id(entity.id)] = false
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
Tick :: distinct u64
|
||||
|
||||
System :: struct {
|
||||
name: string,
|
||||
state: rawptr,
|
||||
world: ^World,
|
||||
|
||||
init, dispose: proc(world: ^World, state: rawptr) -> bool,
|
||||
tick: proc(world: ^World, state: rawptr),
|
||||
}
|
||||
|
||||
create_system :: proc(
|
||||
name: string,
|
||||
state: $T,
|
||||
init, dispose: proc(world: ^World, state: rawptr) -> bool,
|
||||
tick: proc(world: ^World, state: rawptr),
|
||||
allocator := context.allocator,
|
||||
) -> System
|
||||
{
|
||||
return System {
|
||||
name = name,
|
||||
state = new_clone(state, allocator),
|
||||
init = init, dispose = dispose,
|
||||
tick = tick,
|
||||
}
|
||||
}
|
||||
|
||||
@(private="file") init_systems :: proc(world: ^World) {
|
||||
for &s in &world.systems {
|
||||
assert(&s != nil)
|
||||
|
||||
if s.init != nil {
|
||||
s.init(world, s.state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@(private="file") dispose_systems :: proc(world: ^World) {
|
||||
#reverse for &s in &world.systems {
|
||||
assert(&s != nil)
|
||||
|
||||
if s.dispose != nil {
|
||||
s.dispose(world, s.state)
|
||||
}
|
||||
|
||||
free(s.state)
|
||||
s.state = nil
|
||||
}
|
||||
|
||||
world.systems = nil
|
||||
}
|
||||
|
||||
tick :: proc(world: ^World) {
|
||||
for &s in &world.systems {
|
||||
assert(&s != nil)
|
||||
assert(s.tick != nil)
|
||||
s.tick(world, s.state)
|
||||
}
|
||||
|
||||
world.tick += 1
|
||||
}
|
||||
|
||||
Query :: struct {
|
||||
entities: [dynamic]Entity,
|
||||
include, exclude: [dynamic]typeid,
|
||||
}
|
||||
|
||||
Query_Types :: []typeid
|
||||
|
||||
create_query :: proc(world: ^World, include: []typeid, exclude: []typeid = {}) -> Query {
|
||||
query: Query
|
||||
|
||||
// TODO: SS - Verify that the same component/typeid doesn't exist in both the include AND the exclude list.
|
||||
|
||||
query.entities = make([dynamic]Entity, 0, 1024) // NOTE: SS - Start capacity is hardcoded.
|
||||
query.include = make([dynamic]typeid, 0, len(include))
|
||||
query.exclude = make([dynamic]typeid, 0, len(exclude))
|
||||
|
||||
append_elems(&query.include, ..include)
|
||||
append_elems(&query.exclude, ..exclude)
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
destroy_query :: proc(query: ^Query) {
|
||||
delete(query.entities)
|
||||
|
||||
delete(query.include)
|
||||
delete(query.exclude)
|
||||
}
|
||||
|
||||
entities_alive_with_component :: proc(type: typeid, world: ^World) -> u32 {
|
||||
component_storage := &world.component_registry[type]
|
||||
assert(component_storage != nil)
|
||||
|
||||
// Check how many entities have this component.
|
||||
amount := u32(0)
|
||||
for alive, i in component_storage.alive {
|
||||
if alive {
|
||||
amount += 1
|
||||
}
|
||||
}
|
||||
|
||||
return amount
|
||||
}
|
||||
|
||||
query_entities :: proc(world: ^World, query: ^Query) -> []Entity {
|
||||
assert(query != nil)
|
||||
clear(&query.entities)
|
||||
|
||||
// Begin with the types-to-include.
|
||||
component_with_fewest_entities: typeid = nil
|
||||
amount_of_entities_with_component := max(u32)
|
||||
|
||||
for t in query.include {
|
||||
amount := entities_alive_with_component(t, world)
|
||||
|
||||
if amount == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// If this component has less entities than 'amount_of_entities_with_component', we assign it as the 'component_with_fewest_entities'.
|
||||
if amount < amount_of_entities_with_component {
|
||||
amount_of_entities_with_component = amount
|
||||
component_with_fewest_entities = t
|
||||
}
|
||||
}
|
||||
|
||||
assert(component_with_fewest_entities != nil)
|
||||
assert(amount_of_entities_with_component > 0)
|
||||
|
||||
// fmt.printfln("component_with_fewest_entities: %v (%v)", component_with_fewest_entities, amount_of_entities_with_component)
|
||||
|
||||
// Add all entities with the 'component_with_fewest_entities' component.
|
||||
component_storage := &world.component_registry[component_with_fewest_entities]
|
||||
for alive, i in component_storage.alive {
|
||||
if alive {
|
||||
id := Entity_ID(i)
|
||||
generation := world.entity_generations[id]
|
||||
|
||||
append(&query.entities, Entity {
|
||||
id = id,
|
||||
generation = generation,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Remove entities that don't have the other 'include'-components.
|
||||
#reverse for entity, i in query.entities {
|
||||
for t in query.include {
|
||||
if t == component_with_fewest_entities {
|
||||
continue
|
||||
}
|
||||
|
||||
if !has_component(t, entity, world) {
|
||||
unordered_remove(&query.entities, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove entities that have any of the 'exclude'-components.
|
||||
#reverse for entity, i in query.entities {
|
||||
for t in query.exclude {
|
||||
assert(t != component_with_fewest_entities) // We should not be here. A component that is in the 'include' part of the query can't be in the 'exclude' part of the query too.
|
||||
|
||||
if has_component(t, entity, world) {
|
||||
unordered_remove(&query.entities, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return query.entities[:]
|
||||
}
|
||||
Reference in New Issue
Block a user