-
Notifications
You must be signed in to change notification settings - Fork 66
fix world_delete #293
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix world_delete #293
Conversation
Ukendio
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall very happy that you took the time to reproduce the test and make a mockup implementation that handled the different invariants very well. Some slight edge cases that we discussed and also some nitpicks have been requested
src/jecs.luau
Outdated
| should_delete = true | ||
| break | ||
| else | ||
| local on_remove = id_record.on_remove | ||
| to_remove[id] = id_record | ||
| remove_count += 1 | ||
| end | ||
| end | ||
|
|
||
| local to = archetype_traverse_remove(world, id, idr_t_archetype) | ||
| for i = #entities, 1, -1 do | ||
| if should_delete then | ||
| for i = #entities, 1, -1 do | ||
| local child = entities[i] | ||
| world_delete(world, child) | ||
| end | ||
| else | ||
| if remove_count == 1 then | ||
| local id, id_record = next(to_remove) | ||
| local to = archetype_traverse_remove(world, id :: i53, idr_t_archetype) | ||
| local on_remove = (id_record :: componentrecord).on_remove | ||
| for i = #entities, 1, -1 do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can delete the child as soon as you see the flag the first time, this way you do not iterate more types than necessary.
Changing this, renaming the variable from should_delete to something else would reflect the behaviour of the code better.
local deleted_any = false
local remove_count = 0
for _, id in idr_t_types do
if not ecs_is_pair(id) then
continue
end
local object = entity_index_get_alive(
entity_index, ecs_pair_second(id))
if object ~= entity then
continue
end
local id_record = component_index[id]
local flags = id_record.flags
local flags_delete_mask = bit32.btest(flags, ecs_id_delete)
if flags_delete_mask then
deleted_any = true
for i = #entities, 1, -1 do
local child = entities[i]
world_delete(world, child)
end
break
else
to_remove[id] = id_record
remove_count += 1
end
end
if deleted_any then
continue
end
src/jecs.luau
Outdated
| end | ||
| if idr_t then | ||
| local archetype_ids = idr_t.records | ||
| local to_remove = {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
type this as { [i53]: componentrecord}
src/jecs.luau
Outdated
| local child = entities[i] | ||
| if on_remove then | ||
| on_remove(child, id) | ||
| on_remove(child, id :: i53) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This type cast is most definitely unnecessary.
src/jecs.luau
Outdated
| local on_remove = record.on_remove | ||
| if on_remove then | ||
| for _, child in entities do | ||
| on_remove(child, id :: i53) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as aforementioned. The type cast is unnecessary.
src/jecs.luau
Outdated
| for id, record in to_remove do | ||
| table.remove(dst_types, table.find(dst_types, id)) | ||
|
|
||
| local on_remove = record.on_remove |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
record just means row. we want to specify it means component_record in this context.
src/jecs.luau
Outdated
| if should_delete then | ||
| for i = #entities, 1, -1 do | ||
| local child = entities[i] | ||
| world_delete(world, child) | ||
| end | ||
| else | ||
| if remove_count == 1 then | ||
| local id, id_record = next(to_remove) | ||
| local to = archetype_traverse_remove(world, id :: i53, idr_t_archetype) | ||
| local on_remove = (id_record :: componentrecord).on_remove | ||
| for i = #entities, 1, -1 do | ||
| local child = entities[i] | ||
| if on_remove then | ||
| on_remove(child, id) | ||
| on_remove(child, id :: i53) | ||
| end | ||
|
|
||
| local r = entity_index_try_get_unsafe(child) :: record | ||
| inner_entity_move(child, r, to) | ||
| end | ||
| end | ||
| end | ||
| elseif remove_count > 1 then | ||
| local dst_types = table.clone(idr_t_types) | ||
| for id, record in to_remove do | ||
| table.remove(dst_types, table.find(dst_types, id)) | ||
|
|
||
| local on_remove = record.on_remove | ||
| if on_remove then | ||
| for _, child in entities do | ||
| on_remove(child, id :: i53) | ||
| end | ||
| end | ||
| end | ||
|
|
||
| local to = archetype_ensure(world, dst_types) | ||
| for i = #entities, 1, -1 do | ||
| local child = entities[i] | ||
| local r = entity_index_try_get_unsafe(child) :: record | ||
| inner_entity_move(child, r, to) | ||
| end | ||
| end | ||
| end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't make special cases for remove_count == 1.
For loops are really cheap and have instructions to stop. whereas manual next() calls is NOT optimized in the VM and are very costly.
This will also allow us to have less branches in general
I would much prefer this which I have now benchmarked to be just as fast even with higher fragmentation (5 pairs with the same target). This will also allow us to check for side effects!
local dst_types = table.clone(idr_t_types)
for id, component_record in to_remove do
table.remove(dst_types, table.find(dst_types, id))
end
local to_u = archetype_ensure(world, dst_types)
for i = #entities, 1, -1 do
local child = entities[i]
local r = entity_index_try_get_unsafe(child) :: record
local to = to_u
for id, component_record in to_remove do
local on_remove = component_record.on_remove
if on_remove then
-- NOTE(marcus): We could be smarter with this and
-- assume hooks are deterministic and that they will
-- move to the same archetype. However users often are not reasonable people.
on_remove(child, id)
local src = r.archetype
if src ~= idr_t_archetype then
to = archetype_traverse_remove(world, id, src)
end
end
end
inner_entity_move(child, r, to)
end
Brief Description of your Changes.
Fixes world_delete removing target pair to all entities, which meant making the archetype empty, which prevented iterating the entities again for further cleanup.
Impact of your Changes
Needs to allocate and populate a dictionary for every archetype. And if removes more than one pair. It needs a more expensive
archetype_ensurecall.Tests Performed
Additional Comments