diff --git a/README.md b/README.md index 1377bec..afb055e 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,11 @@ remove :: entity, fragment... -> boolean, boolean clear :: entity -> boolean, boolean destroy :: entity -> boolean, boolean +multi_set :: entity, fragment[], component[]? -> boolean, boolean +multi_assign :: entity, fragment[], component[]? -> boolean, boolean +multi_insert :: entity, fragment[], component[]? -> boolean, boolean +multi_remove :: entity, fragment[] -> boolean, boolean + batch_set :: query, fragment, any... -> integer, boolean batch_assign :: query, fragment, any... -> integer, boolean batch_insert :: query, fragment, any... -> integer, boolean diff --git a/develop/untests.lua b/develop/untests.lua index 9088f8c..e1cbb97 100644 --- a/develop/untests.lua +++ b/develop/untests.lua @@ -2510,3 +2510,436 @@ do assert(entities[1] == e2) end end + +do + local f1, f2, f3 = evo.id(3) + + do + local e = evo.id() + assert(not evo.multi_insert(e, {})) + assert(not evo.multi_insert(e, {}, {})) + assert(not evo.multi_insert(e, {}, { 41 })) + assert(evo.is_alive(e) and evo.is_empty(e)) + + assert(evo.multi_insert(e, { f1 })) + assert(evo.has(e, f1) and evo.get(e, f1) == true) + + assert(not evo.multi_insert(e, { f1 })) + assert(evo.has(e, f1) and evo.get(e, f1) == true) + + assert(evo.multi_insert(e, { f2 }, { 42, 43 })) + assert(evo.has(e, f1) and evo.get(e, f1) == true) + assert(evo.has(e, f2) and evo.get(e, f2) == 42) + end + + do + local e = evo.id() + assert(evo.multi_insert(e, { f1, f2 }, { 41 })) + assert(evo.has(e, f1) and evo.get(e, f1) == 41) + assert(evo.has(e, f2) and evo.get(e, f2) == true) + + assert(evo.multi_insert(e, { f1, f3 }, { 20, 43 })) + assert(evo.has(e, f1) and evo.get(e, f1) == 41) + assert(evo.has(e, f2) and evo.get(e, f2) == true) + assert(evo.has(e, f3) and evo.get(e, f3) == 43) + end +end + +do + local f1, f2, f3 = evo.id(3) + + do + local e1 = evo.id() + assert(evo.multi_insert(e1, { f1, f2 }, { 41, 42 })) + assert(evo.has(e1, f1) and evo.get(e1, f1) == 41) + assert(evo.has(e1, f2) and evo.get(e1, f2) == 42) + + local e2 = evo.id() + assert(evo.multi_insert(e2, { f1, f2 }, { 43, 44 })) + assert(evo.has(e2, f1) and evo.get(e2, f1) == 43) + assert(evo.has(e2, f2) and evo.get(e2, f2) == 44) + + assert(evo.multi_insert(e1, { f3 })) + do + local chunk, entities = evo.chunk(f1, f2) + assert(entities and #entities == 1 and entities[1] == e2) + assert(chunk and evo.select(chunk, f2)[1] == 44) + end + end + + do + local e1, e2 = evo.id(2) + evo.defer() + do + evo.multi_insert(e1, { f1, f2 }, { 41, 42 }) + evo.multi_insert(e2, { f2, f2 }, { 43, 44 }) + end + assert(evo.is_alive(e1) and evo.is_empty(e1)) + assert(evo.is_alive(e2) and evo.is_empty(e2)) + assert(evo.commit()) + assert(evo.has(e1, f1) and evo.get(e1, f1) == 41) + assert(evo.has(e1, f2) and evo.get(e1, f2) == 42) + assert(evo.has(e2, f2) and evo.get(e2, f2) == 44) + end +end + +do + local f1, f2, f3 = evo.id(3) + + evo.set(f3, evo.TAG) + + local last_set_entity = 0 + local last_set_component = 0 + + evo.set(f1, evo.ON_SET, function(e, f, c) + assert(f == f1) + last_set_entity = e + last_set_component = c + end) + + evo.set(f2, evo.ON_SET, function(e, f, c) + assert(f == f2) + last_set_entity = e + last_set_component = c + end) + + evo.set(f3, evo.ON_SET, function(e, f, c) + assert(f == f3) + last_set_entity = e + last_set_component = c + end) + + do + local e = evo.id() + assert(evo.multi_insert(e, { f1, f2 }, { 41, 42 })) + assert(last_set_entity == e and last_set_component == 42) + end + + do + local e = evo.id() + assert(evo.multi_insert(e, { f1, f2, f3 }, { 41, 42, 43 })) + assert(last_set_entity == e and last_set_component == nil) + end +end + +do + local f1, f2, f3 = evo.id(3) + + do + local e = evo.id() + assert(not evo.multi_assign(e, {})) + assert(not evo.multi_assign(e, {}, {})) + assert(not evo.multi_assign(e, {}, { 41 })) + assert(evo.is_alive(e) and evo.is_empty(e)) + + assert(evo.multi_insert(e, { f1 }, { 21 })) + assert(evo.multi_assign(e, { f1, f2 }, { 41, 42 })) + assert(not evo.multi_assign(e, { f2 }, { 42 })) + assert(evo.has(e, f1) and evo.get(e, f1) == 41) + assert(not evo.has(e, f2) and evo.get(e, f2) == nil) + + assert(not evo.multi_assign(e, { f3 }, { 43 })) + assert(evo.has(e, f1) and evo.get(e, f1) == 41) + assert(not evo.has(e, f2) and evo.get(e, f2) == nil) + assert(not evo.has(e, f3) and evo.get(e, f3) == nil) + + assert(evo.multi_insert(e, { f2 }, { 22 })) + assert(evo.multi_assign(e, { f2 })) + assert(evo.has(e, f1) and evo.get(e, f1) == 41) + assert(evo.has(e, f2) and evo.get(e, f2) == true) + assert(evo.multi_assign(e, { f2 }, { 42, 43 })) + assert(evo.has(e, f1) and evo.get(e, f1) == 41) + assert(evo.has(e, f2) and evo.get(e, f2) == 42) + end + + do + local e1, e2 = evo.id(2) + evo.defer() + do + evo.multi_insert(e1, { f1, f2 }, { 21, 22 }) + evo.multi_assign(e1, { f1, f2 }, { 41, 42 }) + + evo.multi_insert(e2, { f1, f2 }, { 31, 32 }) + evo.multi_assign(e2, { f1, f2 }, { 51, 52 }) + end + assert(evo.is_alive(e1) and evo.is_empty(e1)) + assert(evo.is_alive(e2) and evo.is_empty(e2)) + assert(evo.commit()) + assert(evo.has(e1, f1) and evo.get(e1, f1) == 41) + assert(evo.has(e1, f2) and evo.get(e1, f2) == 42) + assert(evo.has(e2, f1) and evo.get(e2, f1) == 51) + assert(evo.has(e2, f2) and evo.get(e2, f2) == 52) + end +end + +do + local f1, f2, f3 = evo.id(3) + + evo.set(f3, evo.TAG) + + local last_set_entity = 0 + local last_set_component = 0 + + evo.set(f1, evo.ON_SET, function(e, f, c) + assert(f == f1) + last_set_entity = e + last_set_component = c + end) + + evo.set(f2, evo.ON_SET, function(e, f, c) + assert(f == f2) + last_set_entity = e + last_set_component = c + end) + + evo.set(f3, evo.ON_SET, function(e, f, c) + assert(f == f3) + last_set_entity = e + last_set_component = c + end) + + do + local e = evo.id() + assert(not evo.multi_assign(e, { f1, f2 }, { 41, 42 })) + assert(last_set_entity == 0 and last_set_component == 0) + + assert(evo.multi_insert(e, { f1, f2 }, { 21, 22 })) + assert(last_set_entity == e and last_set_component == 22) + + assert(evo.multi_assign(e, { f1, f2 }, { 41, 42 })) + assert(last_set_entity == e and last_set_component == 42) + end + + do + local e = evo.id() + assert(evo.multi_insert(e, { f1, f2, f3 }, { 21, 22, 23 })) + assert(last_set_entity == e and last_set_component == nil) + + last_set_entity, last_set_component = 0, 0 + assert(evo.multi_assign(e, { f1, f2, f3 }, { 41, 42, 43 })) + assert(last_set_entity == e and last_set_component == nil) + assert(evo.has(e, f1) and evo.get(e, f1) == 41) + assert(evo.has(e, f2) and evo.get(e, f2) == 42) + assert(evo.has(e, f3) and evo.get(e, f3) == nil) + end +end + +do + local f1, f2, f3, f4 = evo.id(4) + + evo.set(f3, evo.TAG) + + do + local e = evo.id() + assert(evo.multi_insert(e, { f1, f2, f3 }, { 41, 42, 43 })) + assert(evo.has_all(e, f1, f2, f3)) + + assert(evo.multi_remove(e, {})) + assert(evo.multi_remove(e, { f4 })) + assert(evo.has_all(e, f1, f2, f3)) + + assert(evo.multi_remove(e, { f3 })) + assert(evo.has(e, f1) and evo.has(e, f2) and not evo.has(e, f3)) + assert(evo.get(e, f1) == 41 and evo.get(e, f2) == 42 and evo.get(e, f3) == nil) + + assert(evo.multi_remove(e, { f1, f2, f4 })) + assert(not evo.has_any(e, f1, f2, f3)) + assert(evo.get(e, f1) == nil and evo.get(e, f2) == nil and evo.get(e, f3) == nil) + end + + do + local e = evo.id() + assert(evo.multi_insert(e, { f1, f2, f3 }, { 41, 42, 43 })) + assert(evo.has_all(e, f1, f2, f3)) + evo.defer() + evo.multi_remove(e, { f1, f2 }) + assert(evo.has_all(e, f1, f2, f3)) + assert(evo.commit()) + assert(not evo.has(e, f1) and not evo.has(e, f2) and evo.has(e, f3)) + end +end + +do + local f1, f2, f3 = evo.id(3) + + evo.set(f3, evo.TAG) + + local last_remove_entity = 0 + local last_remove_component = 0 + + evo.set(f1, evo.ON_REMOVE, function(e, f, c) + assert(f == f1) + last_remove_entity = e + last_remove_component = c + end) + + evo.set(f2, evo.ON_REMOVE, function(e, f, c) + assert(f == f2) + last_remove_entity = e + last_remove_component = c + end) + + evo.set(f3, evo.ON_REMOVE, function(e, f, c) + assert(f == f3) + last_remove_entity = e + last_remove_component = c + end) + + do + local e = evo.id() + assert(evo.multi_remove(e, { f1, f2 })) + assert(last_remove_entity == 0 and last_remove_component == 0) + + assert(evo.multi_insert(e, { f1, f2, f3 }, { 41, 42 })) + assert(last_remove_entity == 0 and last_remove_component == 0) + assert(evo.multi_remove(e, { f1, f2 })) + assert(last_remove_entity == e and last_remove_component == 42) + assert(evo.multi_remove(e, { f3 })) + assert(last_remove_entity == e and last_remove_component == nil) + end +end + +do + local f1, f2, f3 = evo.id(3) + + do + local e1, e2 = evo.id(2) + assert(evo.multi_insert(e1, { f1, f2, f3 }, { 41, 42, 43 })) + assert(evo.multi_insert(e2, { f1, f2, f3 }, { 44, 45, 46 })) + + assert(evo.multi_remove(e1, { f1, f2 })) + + do + local chunk, entities = evo.chunk(f1, f2, f3) + assert(entities and #entities == 1 and entities[1] == e2) + assert(chunk and evo.select(chunk, f2)[1] == 45) + end + + do + local chunk, entities = evo.chunk(f3) + assert(entities and #entities == 1 and entities[1] == e1) + assert(chunk and evo.select(chunk, f3)[1] == 43) + end + end +end + +do + local f1, f2, f3, f4 = evo.id(4) + + evo.set(f3, evo.DEFAULT, 43) + evo.set(f4, evo.TAG) + + + do + local e = evo.id() + assert(not evo.multi_set(e, {})) + assert(not evo.multi_set(e, {}, {})) + assert(not evo.multi_set(e, {}, { 41 })) + assert(evo.is_alive(e) and evo.is_empty(e)) + + assert(evo.multi_set(e, { f1 })) + assert(evo.has(e, f1) and evo.get(e, f1) == true) + + assert(evo.multi_set(e, { f1 })) + assert(evo.has(e, f1) and evo.get(e, f1) == true) + + assert(evo.multi_set(e, { f1 }, { 41 })) + assert(evo.has(e, f1) and evo.get(e, f1) == 41) + + assert(evo.multi_set(e, { f2 }, { 42 })) + assert(evo.has(e, f1) and evo.get(e, f1) == 41) + assert(evo.has(e, f2) and evo.get(e, f2) == 42) + + assert(evo.multi_set(e, { f2 })) + assert(evo.has(e, f1) and evo.get(e, f1) == 41) + assert(evo.has(e, f2) and evo.get(e, f2) == true) + + assert(evo.multi_set(e, { f2, f3 }, { 42 })) + assert(evo.has(e, f1) and evo.get(e, f1) == 41) + assert(evo.has(e, f2) and evo.get(e, f2) == 42) + assert(evo.has(e, f3) and evo.get(e, f3) == 43) + + assert(evo.multi_set(e, { f3, f4 }, { 33, 44 })) + assert(evo.has(e, f1) and evo.get(e, f1) == 41) + assert(evo.has(e, f2) and evo.get(e, f2) == 42) + assert(evo.has(e, f3) and evo.get(e, f3) == 33) + assert(evo.has(e, f4) and evo.get(e, f4) == nil) + end +end + +do + local f1, f2, f3 = evo.id(3) + + evo.set(f2, evo.DEFAULT, 42) + evo.set(f3, evo.TAG) + + local last_assign_entity = 0 + local last_assign_new_component = 0 + local last_assign_old_component = 0 + + evo.set(f1, evo.ON_ASSIGN, function(e, f, nc, oc) + assert(f == f1) + last_assign_entity = e + last_assign_new_component = nc + last_assign_old_component = oc + end) + + evo.set(f2, evo.ON_ASSIGN, function(e, f, nc, oc) + assert(f == f2) + last_assign_entity = e + last_assign_new_component = nc + last_assign_old_component = oc + end) + + evo.set(f3, evo.ON_ASSIGN, function(e, f, nc, oc) + assert(f == f3) + last_assign_entity = e + last_assign_new_component = nc + last_assign_old_component = oc + end) + + local last_insert_entity = 0 + local last_insert_component = 0 + + evo.set(f1, evo.ON_INSERT, function(e, f, nc) + assert(f == f1) + last_insert_entity = e + last_insert_component = nc + end) + + evo.set(f2, evo.ON_INSERT, function(e, f, nc) + assert(f == f2) + last_insert_entity = e + last_insert_component = nc + end) + + evo.set(f3, evo.ON_INSERT, function(e, f, nc) + assert(f == f3) + last_insert_entity = e + last_insert_component = nc + end) + + do + last_assign_entity, last_assign_old_component, last_assign_new_component = 0, 0, 0 + last_insert_entity, last_insert_component = 0, 0 + + local e = evo.id() + assert(evo.multi_set(e, { f1 })) + assert(last_assign_entity == 0 and last_assign_old_component == 0 and last_assign_new_component == 0) + assert(last_insert_entity == e and last_insert_component == true) + + last_assign_entity, last_assign_old_component, last_assign_new_component = 0, 0, 0 + last_insert_entity, last_insert_component = 0, 0 + + assert(evo.multi_set(e, { f1 }, { 41 })) + assert(last_assign_entity == e and last_assign_old_component == true and last_assign_new_component == 41) + assert(last_insert_entity == 0 and last_insert_component == 0) + + last_assign_entity, last_assign_old_component, last_assign_new_component = 0, 0, 0 + last_insert_entity, last_insert_component = 0, 0 + + assert(evo.multi_set(e, { f1, f2 }, { 11 })) + assert(last_assign_entity == e and last_assign_old_component == 41 and last_assign_new_component == 11) + assert(last_insert_entity == e and last_insert_component == 42) + end +end diff --git a/evolved.lua b/evolved.lua index b06767a..093a732 100644 --- a/evolved.lua +++ b/evolved.lua @@ -369,6 +369,11 @@ local __EMPTY_FRAGMENT_LIST = setmetatable({}, { __newindex = function() error('attempt to modify empty fragment list') end }) +---@type evolved.component[] +local __EMPTY_COMPONENT_LIST = setmetatable({}, { + __newindex = function() error('attempt to modify empty component list') end +}) + ---@type evolved.component[] local __EMPTY_COMPONENT_STORAGE = setmetatable({}, { __newindex = function() error('attempt to modify empty component storage') end @@ -644,6 +649,25 @@ local function __chunk_with_fragment(parent_chunk, child_fragment) return child_chunk end +---@param chunk? evolved.chunk +---@param fragment_list evolved.fragment[] +---@return evolved.chunk? +---@nodiscard +local function __chunk_with_fragment_list(chunk, fragment_list) + local fragment_count = #fragment_list + + if fragment_count == 0 then + return chunk + end + + for i = 1, fragment_count do + local fragment = fragment_list[i] + chunk = __chunk_with_fragment(chunk, fragment) + end + + return chunk +end + ---@param chunk? evolved.chunk ---@param fragment evolved.fragment ---@return evolved.chunk? @@ -699,6 +723,25 @@ local function __chunk_without_fragments(chunk, ...) return chunk end +---@param chunk? evolved.chunk +---@param fragment_list evolved.fragment[] +---@return evolved.chunk? +---@nodiscard +local function __chunk_without_fragment_list(chunk, fragment_list) + local fragment_count = #fragment_list + + if fragment_count == 0 then + return chunk + end + + for i = 1, fragment_count do + local fragment = fragment_list[i] + chunk = __chunk_without_fragment(chunk, fragment) + end + + return chunk +end + --- --- --- @@ -955,8 +998,8 @@ local function __chunk_insert(chunk, fragment, ...) local old_f = old_component_fragments[i] local old_cs = old_component_storages[i] local new_ci = new_component_indices[old_f] - local new_cs = new_component_storages[new_ci] - if new_cs then + if new_ci then + local new_cs = new_component_storages[new_ci] __table_move(old_cs, 1, old_size, new_size + 1, new_cs) end end @@ -1098,8 +1141,8 @@ local function __chunk_remove(chunk, ...) local new_f = new_component_fragments[i] local new_cs = new_component_storages[i] local old_ci = old_component_indices[new_f] - local old_cs = old_component_storages[old_ci] - if old_cs then + if old_ci then + local old_cs = old_component_storages[old_ci] __table_move(old_cs, 1, old_size, new_size + 1, new_cs) end end @@ -1305,12 +1348,18 @@ local __defer_op = { remove = 4, clear = 5, destroy = 6, - batch_set = 7, - batch_assign = 8, - batch_insert = 9, - batch_remove = 10, - batch_clear = 11, - batch_destroy = 12, + + multi_set = 7, + multi_assign = 8, + multi_insert = 9, + multi_remove = 10, + + batch_set = 11, + batch_assign = 12, + batch_insert = 13, + batch_remove = 14, + batch_clear = 15, + batch_destroy = 16, } ---@type table @@ -1352,6 +1401,40 @@ local __defer_ops = { evolved.destroy(entity) return 1 end, + [__defer_op.multi_set] = function(bytes, index) + local entity = bytes[index + 0] + local fragments = bytes[index + 1] + local components = bytes[index + 2] + evolved.multi_set(entity, fragments, components) + __release_table(__TABLE_POOL_TAG__FRAGMENT_LIST, fragments) + __release_table(__TABLE_POOL_TAG__COMPONENT_LIST, components) + return 3 + end, + [__defer_op.multi_assign] = function(bytes, index) + local entity = bytes[index + 0] + local fragments = bytes[index + 1] + local components = bytes[index + 2] + evolved.multi_assign(entity, fragments, components) + __release_table(__TABLE_POOL_TAG__FRAGMENT_LIST, fragments) + __release_table(__TABLE_POOL_TAG__COMPONENT_LIST, components) + return 3 + end, + [__defer_op.multi_insert] = function(bytes, index) + local entity = bytes[index + 0] + local fragments = bytes[index + 1] + local components = bytes[index + 2] + evolved.multi_insert(entity, fragments, components) + __release_table(__TABLE_POOL_TAG__FRAGMENT_LIST, fragments) + __release_table(__TABLE_POOL_TAG__COMPONENT_LIST, components) + return 3 + end, + [__defer_op.multi_remove] = function(bytes, index) + local entity = bytes[index + 0] + local fragments = bytes[index + 1] + evolved.multi_remove(entity, fragments) + __release_table(__TABLE_POOL_TAG__FRAGMENT_LIST, fragments) + return 2 + end, [__defer_op.batch_set] = function(bytes, index) local query = bytes[index + 0] local fragment = bytes[index + 1] @@ -1534,6 +1617,92 @@ local function __defer_destroy(entity) __defer_length = length + 2 end +---@param entity evolved.entity +---@param fragments evolved.fragment[] +---@param components any[] +local function __defer_multi_set(entity, fragments, components) + local fragment_count = #fragments + local fragment_list = __acquire_table(__TABLE_POOL_TAG__FRAGMENT_LIST, fragment_count, 0) + __table_move(fragments, 1, fragment_count, 1, fragment_list) + + local component_count = #components + local component_list = __acquire_table(__TABLE_POOL_TAG__COMPONENT_LIST, component_count, 0) + __table_move(components, 1, component_count, 1, component_list) + + local length = __defer_length + local bytecode = __defer_bytecode + + bytecode[length + 1] = __defer_op.multi_set + bytecode[length + 2] = entity + bytecode[length + 3] = fragment_list + bytecode[length + 4] = component_list + + __defer_length = length + 4 +end + +---@param entity evolved.entity +---@param fragments evolved.fragment[] +---@param components any[] +local function __defer_multi_assign(entity, fragments, components) + local fragment_count = #fragments + local fragment_list = __acquire_table(__TABLE_POOL_TAG__FRAGMENT_LIST, fragment_count, 0) + __table_move(fragments, 1, fragment_count, 1, fragment_list) + + local component_count = #components + local component_list = __acquire_table(__TABLE_POOL_TAG__COMPONENT_LIST, component_count, 0) + __table_move(components, 1, component_count, 1, component_list) + + local length = __defer_length + local bytecode = __defer_bytecode + + bytecode[length + 1] = __defer_op.multi_assign + bytecode[length + 2] = entity + bytecode[length + 3] = fragment_list + bytecode[length + 4] = component_list + + __defer_length = length + 4 +end + +---@param entity evolved.entity +---@param fragments evolved.fragment[] +---@param components any[] +local function __defer_multi_insert(entity, fragments, components) + local fragment_count = #fragments + local fragment_list = __acquire_table(__TABLE_POOL_TAG__FRAGMENT_LIST, fragment_count, 0) + __table_move(fragments, 1, fragment_count, 1, fragment_list) + + local component_count = #components + local component_list = __acquire_table(__TABLE_POOL_TAG__COMPONENT_LIST, component_count, 0) + __table_move(components, 1, component_count, 1, component_list) + + local length = __defer_length + local bytecode = __defer_bytecode + + bytecode[length + 1] = __defer_op.multi_insert + bytecode[length + 2] = entity + bytecode[length + 3] = fragment_list + bytecode[length + 4] = component_list + + __defer_length = length + 4 +end + +---@param entity evolved.entity +---@param fragments evolved.fragment[] +local function __defer_multi_remove(entity, fragments) + local fragment_count = #fragments + local fragment_list = __acquire_table(__TABLE_POOL_TAG__FRAGMENT_LIST, fragment_count, 0) + __table_move(fragments, 1, fragment_count, 1, fragment_list) + + local length = __defer_length + local bytecode = __defer_bytecode + + bytecode[length + 1] = __defer_op.multi_remove + bytecode[length + 2] = entity + bytecode[length + 3] = fragment_list + + __defer_length = length + 3 +end + ---@param query evolved.query ---@param fragment evolved.fragment ---@param ... any component arguments @@ -1834,6 +2003,12 @@ function evolved.set(entity, fragment, ...) local new_chunk = __chunk_with_fragment(old_chunk, fragment) + if not new_chunk then + return false, false + end + + __defer() + if old_chunk == new_chunk then local old_component_indices = old_chunk.__component_indices local old_component_storages = old_chunk.__component_storages @@ -1867,12 +2042,7 @@ function evolved.set(entity, fragment, ...) __fragment_call_set_and_assign_hooks(entity, fragment) end end - - return true, false - end - - __defer() - do + else local new_entities = new_chunk.__entities local new_component_indices = new_chunk.__component_indices local new_component_storages = new_chunk.__component_storages @@ -1880,6 +2050,23 @@ function evolved.set(entity, fragment, ...) local new_place = #new_entities + 1 new_entities[new_place] = entity + if old_chunk then + local old_component_storages = old_chunk.__component_storages + local old_component_fragments = old_chunk.__component_fragments + + for i = 1, #old_component_fragments do + local old_f = old_component_fragments[i] + local old_cs = old_component_storages[i] + local new_ci = new_component_indices[old_f] + if new_ci then + local new_cs = new_component_storages[new_ci] + new_cs[new_place] = old_cs[old_place] + end + end + + __detach_entity(entity) + end + do local new_component_index = new_component_indices[fragment] @@ -1911,28 +2098,12 @@ function evolved.set(entity, fragment, ...) end end - if old_chunk then - local old_component_storages = old_chunk.__component_storages - local old_component_fragments = old_chunk.__component_fragments - - for i = 1, #old_component_fragments do - local old_f = old_component_fragments[i] - local old_cs = old_component_storages[i] - local new_ci = new_component_indices[old_f] - local new_cs = new_component_storages[new_ci] - if new_cs then - new_cs[new_place] = old_cs[old_place] - end - end - - __detach_entity(entity) - end - __entity_chunks[entity_index] = new_chunk __entity_places[entity_index] = new_place __structural_changes = __structural_changes + 1 end + __defer_commit() return true, false end @@ -1961,6 +2132,8 @@ function evolved.assign(entity, fragment, ...) return false, false end + __defer() + do local component_indices = chunk.__component_indices local component_storages = chunk.__component_storages @@ -1996,6 +2169,7 @@ function evolved.assign(entity, fragment, ...) end end + __defer_commit() return true, false end @@ -2021,11 +2195,12 @@ function evolved.insert(entity, fragment, ...) local new_chunk = __chunk_with_fragment(old_chunk, fragment) - if old_chunk == new_chunk then + if not new_chunk or old_chunk == new_chunk then return false, false end __defer() + do local new_entities = new_chunk.__entities local new_component_indices = new_chunk.__component_indices @@ -2034,6 +2209,23 @@ function evolved.insert(entity, fragment, ...) local new_place = #new_entities + 1 new_entities[new_place] = entity + if old_chunk then + local old_component_storages = old_chunk.__component_storages + local old_component_fragments = old_chunk.__component_fragments + + for i = 1, #old_component_fragments do + local old_f = old_component_fragments[i] + local old_cs = old_component_storages[i] + local new_ci = new_component_indices[old_f] + if new_ci then + local new_cs = new_component_storages[new_ci] + new_cs[new_place] = old_cs[old_place] + end + end + + __detach_entity(entity) + end + do local new_component_index = new_component_indices[fragment] @@ -2065,28 +2257,12 @@ function evolved.insert(entity, fragment, ...) end end - if old_chunk then - local old_component_storages = old_chunk.__component_storages - local old_component_fragments = old_chunk.__component_fragments - - for i = 1, #old_component_fragments do - local old_f = old_component_fragments[i] - local old_cs = old_component_storages[i] - local new_ci = new_component_indices[old_f] - local new_cs = new_component_storages[new_ci] - if new_cs then - new_cs[new_place] = old_cs[old_place] - end - end - - __detach_entity(entity) - end - __entity_chunks[entity_index] = new_chunk __entity_places[entity_index] = new_place __structural_changes = __structural_changes + 1 end + __defer_commit() return true, false end @@ -2117,6 +2293,7 @@ function evolved.remove(entity, ...) end __defer() + do local old_fragment_set = old_chunk.__fragment_set local old_component_indices = old_chunk.__component_indices @@ -2150,8 +2327,8 @@ function evolved.remove(entity, ...) local new_f = new_component_fragments[i] local new_cs = new_component_storages[i] local old_ci = old_component_indices[new_f] - local old_cs = old_component_storages[old_ci] - if old_cs then + if old_ci then + local old_cs = old_component_storages[old_ci] new_cs[new_place] = old_cs[old_place] end end @@ -2166,6 +2343,7 @@ function evolved.remove(entity, ...) __structural_changes = __structural_changes + 1 end + __defer_commit() return true, false end @@ -2193,6 +2371,7 @@ function evolved.clear(entity) end __defer() + do local fragment_list = chunk.__fragment_list local component_indices = chunk.__component_indices @@ -2216,6 +2395,7 @@ function evolved.clear(entity) __structural_changes = __structural_changes + 1 end + __defer_commit() return true, false end @@ -2244,6 +2424,7 @@ function evolved.destroy(entity) end __defer() + do local fragment_list = chunk.__fragment_list local component_indices = chunk.__component_indices @@ -2268,6 +2449,477 @@ function evolved.destroy(entity) __structural_changes = __structural_changes + 1 end + + __defer_commit() + return true, false +end + +---@param entity evolved.entity +---@param fragments evolved.fragment[] +---@param components? any[] +---@return boolean is_any_set +---@return boolean is_deferred +function evolved.multi_set(entity, fragments, components) + local fragment_count = #fragments + + if fragment_count == 0 then + return false, false + end + + if not components then + components = __EMPTY_COMPONENT_LIST + end + + if __defer_depth > 0 then + __defer_multi_set(entity, fragments, components) + return false, true + end + + local entity_index = entity % 0x100000 + + if __freelist_ids[entity_index] ~= entity then + return false, false + end + + local old_chunk = __entity_chunks[entity_index] + local old_place = __entity_places[entity_index] + + local new_chunk = __chunk_with_fragment_list(old_chunk, fragments) + + if not new_chunk then + return false, false + end + + __defer() + + if old_chunk == new_chunk then + local old_fragment_set = old_chunk.__fragment_set + local old_component_indices = old_chunk.__component_indices + local old_component_storages = old_chunk.__component_storages + + for i = 1, fragment_count do + local fragment = fragments[i] + if old_fragment_set[fragment] then + local old_component_index = old_component_indices[fragment] + + if old_component_index then + local old_component_storage = old_component_storages[old_component_index] + local old_component = old_component_storage[old_place] + + if old_chunk.__has_defaults_or_constructs then + local new_component = components[i] + + if new_component == nil then new_component = evolved.get(fragment, evolved.DEFAULT) end + if new_component == nil then new_component = true end + + old_component_storage[old_place] = new_component + + if old_chunk.__has_set_or_assign_hooks then + __fragment_call_set_and_assign_hooks(entity, fragment, new_component, old_component) + end + else + local new_component = components[i] + if new_component == nil then new_component = true end + + old_component_storage[old_place] = new_component + + if old_chunk.__has_set_or_assign_hooks then + __fragment_call_set_and_assign_hooks(entity, fragment, new_component, old_component) + end + end + else + if old_chunk.__has_set_or_assign_hooks then + __fragment_call_set_and_assign_hooks(entity, fragment) + end + end + end + end + else + local new_entities = new_chunk.__entities + local new_component_indices = new_chunk.__component_indices + local new_component_storages = new_chunk.__component_storages + + local old_fragment_set = old_chunk and old_chunk.__fragment_set or __EMPTY_FRAGMENT_SET + + local new_place = #new_entities + 1 + new_entities[new_place] = entity + + if old_chunk then + local old_component_storages = old_chunk.__component_storages + local old_component_fragments = old_chunk.__component_fragments + + for i = 1, #old_component_fragments do + local old_f = old_component_fragments[i] + local old_cs = old_component_storages[i] + local new_ci = new_component_indices[old_f] + if new_ci then + local new_cs = new_component_storages[new_ci] + new_cs[new_place] = old_cs[old_place] + end + end + + __detach_entity(entity) + end + + for i = 1, fragment_count do + local fragment = fragments[i] + if old_fragment_set[fragment] then + local new_component_index = new_component_indices[fragment] + + if new_component_index then + local new_component_storage = new_component_storages[new_component_index] + local old_component = new_component_storage[new_place] + + if new_chunk.__has_defaults_or_constructs then + local new_component = components[i] + + if new_component == nil then new_component = evolved.get(fragment, evolved.DEFAULT) end + if new_component == nil then new_component = true end + + new_component_storage[new_place] = new_component + + if new_chunk.__has_set_or_assign_hooks then + __fragment_call_set_and_assign_hooks(entity, fragment, new_component, old_component) + end + else + local new_component = components[i] + if new_component == nil then new_component = true end + + new_component_storage[new_place] = new_component + + if new_chunk.__has_set_or_assign_hooks then + __fragment_call_set_and_assign_hooks(entity, fragment, new_component, old_component) + end + end + else + if new_chunk.__has_set_or_assign_hooks then + __fragment_call_set_and_assign_hooks(entity, fragment) + end + end + else + local new_component_index = new_component_indices[fragment] + + if new_component_index then + local new_component_storage = new_component_storages[new_component_index] + + if new_chunk.__has_defaults_or_constructs then + local new_component = components[i] + + if new_component == nil then new_component = evolved.get(fragment, evolved.DEFAULT) end + if new_component == nil then new_component = true end + + new_component_storage[new_place] = new_component + + if new_chunk.__has_set_or_insert_hooks then + __fragment_call_set_and_insert_hooks(entity, fragment, new_component) + end + else + local new_component = components[i] + if new_component == nil then new_component = true end + + new_component_storage[new_place] = new_component + + if new_chunk.__has_set_or_insert_hooks then + __fragment_call_set_and_insert_hooks(entity, fragment, new_component) + end + end + else + if new_chunk.__has_set_or_insert_hooks then + __fragment_call_set_and_insert_hooks(entity, fragment) + end + end + end + end + + __entity_chunks[entity_index] = new_chunk + __entity_places[entity_index] = new_place + + __structural_changes = __structural_changes + 1 + end + + __defer_commit() + return true, false +end + +---@param entity evolved.entity +---@param fragments evolved.fragment[] +---@param components? any[] +---@return boolean is_any_assigned +---@return boolean is_deferred +function evolved.multi_assign(entity, fragments, components) + local fragment_count = #fragments + + if fragment_count == 0 then + return false, false + end + + if not components then + components = __EMPTY_COMPONENT_LIST + end + + if __defer_depth > 0 then + __defer_multi_assign(entity, fragments, components) + return false, true + end + + local entity_index = entity % 0x100000 + + if __freelist_ids[entity_index] ~= entity then + return false, false + end + + local chunk = __entity_chunks[entity_index] + local place = __entity_places[entity_index] + + if not chunk or not __chunk_has_any_fragment_list(chunk, fragments) then + return false, false + end + + __defer() + + do + local fragment_set = chunk.__fragment_set + local component_indices = chunk.__component_indices + local component_storages = chunk.__component_storages + + for i = 1, fragment_count do + local fragment = fragments[i] + if fragment_set[fragment] then + local component_index = component_indices[fragment] + + if component_index then + local component_storage = component_storages[component_index] + local old_component = component_storage[place] + + if chunk.__has_defaults_or_constructs then + local new_component = components[i] + + if new_component == nil then new_component = evolved.get(fragment, evolved.DEFAULT) end + if new_component == nil then new_component = true end + + component_storage[place] = new_component + + if chunk.__has_set_or_assign_hooks then + __fragment_call_set_and_assign_hooks(entity, fragment, new_component, old_component) + end + else + local new_component = components[i] + if new_component == nil then new_component = true end + + component_storage[place] = new_component + + if chunk.__has_set_or_assign_hooks then + __fragment_call_set_and_assign_hooks(entity, fragment, new_component, old_component) + end + end + else + if chunk.__has_set_or_assign_hooks then + __fragment_call_set_and_assign_hooks(entity, fragment) + end + end + end + end + end + + __defer_commit() + return true, false +end + +---@param entity evolved.entity +---@param fragments evolved.fragment[] +---@param components? any[] +---@return boolean is_any_inserted +---@return boolean is_deferred +function evolved.multi_insert(entity, fragments, components) + local fragment_count = #fragments + + if fragment_count == 0 then + return false, false + end + + if not components then + components = __EMPTY_COMPONENT_LIST + end + + if __defer_depth > 0 then + __defer_multi_insert(entity, fragments, components) + return false, true + end + + local entity_index = entity % 0x100000 + + if __freelist_ids[entity_index] ~= entity then + return false, false + end + + local old_chunk = __entity_chunks[entity_index] + local old_place = __entity_places[entity_index] + + local new_chunk = __chunk_with_fragment_list(old_chunk, fragments) + + if not new_chunk or old_chunk == new_chunk then + return false, false + end + + __defer() + + do + local new_entities = new_chunk.__entities + local new_component_indices = new_chunk.__component_indices + local new_component_storages = new_chunk.__component_storages + + local old_fragment_set = old_chunk and old_chunk.__fragment_set or __EMPTY_FRAGMENT_SET + + local new_place = #new_entities + 1 + new_entities[new_place] = entity + + if old_chunk then + local old_component_storages = old_chunk.__component_storages + local old_component_fragments = old_chunk.__component_fragments + + for i = 1, #old_component_fragments do + local old_f = old_component_fragments[i] + local old_cs = old_component_storages[i] + local new_ci = new_component_indices[old_f] + if new_ci then + local new_cs = new_component_storages[new_ci] + new_cs[new_place] = old_cs[old_place] + end + end + + __detach_entity(entity) + end + + for i = 1, fragment_count do + local fragment = fragments[i] + if not old_fragment_set[fragment] then + local new_component_index = new_component_indices[fragment] + + if new_component_index then + local new_component_storage = new_component_storages[new_component_index] + + if new_chunk.__has_defaults_or_constructs then + local new_component = components[i] + + if new_component == nil then new_component = evolved.get(fragment, evolved.DEFAULT) end + if new_component == nil then new_component = true end + + new_component_storage[new_place] = new_component + + if new_chunk.__has_set_or_insert_hooks then + __fragment_call_set_and_insert_hooks(entity, fragment, new_component) + end + else + local new_component = components[i] + if new_component == nil then new_component = true end + + new_component_storage[new_place] = new_component + + if new_chunk.__has_set_or_insert_hooks then + __fragment_call_set_and_insert_hooks(entity, fragment, new_component) + end + end + else + if new_chunk.__has_set_or_insert_hooks then + __fragment_call_set_and_insert_hooks(entity, fragment) + end + end + end + end + + __entity_chunks[entity_index] = new_chunk + __entity_places[entity_index] = new_place + + __structural_changes = __structural_changes + 1 + end + + __defer_commit() + return true, false +end + +---@param entity evolved.entity +---@param fragments evolved.fragment[] +---@return boolean is_all_removed +---@return boolean is_deferred +function evolved.multi_remove(entity, fragments) + local fragment_count = #fragments + + if fragment_count == 0 then + return true, false + end + + if __defer_depth > 0 then + __defer_multi_remove(entity, fragments) + return false, true + end + + local entity_index = entity % 0x100000 + + if __freelist_ids[entity_index] ~= entity then + return false, false + end + + local old_chunk = __entity_chunks[entity_index] + local old_place = __entity_places[entity_index] + + local new_chunk = __chunk_without_fragment_list(old_chunk, fragments) + + if old_chunk == new_chunk then + return true, false + end + + __defer() + + do + local old_fragment_set = old_chunk.__fragment_set + local old_component_indices = old_chunk.__component_indices + local old_component_storages = old_chunk.__component_storages + + if old_chunk.__has_remove_hooks then + for i = 1, fragment_count do + local fragment = fragments[i] + if old_fragment_set[fragment] then + local old_component_index = old_component_indices[fragment] + if old_component_index then + local old_component_storage = old_component_storages[old_component_index] + local old_component = old_component_storage[old_place] + __fragment_call_remove_hook(entity, fragment, old_component) + else + __fragment_call_remove_hook(entity, fragment) + end + end + end + end + + if new_chunk then + local new_entities = new_chunk.__entities + local new_component_storages = new_chunk.__component_storages + local new_component_fragments = new_chunk.__component_fragments + + local new_place = #new_entities + 1 + new_entities[new_place] = entity + + for i = 1, #new_component_fragments do + local new_f = new_component_fragments[i] + local new_cs = new_component_storages[i] + local old_ci = old_component_indices[new_f] + if old_ci then + local old_cs = old_component_storages[old_ci] + new_cs[new_place] = old_cs[old_place] + end + end + + __detach_entity(entity) + + __entity_chunks[entity_index] = new_chunk + __entity_places[entity_index] = new_place + else + __detach_entity(entity) + end + + __structural_changes = __structural_changes + 1 + end + __defer_commit() return true, false end