diff --git a/ROADMAP.md b/ROADMAP.md index 2ad52a5..f586d82 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -3,9 +3,7 @@ ## Backlog - optimize batch operations for cases with moving entities to empty chunks -- should we clear chunk's components by on_insert tag callback? - try to keep entity_chunks/places tables as arrays -- update chunk precached hook flags after adding/removing hooks ## After first release diff --git a/develop/untests.lua b/develop/untests.lua index 98079a0..addb91c 100644 --- a/develop/untests.lua +++ b/develop/untests.lua @@ -6237,3 +6237,165 @@ do assert(assign_count == 8 and insert_count == 8 and remove_count == 8) end end + +do + local f1, f2, f3, f4, f5 = evo.id(5) + + local assign_count = 0 + + evo.set(f4, evo.ON_ASSIGN, function() + assign_count = assign_count + 1 + end) + + local e1 = evo.id() + assert(evo.insert(e1, f1, 41)) + + local e12 = evo.id() + assert(evo.insert(e12, f1, 41)) + assert(evo.insert(e12, f2, 42)) + + local e35 = evo.id() + assert(evo.insert(e35, f3, 43)) + assert(evo.insert(e35, f5, 45)) + + local e34 = evo.id() + assert(evo.insert(e34, f3, 43)) + assert(evo.insert(e34, f4, 44)) + + evo.set(f1, evo.ON_ASSIGN, function() + assign_count = assign_count + 1 + end) + + evo.set(f3, evo.ON_ASSIGN, function() + assign_count = assign_count + 1 + end) + + assert(assign_count == 0) + + assert(evo.assign(e1, f1, 41)) + assert(assign_count == 1) + + assert(evo.assign(e12, f1, 42)) + assert(assign_count == 2) + + assert(evo.assign(e34, f3, 43)) + assert(assign_count == 3) + + assert(evo.assign(e35, f3, 43)) + assert(assign_count == 4) +end + +do + local f1, f2, f3 = evo.id(3) + local set_count = 0 + + evo.set(f1, evo.ON_SET, function() set_count = set_count + 1 end) + evo.set(f2, evo.ON_SET, function() set_count = set_count + 1 end) + evo.set(f3, evo.ON_SET, function() set_count = set_count + 1 end) + + local e13 = evo.id() + assert(evo.set(e13, f1, 41) and evo.set(e13, f3, 43)) + assert(set_count == 2) + + local e123 = evo.id() + assert(evo.set(e123, f1, 41) and evo.set(e123, f2, 42) and evo.set(e123, f3, 43)) + assert(set_count == 5) + + assert(evo.assign(e123, f1, 41) and evo.assign(e123, f2, 42) and evo.assign(e123, f3, 43)) + assert(set_count == 8) + + do + set_count = 0 + + assert(evo.remove(f1, evo.ON_SET)) + + evo.set(e13, f1, 41) + assert(set_count == 0) + evo.set(e13, f3, 43) + assert(set_count == 1) + + evo.set(e123, f1, 41) + assert(set_count == 1) + evo.set(e123, f2, 42) + assert(set_count == 2) + evo.set(e123, f3, 43) + assert(set_count == 3) + end + + do + set_count = 0 + + assert(evo.remove(f2, evo.ON_SET)) + + evo.set(e13, f1, 41) + assert(set_count == 0) + evo.set(e13, f3, 43) + assert(set_count == 1) + + evo.set(e123, f1, 41) + assert(set_count == 1) + evo.set(e123, f2, 42) + assert(set_count == 1) + evo.set(e123, f3, 43) + assert(set_count == 2) + end +end + +do + local f1, f2 = evo.id(2) + + local e1 = evo.id() + assert(evo.insert(e1, f1, 41)) + assert(evo.insert(e1, f2, 42)) + + evo.set(f1, evo.DEFAULT, 51) + evo.set(f2, evo.CONSTRUCT, function() return 52 end) + + assert(evo.assign(e1, f1)) + assert(evo.assign(e1, f2)) + + assert(evo.get(e1, f1) == 51) + assert(evo.get(e1, f2) == 52) +end + +do + local f1, f2 = evo.id(2) + + local e1 = evo.id() + assert(evo.insert(e1, f1, 41)) + + local e2 = evo.id() + assert(evo.insert(e2, f1, 41)) + assert(evo.insert(e2, f2, 42)) + + assert(evo.get(e1, f1) == 41) + assert(evo.get(e2, f1) == 41) + assert(evo.get(e2, f2) == 42) + + assert(evo.insert(f1, evo.TAG)) + assert(evo.get(e1, f1) == nil) + assert(evo.get(e2, f1) == nil) + assert(evo.get(e2, f2) == 42) + + assert(evo.remove(f1, evo.TAG)) + assert(evo.get(e1, f1) == true) + assert(evo.get(e2, f1) == true) + assert(evo.get(e2, f2) == 42) + + assert(evo.insert(f2, evo.TAG)) + assert(evo.get(e1, f1) == true) + assert(evo.get(e2, f1) == true) + assert(evo.get(e2, f2) == nil) + + assert(evo.insert(f2, evo.DEFAULT, 42)) + assert(evo.remove(f2, evo.TAG)) + assert(evo.get(e1, f1) == true) + assert(evo.get(e2, f1) == true) + assert(evo.get(e2, f2) == 42) + + assert(evo.set(f1, evo.DEFAULT, 81)) + assert(evo.set(f2, evo.DEFAULT, 82)) + assert(evo.get(e1, f1) == true) + assert(evo.get(e2, f1) == true) + assert(evo.get(e2, f2) == 42) +end diff --git a/evolved.lua b/evolved.lua index 6d22245..87762a6 100644 --- a/evolved.lua +++ b/evolved.lua @@ -458,6 +458,47 @@ local function __component_construct(fragment, ...) return component == nil and true or component end +---@param fragment evolved.fragment +---@param trace fun(chunk: evolved.chunk, ...: any): boolean +---@param ... any additional trace arguments +local function __trace_fragment_chunks(fragment, trace, ...) + local major_chunks = __major_chunks[fragment] + + if not major_chunks then + return + end + + ---@type evolved.chunk[] + local chunk_stack = __acquire_table(__TABLE_POOL_TAG__CHUNK_STACK) + local chunk_stack_size = 0 + + for i = 1, #major_chunks do + local major_chunk = major_chunks[i] + chunk_stack_size = chunk_stack_size + 1 + chunk_stack[chunk_stack_size] = major_chunk + end + + while chunk_stack_size > 0 do + local chunk = chunk_stack[chunk_stack_size] + + chunk_stack[chunk_stack_size] = nil + chunk_stack_size = chunk_stack_size - 1 + + if trace(chunk, ...) then + local chunk_children = chunk.__children + local chunk_child_count = chunk.__child_count + + for i = 1, chunk_child_count do + local chunk_child = chunk_children[i] + chunk_stack_size = chunk_stack_size + 1 + chunk_stack[chunk_stack_size] = chunk_child + end + end + end + + __release_table(__TABLE_POOL_TAG__CHUNK_STACK, chunk_stack, true) +end + ---@param entity evolved.entity ---@param fragment evolved.fragment ---@param new_component evolved.component @@ -5273,10 +5314,138 @@ end --- --- -evolved.set(evolved.TAG, evolved.TAG) +---@param chunk evolved.chunk +---@return boolean +local function __update_chunk_caches_trace(chunk) + local chunk_parent, chunk_fragment = chunk.__parent, chunk.__fragment + + local has_defaults_or_constructs = (chunk_parent and chunk_parent.__has_defaults_or_constructs) + or evolved.has_any(chunk_fragment, evolved.DEFAULT, evolved.CONSTRUCT) + + local has_set_or_assign_hooks = (chunk_parent and chunk_parent.__has_set_or_assign_hooks) + or evolved.has_any(chunk_fragment, evolved.ON_SET, evolved.ON_ASSIGN) + + local has_set_or_insert_hooks = (chunk_parent and chunk_parent.__has_set_or_insert_hooks) + or evolved.has_any(chunk_fragment, evolved.ON_SET, evolved.ON_INSERT) + + local has_remove_hooks = (chunk_parent and chunk_parent.__has_remove_hooks) + or evolved.has(chunk_fragment, evolved.ON_REMOVE) + + chunk.__has_defaults_or_constructs = has_defaults_or_constructs + chunk.__has_set_or_assign_hooks = has_set_or_assign_hooks + chunk.__has_set_or_insert_hooks = has_set_or_insert_hooks + chunk.__has_remove_hooks = has_remove_hooks + + return true +end + +---@param fragment evolved.fragment +local function __update_fragment_hooks(fragment) + __trace_fragment_chunks(fragment, __update_chunk_caches_trace, fragment) +end + +assert(evolved.insert(evolved.ON_SET, evolved.ON_INSERT, __update_fragment_hooks)) +assert(evolved.insert(evolved.ON_ASSIGN, evolved.ON_INSERT, __update_fragment_hooks)) +assert(evolved.insert(evolved.ON_INSERT, evolved.ON_INSERT, __update_fragment_hooks)) +assert(evolved.insert(evolved.ON_REMOVE, evolved.ON_INSERT, __update_fragment_hooks)) + +assert(evolved.insert(evolved.ON_SET, evolved.ON_REMOVE, __update_fragment_hooks)) +assert(evolved.insert(evolved.ON_ASSIGN, evolved.ON_REMOVE, __update_fragment_hooks)) +assert(evolved.insert(evolved.ON_INSERT, evolved.ON_REMOVE, __update_fragment_hooks)) +assert(evolved.insert(evolved.ON_REMOVE, evolved.ON_REMOVE, __update_fragment_hooks)) + +--- +--- +--- +--- +--- + +---@param chunk evolved.chunk +---@param fragment evolved.fragment +---@return boolean +local function __update_chunk_tags_trace(chunk, fragment) + local component_count = chunk.__component_count + local component_indices = chunk.__component_indices + local component_storages = chunk.__component_storages + local component_fragments = chunk.__component_fragments + + local component_index = component_indices[fragment] + + if component_index and evolved.has(fragment, evolved.TAG) then + if component_index ~= component_count then + local last_component_storage = component_storages[component_count] + local last_component_fragment = component_fragments[component_count] + component_indices[last_component_fragment] = component_index + component_storages[component_index] = last_component_storage + component_fragments[component_index] = last_component_fragment + end + + component_indices[fragment] = nil + component_storages[component_count] = nil + component_fragments[component_count] = nil + + component_count = component_count - 1 + chunk.__component_count = component_count + end + + if not component_index and not evolved.has(fragment, evolved.TAG) then + component_count = component_count + 1 + chunk.__component_count = component_count + + local storage = {} + local storage_index = component_count + + component_indices[fragment] = storage_index + component_storages[storage_index] = storage + component_fragments[storage_index] = fragment + + local new_component = evolved.get(fragment, evolved.DEFAULT) + + if new_component == nil then + new_component = true + end + + for i = 1, chunk.__entity_count do + storage[i] = new_component + end + end + + return true +end + +local function __update_fragment_tags(fragment) + __trace_fragment_chunks(fragment, __update_chunk_tags_trace, fragment) +end + +---@param fragment evolved.fragment +local function __update_fragment_defaults(fragment) + __trace_fragment_chunks(fragment, __update_chunk_caches_trace, fragment) +end + +---@param fragment evolved.fragment +local function __update_fragment_constructs(fragment) + __trace_fragment_chunks(fragment, __update_chunk_caches_trace, fragment) +end + +assert(evolved.insert(evolved.TAG, evolved.ON_INSERT, __update_fragment_tags)) +assert(evolved.insert(evolved.TAG, evolved.ON_REMOVE, __update_fragment_tags)) + +assert(evolved.insert(evolved.DEFAULT, evolved.ON_INSERT, __update_fragment_defaults)) +assert(evolved.insert(evolved.DEFAULT, evolved.ON_REMOVE, __update_fragment_defaults)) + +assert(evolved.insert(evolved.CONSTRUCT, evolved.ON_INSERT, __update_fragment_constructs)) +assert(evolved.insert(evolved.CONSTRUCT, evolved.ON_REMOVE, __update_fragment_constructs)) + +--- +--- +--- +--- +--- + +assert(evolved.insert(evolved.TAG, evolved.TAG)) ---@param ... evolved.fragment -evolved.set(evolved.INCLUDES, evolved.CONSTRUCT, function(...) +assert(evolved.insert(evolved.INCLUDES, evolved.CONSTRUCT, function(...) local fragment_count = select('#', ...) if fragment_count == 0 then @@ -5291,11 +5460,11 @@ evolved.set(evolved.INCLUDES, evolved.CONSTRUCT, function(...) end return include_list -end) +end)) ---@param query evolved.query ---@param include_list evolved.fragment[] -evolved.set(evolved.INCLUDES, evolved.ON_SET, function(query, _, include_list) +assert(evolved.insert(evolved.INCLUDES, evolved.ON_SET, function(query, _, include_list) local include_list_size = #include_list ---@type table @@ -5318,14 +5487,14 @@ evolved.set(evolved.INCLUDES, evolved.ON_SET, function(query, _, include_list) evolved.set(query, __INCLUDE_SET, include_set) evolved.set(query, __SORTED_INCLUDE_LIST, sorted_include_list) -end) +end)) -evolved.set(evolved.INCLUDES, evolved.ON_REMOVE, function(query) +assert(evolved.insert(evolved.INCLUDES, evolved.ON_REMOVE, function(query) evolved.remove(query, __INCLUDE_SET, __SORTED_INCLUDE_LIST) -end) +end)) ---@param ... evolved.fragment -evolved.set(evolved.EXCLUDES, evolved.CONSTRUCT, function(...) +assert(evolved.insert(evolved.EXCLUDES, evolved.CONSTRUCT, function(...) local fragment_count = select('#', ...) if fragment_count == 0 then @@ -5340,11 +5509,11 @@ evolved.set(evolved.EXCLUDES, evolved.CONSTRUCT, function(...) end return exclude_list -end) +end)) ---@param query evolved.query ---@param exclude_list evolved.fragment[] -evolved.set(evolved.EXCLUDES, evolved.ON_SET, function(query, _, exclude_list) +assert(evolved.insert(evolved.EXCLUDES, evolved.ON_SET, function(query, _, exclude_list) local exclude_list_size = #exclude_list ---@type table @@ -5367,11 +5536,11 @@ evolved.set(evolved.EXCLUDES, evolved.ON_SET, function(query, _, exclude_list) evolved.set(query, __EXCLUDE_SET, exclude_set) evolved.set(query, __SORTED_EXCLUDE_LIST, sorted_exclude_list) -end) +end)) -evolved.set(evolved.EXCLUDES, evolved.ON_REMOVE, function(query) +assert(evolved.insert(evolved.EXCLUDES, evolved.ON_REMOVE, function(query) evolved.remove(query, __EXCLUDE_SET, __SORTED_EXCLUDE_LIST) -end) +end)) --- ---