diff --git a/examples/sokol/particles/modules/particle/system.v b/examples/sokol/particles/modules/particle/system.v index 51874aa527..c319e61771 100644 --- a/examples/sokol/particles/modules/particle/system.v +++ b/examples/sokol/particles/modules/particle/system.v @@ -19,8 +19,8 @@ mut: } pub fn (mut s System) init(sc SystemConfig) { - unsafe { s.pool.flags.set(.noslices) } - unsafe { s.bin.flags.set(.noslices) } + unsafe { s.pool.flags.set(.noslices | .noshrink) } + unsafe { s.bin.flags.set(.noslices | .noshrink) } for i := 0; i < sc.pool; i++ { p := new(vec2.Vec2{f32(s.width) * 0.5, f32(s.height) * 0.5}) s.bin << p @@ -29,12 +29,19 @@ pub fn (mut s System) init(sc SystemConfig) { pub fn (mut s System) update(dt f64) { mut p := &Particle(0) + mut moved := 0 for i := 0; i < s.pool.len; i++ { p = s.pool[i] p.update(dt) if p.is_dead() { s.bin << p s.pool.delete(i) + moved++ + } + } + $if trace_moves_spool_to_sbin ? { + if moved != 0 { + eprintln('${moved:4} particles s.pool -> s.bin') } } } @@ -64,6 +71,7 @@ pub fn (mut s System) explode(x f32, y f32) { mut reserve := 500 center := vec2.Vec2{x, y} mut p := &Particle(0) + mut moved := 0 for i := 0; i < s.bin.len && reserve > 0; i++ { p = s.bin[i] p.reset() @@ -75,8 +83,14 @@ pub fn (mut s System) explode(x f32, y f32) { p.life_time = rand.f64_in_range(500, 2000) or { 500 } s.pool << p s.bin.delete(i) + moved++ reserve-- } + $if trace_moves_sbin_to_spool ? { + if moved != 0 { + eprintln('${moved:4} particles s.bin -> s.pool') + } + } } pub fn (mut s System) free() { diff --git a/vlib/builtin/array.v b/vlib/builtin/array.v index e29e50d478..e6253d5eb3 100644 --- a/vlib/builtin/array.v +++ b/vlib/builtin/array.v @@ -22,7 +22,8 @@ pub mut: [flag] pub enum ArrayFlags { - noslices + noslices // when <<, `.noslices` will free the old data block immediately (you have to be sure, that there are *no slices* to that specific array). TODO: integrate with reference counting/compiler support for the static cases. + noshrink // when `.noslices` and `.noshrink` are *both set*, .delete(x) will NOT allocate new memory and free the old. It will just move the elements in place, and adjust .len. } // Internal function, used by V (`nums := []int`) @@ -283,6 +284,14 @@ pub fn (mut a array) delete_many(i int, size int) { panic('array.delete: index out of range (i == $i$endidx, a.len == $a.len)') } } + if a.flags.all(.noshrink | .noslices) { + unsafe { + vmemmove(&byte(a.data) + i * a.element_size, &byte(a.data) + (i + size) * a.element_size, + (a.len - i - size) * a.element_size) + } + a.len -= size + return + } // Note: if a is [12,34], a.len = 2, a.delete(0) // should move (2-0-1) elements = 1 element (the 34) forward old_data := a.data @@ -441,6 +450,8 @@ fn (a array) slice(start int, _end int) array { panic('array.slice: slice bounds out of range ($start < 0)') } } + // TODO: integrate reference counting + // a.flags.clear(.noslices) offset := start * a.element_size data := unsafe { &byte(a.data) + offset } l := end - start @@ -461,6 +472,7 @@ fn (a array) slice(start int, _end int) array { // that get the last 3 elements of the array otherwise it return an empty array. // This function always return a valid array. fn (a array) slice_ni(_start int, _end int) array { + // a.flags.clear(.noslices) mut end := _end mut start := _start diff --git a/vlib/builtin/array_shrinkage_test.v b/vlib/builtin/array_shrinkage_test.v new file mode 100644 index 0000000000..84893fe864 --- /dev/null +++ b/vlib/builtin/array_shrinkage_test.v @@ -0,0 +1,57 @@ +fn show_array(name string, a []int) { + eprintln('${name:10} .flags: ${a.flags:34} | .cap: ${a.cap:2} | .len: ${a.len:2} | .data: $a.data | $a') +} + +fn trace_delete_elements(name string, mut a []int) int { + a.delete_many(5, 3) + show_array(name, a) + a << 55 + show_array(name, a) + a << 66 + show_array(name, a) + a << 77 + res := a.cap + eprintln(' << ${name:10} .cap: $a.cap >>') + show_array(name, a) + a << 88 + show_array(name, a) + a << 99 + show_array(name, a) + eprintln('-------------------------------') + return res +} + +fn test_array_cap_shrinkage_after_deletion() { + mut a := [0] + mut middle_cap := 0 + + a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + middle_cap = trace_delete_elements('normal', mut a) + assert middle_cap == 14 + assert a.len == 12 + assert a.cap == 14 + + a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + unsafe { a.flags.set(.noslices) } + middle_cap = dump(trace_delete_elements('noslices', mut a)) + assert middle_cap == 14 + assert a.len == 12 + assert a.cap == 14 + + a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + unsafe { a.flags.set(.noshrink) } + middle_cap = dump(trace_delete_elements('noshrink', mut a)) + assert middle_cap == 14 + assert a.len == 12 + assert a.cap == 14 + + // Note: when *both* flags are set, the memory block for the array + // should NOT shrink on deleting array elements, thus << after the + // deletion, will still have space (till .cap is reached). + a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + unsafe { a.flags.set(.noslices | .noshrink) } + middle_cap = dump(trace_delete_elements('both', mut a)) + assert middle_cap == 10 + assert a.len == 12 + assert a.cap == 20 +}