package ecs import "core:container/queue" import "core:fmt" 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 } 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] return entity.generation < current_gen } Component_Storage :: struct { data: []typeid, alive: []bool, } 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, tick: Tick, systems: []System, } 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) 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.. (Entity, bool) { // Try to get a free entity. id, ok := queue.pop_front_safe(&world.entity_id_queue) if !ok { 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. gen := &world.entity_generations[id] gen^ += 1; return Entity { id = id, generation = gen^, }, true } 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. 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. 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. Location: %v", typeid_of(T), entity, loc) return nil, false } component_storage := &world.component_registry[T] 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] = true return comp, true } has_component :: proc(t: typeid, entity: Entity, world: ^World, loc := #caller_location) -> bool { if !entity_valid(entity) { 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 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] comp := transmute(^T)(&component_storage.data[entity.id]) return comp, true } 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 { comp^ = {} 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[:] }