package ecs import "core:mem" import "base:runtime" import "core:container/queue" import "core:fmt" import "core:log" import "core:time" 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: rawptr, elem_size: u32, alive: []bool, } Component_Registry :: map[typeid]Component_Storage get_addr_of_component_for_entity_id_from_storage :: proc(storage: ^Component_Storage, entity_id: Entity_ID) -> rawptr { return rawptr(uintptr(storage.data) + uintptr((u32(entity_id) * storage.elem_size))) } // 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, max_entities: u32, 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, systems: []System) -> (^World, bool) { world := new(World) world.name = name world.max_entities = max_entities 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("Registering component '%v' (size: %v).", c, size_of(c)) // if c in world.component_registry { // log.warnf("Component '%v' already registered.", c) // continue // } // // register_component(world, c) // // id := typeid_of(type_of(c)) // // info: ^runtime.Type_Info // // info = type_info_of(id) // // fmt.printfln("Runtime info about component: %v", c) // // world.component_registry[c] = Component_Storage { // // data = make([]type_of(c), max_entities), // // alive = make([]bool, max_entities) // // } // // for i in 0..<10 { // // fmt.printfln("ADDR %v: %v", i, &world.component_registry[c].data[i]) // // } // } { // 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.. bool { log.infof("Registering component '%v' (size: %v).", typeid_of(A), size_of(A)) if A in world.component_registry { log.warnf("Component '%v' already registered.", typeid_of(A)) return false } base := make([]A, world.max_entities) world.component_registry[A] = Component_Storage { data = &base[0], elem_size = size_of(A), alive = make([]bool, world.max_entities) } return true } destroy_world :: proc(world: ^World) { assert(world != nil) for c, e in &world.component_registry { // delete(e.data) free(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) { // Systems. // Dispose the world's systems. dispose_systems(world) world.systems = nil } free(world) } 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 { 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 base_addr := get_addr_of_component_for_entity_id_from_storage(&v, entity.id) mem.set(base_addr, 0, int(v.elem_size)) } } entity^ = INVALID_ENTITY return true } add_component :: proc(entity: Entity, world: ^World, component: $T, loc := #caller_location) -> (^T, bool) { // MAYBE: SS - Remove 'world' here? Entity could cache the world they're a part of.. but that would make entities bigger. 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)(get_addr_of_component_for_entity_id_from_storage(component_storage, 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)(get_addr_of_component_for_entity_id_from_storage(component_storage, entity.id)) // fmt.printfln("Comp is: %#v (size of elem in storage: %v, size of comp: %v)", comp, component_storage.elem_size, size_of(comp)) // fmt.printfln("Address is: %v.", ) 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 = state, 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) { sw: time.Stopwatch for &s in &world.systems { assert(&s != nil) assert(s.tick != nil) time.stopwatch_reset(&sw) time.stopwatch_start(&sw) { s.tick(world, s.state) } time.stopwatch_stop(&sw) // TODO: SS - Reintroduce this at some point. Really nice for debugging! :) // system_tick_duration := time.stopwatch_duration(sw) // fmt.printfln("System '%v' took %v ms.", s.name, time.duration_milliseconds(system_tick_duration)) } world.tick += 1 } Query :: struct { entities: [dynamic]Entity, include, exclude: [dynamic]typeid, } Query_Types :: []typeid create_query :: proc(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 { assert(world != nil) component_storage := &world.component_registry[type] if component_storage == nil { fmt.eprintfln("No component storage for type '%v' in world '%v'.", type, world.name) assert(false) } 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 } } if component_with_fewest_entities == nil { return {} } 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[:] }