190 lines
4.9 KiB
Text
190 lines
4.9 KiB
Text
// A dynamic array whose values will never move in memory.
|
|
//
|
|
// This means it is safe to take a pointer to a value within the array
|
|
// while continuing to append to it.
|
|
Stable_Array :: struct(T: Type, items_per_chunk := 32) {
|
|
allocator: Allocator;
|
|
chunks: [..]*Chunk;
|
|
count: int;
|
|
|
|
Chunk :: Static_Array(items_per_chunk, T);
|
|
}
|
|
|
|
append :: (a: *Stable_Array) -> *a.T {
|
|
chunk := find_or_create_chunk(a, 1);
|
|
a.count += 1;
|
|
return append(chunk);
|
|
}
|
|
|
|
append :: (a: *Stable_Array, value: a.T) -> *a.T {
|
|
chunk := find_or_create_chunk(a, 1);
|
|
item := append(chunk, value);
|
|
a.count += 1;
|
|
return item;
|
|
}
|
|
|
|
append :: (a: *Stable_Array, values: ..a.T) -> *a.T {
|
|
// @todo(judah): this should look for chunks where can just copy values directly
|
|
// rather than calling append for each one.
|
|
|
|
first: *a.T;
|
|
for values {
|
|
if first == null {
|
|
first = inline append(a, it);
|
|
}
|
|
else {
|
|
inline append(a, it);
|
|
}
|
|
}
|
|
|
|
return first;
|
|
}
|
|
|
|
reset :: (a: *Stable_Array) {
|
|
for a.chunks it.count = 0;
|
|
a.count = 0;
|
|
a.chunks.count = 0;
|
|
}
|
|
|
|
find :: (a: Stable_Array, $predicate: (a.T) -> bool) -> a.T, bool, int {
|
|
for a if inline predicate(it) return it, true, it_index;
|
|
return mem.undefined_of(a.T), false, -1;
|
|
}
|
|
|
|
find_pointer :: (a: *Stable_Array, $predicate: (a.T) -> bool) -> *a.T, bool, int {
|
|
for * a if inline predicate(it.*) return it, true, it_index;
|
|
return null, false, -1;
|
|
}
|
|
|
|
operator [] :: (a: Stable_Array, index: int, loc := #caller_location) -> a.T #no_abc {
|
|
cidx := index / a.items_per_chunk;
|
|
iidx := index % a.items_per_chunk;
|
|
meta.check_bounds(cidx, a.chunks.count, loc = loc);
|
|
meta.check_bounds(iidx, a.chunks[cidx].count, loc = loc);
|
|
return a.chunks[cidx].items[iidx];
|
|
}
|
|
|
|
operator *[] :: (a: *Stable_Array, index: int, loc := #caller_location) -> *a.T #no_abc {
|
|
cidx := index / a.items_per_chunk;
|
|
iidx := index % a.items_per_chunk;
|
|
meta.check_bounds(cidx, a.chunks.count, loc = loc);
|
|
meta.check_bounds(iidx, a.chunks[cidx].count, loc = loc);
|
|
return *a.chunks[cidx].items[iidx];
|
|
}
|
|
|
|
operator []= :: (a: *Stable_Array, index: int, value: a.T, loc := #caller_location) #no_abc {
|
|
cidx := index / a.items_per_chunk;
|
|
iidx := index % a.items_per_chunk;
|
|
meta.check_bounds(cidx, a.chunks.count, loc = loc);
|
|
meta.check_bounds(iidx, a.chunks[cidx].count, loc = loc);
|
|
a.chunks[cidx].items[iidx] = value;
|
|
}
|
|
|
|
for_expansion :: (a: Stable_Array, body: Code, flags: For_Flags) #expand {
|
|
for #v2 <=(flags & .REVERSE == .REVERSE) i: 0..a.count - 1 {
|
|
`it_index := i;
|
|
#if flags & .POINTER == .POINTER {
|
|
`it := *a[i];
|
|
}
|
|
else {
|
|
`it := a[i];
|
|
}
|
|
|
|
#insert,scope(body) body;
|
|
}
|
|
}
|
|
|
|
|
|
#scope_file;
|
|
|
|
mem :: #import "jc/memory";
|
|
meta :: #import "jc/meta";
|
|
|
|
find_or_create_chunk :: (a: *Stable_Array, amount: int) -> *a.Chunk {
|
|
if a.chunks.count == 0 {
|
|
return create_chunk(a);
|
|
}
|
|
|
|
last := a.chunks[a.chunks.count - 1];
|
|
if amount > a.items_per_chunk - last.count {
|
|
last = create_chunk(a);
|
|
}
|
|
|
|
return last;
|
|
}
|
|
|
|
create_chunk :: (a: *Stable_Array) -> *a.Chunk {
|
|
mem.lazy_set_allocator(a);
|
|
mem.lazy_set_allocator(*a.chunks);
|
|
|
|
chunk := mem.request_memory(a.Chunk,, allocator = a.allocator);
|
|
append(*a.chunks, chunk);
|
|
return chunk;
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------
|
|
// TESTS
|
|
// ----------------------------------------------------------
|
|
|
|
#if #exists(RUN_TESTS) #run {
|
|
test :: #import "jc/meta/test";
|
|
|
|
test.run("basic operations", t => {
|
|
a: Stable_Array(int, 4);
|
|
|
|
append(*a, 10, 20, 30, 40);
|
|
test.expect(t, a.count == 4);
|
|
test.expect(t, a.chunks.count == 1, "chunk count was %", a.chunks.count);
|
|
|
|
append(*a, 50);
|
|
test.expect(t, a.count == 5);
|
|
test.expect(t, a.chunks.count == 2, "chunk count was %", a.chunks.count);
|
|
|
|
append(*a, 60, 70, 80, 90, 100, 110, 120);
|
|
test.expect(t, a.count == 12);
|
|
test.expect(t, a.chunks.count == 3, "chunk count was %", a.chunks.count);
|
|
|
|
for a {
|
|
test.expect(t, it == (it_index + 1) * 10, "% was %", it, (it_index + 1) * 10);
|
|
}
|
|
});
|
|
|
|
test.run("iteration", t => {
|
|
a: Stable_Array(int);
|
|
append(*a, 10, 20, 30, 40);
|
|
|
|
last := 999;
|
|
for < a {
|
|
test.expect(t, it == (it_index + 1) * 10);
|
|
test.expect(t, it < last);
|
|
|
|
last = it;
|
|
}
|
|
|
|
for * a it.* = 1;
|
|
for a test.expect(t, it == 1);
|
|
|
|
ptr, ok, idx := find_pointer(*a, v => v == 1);
|
|
test.expect(t, ok);
|
|
test.expect(t, idx == 0);
|
|
test.expect(t, ptr == *a[0]);
|
|
|
|
a[a.count - 1] = -1;
|
|
|
|
_, ok, idx = find(a, v => v == -1);
|
|
test.expect(t, ok);
|
|
test.expect(t, idx == a.count - 1);
|
|
});
|
|
|
|
test.run("stability", t => {
|
|
a: Stable_Array(int, 1);
|
|
|
|
first := append(*a, 10);
|
|
addr := first.(u64);
|
|
for 0..10 append(*a, it * 10);
|
|
|
|
test.expect(t, first.(u64) == addr);
|
|
test.expect(t, first.* == 10);
|
|
});
|
|
}
|