non-shrinking version of garbage collection

This commit is contained in:
BlackMATov
2026-02-02 20:46:50 +07:00
parent a1440f26df
commit d1105c15ad
10 changed files with 62 additions and 40 deletions

View File

@@ -55,6 +55,7 @@
- [Fragment Requirements](#fragment-requirements) - [Fragment Requirements](#fragment-requirements)
- [Destruction Policies](#destruction-policies) - [Destruction Policies](#destruction-policies)
- [Custom Component Storages](#custom-component-storages) - [Custom Component Storages](#custom-component-storages)
- [Garbage Collection](#garbage-collection)
- [Cheat Sheet](#cheat-sheet) - [Cheat Sheet](#cheat-sheet)
- [Aliases](#aliases) - [Aliases](#aliases)
- [Predefs](#predefs) - [Predefs](#predefs)
@@ -1346,6 +1347,24 @@ evolved.builder()
evolved.process_with(MOVEMENT_SYSTEM, 0.016) evolved.process_with(MOVEMENT_SYSTEM, 0.016)
``` ```
### Garbage Collection
While using the library, some internal data structures can become obsolete and should be cleaned up to free memory. For example, empty chunks that no longer contain entities can be removed. Component storages can also have unused capacity that can be shrunk to save memory. The library provides a function to control this garbage collection process.
```lua
---@param no_shrink? boolean
function evolved.collect_garbage(no_shrink) end
```
By default, [`evolved.collect_garbage`](#evolvedcollect_garbage) cleans up obsolete data structures and shrinks component storages to fit their current size. If you pass `true`, it only cleans up obsolete data structures and skips shrinking. This avoids the overhead of resizing storages, which can be expensive.
Call this function periodically to keep memory usage under control. It is best to call it between levels or during loading screens when performance is not critical. Also, call Lua's built-in garbage collector afterward to ensure all unused memory is freed.
```lua
evolved.collect_garbage()
collectgarbage('collect')
```
## Cheat Sheet ## Cheat Sheet
### Aliases ### Aliases
@@ -1481,7 +1500,7 @@ process :: system... -> ()
process_with :: system, ... -> () process_with :: system, ... -> ()
debug_mode :: boolean -> () debug_mode :: boolean -> ()
collect_garbage :: () collect_garbage :: boolean? -> ()
``` ```
### Classes ### Classes
@@ -2007,7 +2026,8 @@ function evolved.debug_mode(yesno) end
### `evolved.collect_garbage` ### `evolved.collect_garbage`
```lua ```lua
function evolved.collect_garbage() end ---@param no_shrink? boolean
function evolved.collect_garbage(no_shrink) end
``` ```
## Classes ## Classes

View File

@@ -9,7 +9,6 @@
## Thoughts ## Thoughts
- We should have a way to not copy components on deferred spawn/clone - We should have a way to not copy components on deferred spawn/clone
- Having a light version of the gargabe collector can be useful for some use-cases
- Basic default component value as true looks awful, should we use something else? - Basic default component value as true looks awful, should we use something else?
## Known Issues ## Known Issues

View File

@@ -118,11 +118,11 @@ end
--- ---
if math.random(1, 2) == 1 then if math.random(1, 2) == 1 then
evo.collect_garbage() evo.collect_garbage(math.random(1, 2) == 1)
end end
evo.destroy(__table_unpack(all_entity_list)) evo.destroy(__table_unpack(all_entity_list))
if math.random(1, 2) == 1 then if math.random(1, 2) == 1 then
evo.collect_garbage() evo.collect_garbage(math.random(1, 2) == 1)
end end

View File

@@ -124,11 +124,11 @@ end
--- ---
if math.random(1, 2) == 1 then if math.random(1, 2) == 1 then
evo.collect_garbage() evo.collect_garbage(math.random(1, 2) == 1)
end end
evo.destroy(__table_unpack(all_entity_list)) evo.destroy(__table_unpack(all_entity_list))
if math.random(1, 2) == 1 then if math.random(1, 2) == 1 then
evo.collect_garbage() evo.collect_garbage(math.random(1, 2) == 1)
end end

View File

@@ -296,7 +296,7 @@ end
--- ---
if math.random(1, 2) == 1 then if math.random(1, 2) == 1 then
evo.collect_garbage() evo.collect_garbage(math.random(1, 2) == 1)
end end
evo.destroy(__table_unpack(all_query_list)) evo.destroy(__table_unpack(all_query_list))
@@ -304,5 +304,5 @@ evo.destroy(__table_unpack(all_entity_list))
evo.destroy(__table_unpack(all_fragment_list)) evo.destroy(__table_unpack(all_fragment_list))
if math.random(1, 2) == 1 then if math.random(1, 2) == 1 then
evo.collect_garbage() evo.collect_garbage(math.random(1, 2) == 1)
end end

View File

@@ -78,11 +78,11 @@ end
--- ---
if math.random(1, 2) == 1 then if math.random(1, 2) == 1 then
evo.collect_garbage() evo.collect_garbage(math.random(1, 2) == 1)
end end
evo.destroy(__table_unpack(all_entity_list)) evo.destroy(__table_unpack(all_entity_list))
if math.random(1, 2) == 1 then if math.random(1, 2) == 1 then
evo.collect_garbage() evo.collect_garbage(math.random(1, 2) == 1)
end end

View File

@@ -115,17 +115,17 @@ end
if math.random(1, 2) == 1 then if math.random(1, 2) == 1 then
evo.destroy(__table_unpack(all_entity_list)) evo.destroy(__table_unpack(all_entity_list))
if math.random(1, 2) == 1 then if math.random(1, 2) == 1 then
evo.collect_garbage() evo.collect_garbage(math.random(1, 2) == 1)
end end
evo.destroy(__table_unpack(all_fragment_list)) evo.destroy(__table_unpack(all_fragment_list))
else else
evo.destroy(__table_unpack(all_fragment_list)) evo.destroy(__table_unpack(all_fragment_list))
if math.random(1, 2) == 1 then if math.random(1, 2) == 1 then
evo.collect_garbage() evo.collect_garbage(math.random(1, 2) == 1)
end end
evo.destroy(__table_unpack(all_entity_list)) evo.destroy(__table_unpack(all_entity_list))
end end
if math.random(1, 2) == 1 then if math.random(1, 2) == 1 then
evo.collect_garbage() evo.collect_garbage(math.random(1, 2) == 1)
end end

View File

@@ -64,11 +64,11 @@ end
--- ---
if math.random(1, 2) == 1 then if math.random(1, 2) == 1 then
evo.collect_garbage() evo.collect_garbage(math.random(1, 2) == 1)
end end
evo.destroy(__table_unpack(all_entity_list)) evo.destroy(__table_unpack(all_entity_list))
if math.random(1, 2) == 1 then if math.random(1, 2) == 1 then
evo.collect_garbage() evo.collect_garbage(math.random(1, 2) == 1)
end end

View File

@@ -212,7 +212,7 @@
process_with: function(system: System, ...: any) process_with: function(system: System, ...: any)
debug_mode: function(yesno: boolean) debug_mode: function(yesno: boolean)
collect_garbage: function() collect_garbage: function(no_shrink?: boolean)
chunk: function(fragment: Fragment, ...: Fragment): Chunk, { Entity }, integer chunk: function(fragment: Fragment, ...: Fragment): Chunk, { Entity }, integer
builder: function(): Builder builder: function(): Builder

View File

@@ -6145,7 +6145,8 @@ function __evolved_debug_mode(yesno)
__debug_mode = yesno __debug_mode = yesno
end end
function __evolved_collect_garbage() ---@param no_shrink boolean?
function __evolved_collect_garbage(no_shrink)
if __defer_depth > 0 then if __defer_depth > 0 then
__defer_call_hook(__evolved_collect_garbage) __defer_call_hook(__evolved_collect_garbage)
return return
@@ -6209,16 +6210,16 @@ function __evolved_collect_garbage()
local postorder_chunk_entity_count = postorder_chunk.__entity_count local postorder_chunk_entity_count = postorder_chunk.__entity_count
local postorder_chunk_entity_capacity = postorder_chunk.__entity_capacity local postorder_chunk_entity_capacity = postorder_chunk.__entity_capacity
local should_be_purged = local can_be_purged =
postorder_chunk_child_count == 0 and postorder_chunk_child_count == 0 and
postorder_chunk_entity_count == 0 postorder_chunk_entity_count == 0
local should_be_shrunk = local can_be_shrunk =
postorder_chunk_entity_count < postorder_chunk_entity_capacity postorder_chunk_entity_count < postorder_chunk_entity_capacity
if should_be_purged then if can_be_purged then
__purge_chunk(postorder_chunk) __purge_chunk(postorder_chunk)
elseif should_be_shrunk then elseif can_be_shrunk and not no_shrink then
__shrink_chunk(postorder_chunk, 0) __shrink_chunk(postorder_chunk, 0)
end end
end end
@@ -6234,29 +6235,31 @@ function __evolved_collect_garbage()
end end
end end
for table_pool_tag = 1, __table_pool_tag.__count do if not no_shrink then
local table_pool_reserve = __table_pool_reserve[table_pool_tag] for table_pool_tag = 1, __table_pool_tag.__count do
local table_pool_reserve = __table_pool_reserve[table_pool_tag]
---@type evolved.table_pool ---@type evolved.table_pool
local new_table_pool = __lua_table_new(table_pool_reserve) local new_table_pool = __lua_table_new(table_pool_reserve)
for table_pool_index = 1, table_pool_reserve do for table_pool_index = 1, table_pool_reserve do
new_table_pool[table_pool_index] = {} new_table_pool[table_pool_index] = {}
end
new_table_pool.__size = table_pool_reserve
__tagged_table_pools[table_pool_tag] = new_table_pool
end end
new_table_pool.__size = table_pool_reserve do
__entity_chunks = __table_dup(__entity_chunks)
__entity_places = __table_dup(__entity_places)
end
__tagged_table_pools[table_pool_tag] = new_table_pool do
end __defer_points = __list_dup(__defer_points, __defer_depth)
__defer_bytecode = __list_dup(__defer_bytecode, __defer_length)
do end
__entity_chunks = __table_dup(__entity_chunks)
__entity_places = __table_dup(__entity_places)
end
do
__defer_points = __list_dup(__defer_points, __defer_depth)
__defer_bytecode = __list_dup(__defer_bytecode, __defer_length)
end end
__evolved_commit() __evolved_commit()