Skip to content

Commit b9cdbe9

Browse files
committed
custom storage free semantic
1 parent 4f78c82 commit b9cdbe9

3 files changed

Lines changed: 397 additions & 169 deletions

File tree

develop/ROADMAP.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@
99

1010
## Thoughts
1111

12-
- We can create component storages on-demand rather than in advance
1312
- We should have a way to not copy components on deferred spawn/clone
1413
- Not all assoc_list_remove operations need to keep order, we can have an unordered variant also
1514
- We still have several places where we use __lua_next without deterministic order, we should fix that
15+
- Having a light version of the gargabe collector can be useful for some use-cases
16+
- We can shrink the table pool tables on garbage collection if they are too large
17+
- Should we sort chunk children by fragment id?
1618

1719
## Known Issues
1820

develop/testing/realloc_tests.lua

Lines changed: 207 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,37 +22,59 @@ local DOUBLE_STORAGE_TYPEOF = ffi.typeof('$[?]', DOUBLE_TYPEOF)
2222
local STORAGE_SIZES = {}
2323

2424
---@type evolved.realloc
25-
local function float_realloc(old_storage, old_size, new_size)
26-
if old_storage then
27-
assert(STORAGE_SIZES[old_storage] == old_size)
28-
end
25+
local function float_realloc(src, src_size, dst_size)
26+
if dst_size == 0 then
27+
assert(src and src_size > 0)
28+
local expected_src_size = STORAGE_SIZES[src]
29+
assert(expected_src_size == src_size)
30+
STORAGE_SIZES[src] = nil
31+
return
32+
else
33+
if src then
34+
assert(src_size > 0)
35+
local expected_src_size = STORAGE_SIZES[src]
36+
assert(expected_src_size == src_size)
37+
else
38+
assert(src_size == 0)
39+
end
2940

30-
local new_storage = ffi.new(FLOAT_STORAGE_TYPEOF, new_size + 1)
41+
local dst = ffi.new(FLOAT_STORAGE_TYPEOF, dst_size + 1)
42+
STORAGE_SIZES[dst] = dst_size
3143

32-
STORAGE_SIZES[new_storage] = new_size
44+
if src then
45+
ffi.copy(dst + 1, src + 1, math.min(src_size, dst_size) * FLOAT_SIZEOF)
46+
end
3347

34-
if old_storage then
35-
ffi.copy(new_storage + 1, old_storage + 1, math.min(old_size, new_size) * FLOAT_SIZEOF)
48+
return dst
3649
end
37-
38-
return new_storage
3950
end
4051

4152
---@type evolved.realloc
42-
local function double_realloc(old_storage, old_size, new_size)
43-
if old_storage then
44-
assert(STORAGE_SIZES[old_storage] == old_size)
45-
end
53+
local function double_realloc(src, src_size, dst_size)
54+
if dst_size == 0 then
55+
assert(src and src_size > 0)
56+
local expected_src_size = STORAGE_SIZES[src]
57+
assert(expected_src_size == src_size)
58+
STORAGE_SIZES[src] = nil
59+
return
60+
else
61+
if src then
62+
assert(src_size > 0)
63+
local expected_src_size = STORAGE_SIZES[src]
64+
assert(expected_src_size == src_size)
65+
else
66+
assert(src_size == 0)
67+
end
4668

47-
local new_storage = ffi.new(DOUBLE_STORAGE_TYPEOF, new_size + 1)
69+
local dst = ffi.new(DOUBLE_STORAGE_TYPEOF, dst_size + 1)
70+
STORAGE_SIZES[dst] = dst_size
4871

49-
STORAGE_SIZES[new_storage] = new_size
72+
if src then
73+
ffi.copy(dst + 1, src + 1, math.min(src_size, dst_size) * DOUBLE_SIZEOF)
74+
end
5075

51-
if old_storage then
52-
ffi.copy(new_storage + 1, old_storage + 1, math.min(old_size, new_size) * DOUBLE_SIZEOF)
76+
return dst
5377
end
54-
55-
return new_storage
5678
end
5779

5880
---@type evolved.compmove
@@ -399,3 +421,168 @@ do
399421

400422
evo.collect_garbage()
401423
end
424+
425+
do
426+
evo.collect_garbage()
427+
428+
local alloc_call_count = 0
429+
local free_call_count = 0
430+
local resize_call_count = 0
431+
432+
local function ctor_realloc()
433+
---@type evolved.realloc
434+
return function(src, src_size, dst_size)
435+
if dst_size == 0 then
436+
assert(src and src_size > 0)
437+
free_call_count = free_call_count + 1
438+
return
439+
else
440+
if src then
441+
assert(src_size > 0)
442+
resize_call_count = resize_call_count + 1
443+
else
444+
assert(src_size == 0)
445+
alloc_call_count = alloc_call_count + 1
446+
end
447+
448+
local dst = {}
449+
450+
if src then
451+
for i = 1, math.min(src_size, dst_size) do
452+
dst[i] = src[i]
453+
end
454+
end
455+
456+
return dst
457+
end
458+
end
459+
end
460+
461+
do
462+
local realloc1 = ctor_realloc()
463+
local realloc2 = ctor_realloc()
464+
465+
local f1 = evo.builder():default(44):realloc(realloc1):build()
466+
467+
alloc_call_count, free_call_count, resize_call_count = 0, 0, 0
468+
469+
do
470+
local e1 = evo.builder():set(f1, 21):build()
471+
assert(evo.has(e1, f1) and evo.get(e1, f1) == 21)
472+
assert(alloc_call_count == 1 and free_call_count == 0)
473+
474+
local e2 = evo.builder():set(f1, 42):build()
475+
assert(evo.has(e1, f1) and evo.get(e1, f1) == 21)
476+
assert(evo.has(e2, f1) and evo.get(e2, f1) == 42)
477+
assert(alloc_call_count == 1 and free_call_count == 0)
478+
479+
evo.collect_garbage()
480+
assert(alloc_call_count == 1 and free_call_count == 0 and resize_call_count == 0)
481+
482+
evo.destroy(e1)
483+
assert(alloc_call_count == 1 and free_call_count == 0 and resize_call_count == 0)
484+
485+
evo.collect_garbage()
486+
assert(alloc_call_count == 1 and free_call_count == 0 and resize_call_count == 0)
487+
488+
evo.destroy(e2)
489+
assert(alloc_call_count == 1 and free_call_count == 0 and resize_call_count == 0)
490+
491+
evo.collect_garbage()
492+
assert(alloc_call_count == 1 and free_call_count == 1 and resize_call_count == 0)
493+
end
494+
495+
alloc_call_count, free_call_count, resize_call_count = 0, 0, 0
496+
497+
do
498+
local es, ec = evo.multi_spawn(10, { [f1] = 84 })
499+
assert(alloc_call_count == 1 and free_call_count == 0 and resize_call_count == 0)
500+
501+
for i = 1, ec / 2 do evo.destroy(es[i]) end
502+
assert(alloc_call_count == 1 and free_call_count == 0 and resize_call_count == 0)
503+
504+
evo.collect_garbage()
505+
assert(alloc_call_count == 1 and free_call_count == 0 and resize_call_count == 1)
506+
507+
evo.set(f1, evo.REALLOC, realloc2)
508+
assert(alloc_call_count == 2 and free_call_count == 1 and resize_call_count == 1)
509+
510+
for i = 1, ec do evo.destroy(es[i]) end
511+
evo.collect_garbage()
512+
assert(alloc_call_count == 2 and free_call_count == 2 and resize_call_count == 1)
513+
end
514+
515+
alloc_call_count, free_call_count, resize_call_count = 0, 0, 0
516+
517+
do
518+
local e1 = evo.builder():set(f1, 24):build()
519+
assert(evo.has(e1, f1) and evo.get(e1, f1) == 24)
520+
assert(alloc_call_count == 1 and free_call_count == 0 and resize_call_count == 0)
521+
522+
evo.set(f1, evo.TAG)
523+
assert(evo.has(e1, f1) and evo.get(e1, f1) == nil)
524+
assert(alloc_call_count == 1 and free_call_count == 1 and resize_call_count == 0)
525+
526+
local es, ec = evo.multi_spawn(20, { [f1] = 48 })
527+
for i = 1, ec do assert(evo.has(es[i], f1) and evo.get(es[i], f1) == nil) end
528+
assert(alloc_call_count == 1 and free_call_count == 1 and resize_call_count == 0)
529+
530+
evo.remove(f1, evo.TAG)
531+
assert(evo.has(e1, f1) and evo.get(e1, f1) == 44)
532+
for i = 1, ec do assert(evo.has(es[i], f1) and evo.get(es[i], f1) == 44) end
533+
assert(alloc_call_count == 2 and free_call_count == 1 and resize_call_count == 0)
534+
535+
evo.destroy(e1)
536+
for i = 1, ec do evo.destroy(es[i]) end
537+
assert(alloc_call_count == 2 and free_call_count == 1 and resize_call_count == 0)
538+
539+
evo.collect_garbage()
540+
assert(alloc_call_count == 2 and free_call_count == 2 and resize_call_count == 0)
541+
end
542+
543+
alloc_call_count, free_call_count, resize_call_count = 0, 0, 0
544+
545+
do
546+
local e1 = evo.builder():set(f1, 100):build()
547+
assert(evo.has(e1, f1) and evo.get(e1, f1) == 100)
548+
assert(alloc_call_count == 1 and free_call_count == 0 and resize_call_count == 0)
549+
550+
evo.set(f1, evo.TAG)
551+
assert(evo.has(e1, f1) and evo.get(e1, f1) == nil)
552+
assert(alloc_call_count == 1 and free_call_count == 1 and resize_call_count == 0)
553+
554+
local es, ec = evo.multi_spawn(20, { [f1] = 48 })
555+
for i = 1, ec do assert(evo.has(es[i], f1) and evo.get(es[i], f1) == nil) end
556+
assert(alloc_call_count == 1 and free_call_count == 1 and resize_call_count == 0)
557+
558+
evo.destroy(e1)
559+
for i = 1, ec do evo.destroy(es[i]) end
560+
assert(alloc_call_count == 1 and free_call_count == 1 and resize_call_count == 0)
561+
562+
evo.collect_garbage()
563+
assert(alloc_call_count == 1 and free_call_count == 1 and resize_call_count == 0)
564+
end
565+
end
566+
567+
568+
do
569+
local realloc = ctor_realloc()
570+
571+
local f1 = evo.builder():realloc(realloc):build()
572+
573+
alloc_call_count, free_call_count, resize_call_count = 0, 0, 0
574+
575+
do
576+
local e1 = evo.builder():set(f1, 42):build()
577+
assert(evo.has(e1, f1) and evo.get(e1, f1) == 42)
578+
assert(alloc_call_count == 1 and free_call_count == 0 and resize_call_count == 0)
579+
580+
evo.destroy(e1)
581+
assert(not evo.has(e1, f1) and evo.get(e1, f1) == nil)
582+
assert(alloc_call_count == 1 and free_call_count == 0 and resize_call_count == 0)
583+
584+
evo.set(f1, evo.TAG)
585+
assert(alloc_call_count == 1 and free_call_count == 1 and resize_call_count == 0)
586+
end
587+
end
588+
end

0 commit comments

Comments
 (0)