diff --git a/ecs.odin b/ecs.odin index a00b015..3d3db42 100644 --- a/ecs.odin +++ b/ecs.odin @@ -1,5 +1,7 @@ package ecs +import "core:mem" +import "base:runtime" import "core:container/queue" import "core:fmt" import "core:log" @@ -29,14 +31,21 @@ entity_old :: proc(world: ^World, entity: Entity) -> bool { } Component_Storage :: struct { - data: []typeid, + 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), @@ -46,8 +55,10 @@ World :: struct { systems: []System, } -create_world :: proc(name: string, max_entities: u32, components_to_register: []typeid, systems: []System) -> (^World, bool) { +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 { @@ -57,19 +68,30 @@ create_world :: proc(name: string, max_entities: u32, components_to_register: [] world.component_registry = registry - for c in components_to_register { - // log.infof("Registered component '%v'.", c) + // 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 - } + // 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) - world.component_registry[c] = Component_Storage { - data = make([]typeid, max_entities), - alive = make([]bool, max_entities) - } - } + // // 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))) @@ -97,11 +119,30 @@ create_world :: proc(name: string, max_entities: u32, components_to_register: [] return world, true } +register_component :: proc(world: ^World, $A: typeid) -> 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) + // delete(e.data) + free(e.data) delete(e.alive) delete_key(&world.component_registry, c) @@ -153,10 +194,11 @@ destroy_entity :: proc(entity: ^Entity, world: ^World, loc := #caller_location) queue.push_back(&world.entity_id_queue, entity.id) // Reset the entity's components. - for c, v in &world.component_registry { + for c, &v in &world.component_registry { if v.alive[entity.id] { v.alive[entity.id] = false - v.data[entity.id] = {} + base_addr := get_addr_of_component_for_entity_id_from_storage(&v, entity.id) + mem.set(base_addr, 0, int(v.elem_size)) } } @@ -165,7 +207,7 @@ destroy_entity :: proc(entity: ^Entity, world: ^World, loc := #caller_location) return true } -add_component :: proc(entity: Entity, world: ^World, component: $T, loc := #caller_location) -> (^T, bool) { +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 @@ -187,7 +229,8 @@ add_component :: proc(entity: Entity, world: ^World, component: $T, loc := #call 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 := transmute(^T)(get_addr_of_component_for_entity_id_from_storage(component_storage, entity.id)) comp^ = component component_storage.alive[entity.id] = true @@ -225,7 +268,9 @@ get_component :: proc($T: typeid, entity: Entity, world: ^World, loc := #caller_ } component_storage := &world.component_registry[T] - comp := transmute(^T)(&component_storage.data[entity.id]) + 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 } @@ -258,7 +303,7 @@ System :: struct { create_system :: proc( name: string, - state: $T, + state: ^$T, init, dispose: proc(world: ^World, state: rawptr) -> bool, tick: proc(world: ^World, state: rawptr), allocator := context.allocator, @@ -266,7 +311,7 @@ create_system :: proc( { return System { name = name, - state = new_clone(state, allocator), + state = state, init = init, dispose = dispose, tick = tick, } @@ -290,7 +335,7 @@ create_system :: proc( s.dispose(world, s.state) } - free(s.state) + // free(s.state) s.state = nil } @@ -312,8 +357,8 @@ tick :: proc(world: ^World) { time.stopwatch_stop(&sw) // TODO: SS - Reintroduce this at some point. Really nice for debugging! :) - // system_tick_time := time.stopwatch_duration(sw) - // fmt.printfln("System '%v' took %v ms.", s.name, time.duration_milliseconds(system_tick_time)) + // system_tick_duration := time.stopwatch_duration(sw) + // fmt.printfln("System '%v' took %v ms.", s.name, time.duration_milliseconds(system_tick_duration)) } @@ -327,7 +372,7 @@ Query :: struct { Query_Types :: []typeid -create_query :: proc(world: ^World, include: []typeid, exclude: []typeid = {}) -> Query { +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. @@ -350,7 +395,13 @@ destroy_query :: proc(query: ^Query) { } 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. @@ -386,7 +437,9 @@ query_entities :: proc(world: ^World, query: ^Query) -> []Entity { } } - assert(component_with_fewest_entities != nil) + 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)