Skip to content

Commit 5b69733

Browse files
authored
Merge pull request #18 from vibe-d/vector
Add a simple vector implementation
2 parents ca0b4c5 + 89bb72a commit 5b69733

File tree

2 files changed

+201
-2
lines changed

2 files changed

+201
-2
lines changed

source/vibe/container/internal/rctable.d

+3-2
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ struct RCTable(T, Allocator = IAllocator) {
4646
~this()
4747
@trusted {
4848
if (m_table.ptr && --this.refCount == 0) {
49-
static if (hasIndirections!T) {
49+
static if (hasIndirections!T && !is(Allocator == GCAllocator)) {
5050
if (m_table.ptr !is null) () @trusted {
5151
GC.removeRange(m_table.ptr);
5252
}();
@@ -85,7 +85,8 @@ struct RCTable(T, Allocator = IAllocator) {
8585
assert(cast(size_t)cast(void*)m_table.ptr % T.alignof == 0);
8686
this.refCount = 1;
8787
} catch (Exception e) assert(false, e.msg);
88-
static if (hasIndirections!T) GC.addRange(m_table.ptr, m_table.length * T.sizeof);
88+
89+
static if (hasIndirections!T && !is(Allocator == GCAllocator)) GC.addRange(m_table.ptr, m_table.length * T.sizeof);
8990
}
9091

9192
/// Deallocates without running destructors

source/vibe/container/vector.d

+198
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
module vibe.container.vector;
2+
3+
import vibe.container.internal.rctable;
4+
import vibe.container.internal.utilallocator;
5+
6+
import std.algorithm.comparison : max;
7+
import std.algorithm.mutation : swap;
8+
9+
10+
/** Represents a deterministically allocated vector/array type.
11+
12+
The underlying buffer is allocated in powers of two and uses copy-on-write
13+
to enable value semantics without requiring to copy data when passing
14+
copies of the vector around.
15+
*/
16+
struct Vector(T, Allocator = GCAllocator)
17+
{
18+
private {
19+
alias Table = RCTable!(T, Allocator);
20+
Table m_table;
21+
size_t m_length;
22+
}
23+
24+
static if (!is(typeof(Allocator.instance))) {
25+
this(Allocator allocator)
26+
{
27+
m_table = Table(allocator);
28+
}
29+
}
30+
31+
32+
/// Determines whether the vector is currently empty.
33+
bool empty() const { return m_length == 0; }
34+
35+
/** The current number of elements.
36+
37+
Note that reducing the length of a vector will not free the underlying
38+
buffer, but instead will only make use of a smaller portion. This
39+
enables increasing the length later without having to re-allocate.
40+
*/
41+
size_t length() const { return m_length; }
42+
/// ditto
43+
void length(size_t count)
44+
@safe {
45+
if (count == m_length) return;
46+
47+
if (count <= m_table.length) {
48+
if (count < m_length) {
49+
if (() @trusted { return !m_table.isUnique(); } ()) {
50+
auto new_table = () @trusted { return m_table.createNew(allocationCount(count)); } ();
51+
new_table[0 .. count] = m_table[0 .. count];
52+
swap(m_table, new_table);
53+
} else m_table[count .. m_length] = T.init;
54+
}
55+
} else {
56+
auto new_table = () @trusted { return m_table.createNew(allocationCount(count)); } ();
57+
new_table[0 .. m_length] = m_table[0 .. m_length];
58+
swap(m_table, new_table);
59+
}
60+
61+
assert(count <= m_table.length, "Resized table not large enough for requested length!?");
62+
m_length = count;
63+
}
64+
65+
/// Appends elements to the end of the vector
66+
void insertBack(T element)
67+
{
68+
makeUnique();
69+
auto idx = m_length;
70+
length = idx + 1;
71+
swap(m_table[idx], element);
72+
}
73+
/// ditto
74+
void insertBack(T[] elements)
75+
{
76+
if (elements.length == 0) return;
77+
78+
makeUnique();
79+
auto idx = m_length;
80+
length = idx + elements.length;
81+
foreach (i, ref el; elements)
82+
m_table[idx + i] = el;
83+
}
84+
/// ditto
85+
void opOpAssign(string op = "~")(T element) { insertBack(element); }
86+
/// ditto
87+
void opOpAssign(string op = "~")(T[] elements) { insertBack(elements); }
88+
89+
static if (is(typeof((const T x) { T y; y = x; }))) {
90+
/// ditto
91+
void insertBack(const(T)[] elements) {
92+
if (elements.length == 0) return;
93+
94+
makeUnique();
95+
auto idx = m_length;
96+
length = idx + elements.length;
97+
foreach (i, ref el; elements)
98+
m_table[idx + i] = el;
99+
}
100+
/// ditto
101+
void opOpAssign(string op = "~")(const(T)[] elements) { insertBack(elements); }
102+
}
103+
104+
/// Removes the last element of the vector
105+
void removeBack()
106+
{
107+
assert(length >= 1, "Attempt to remove element from empty vector");
108+
length = length - 1;
109+
}
110+
111+
/** Accesses the element at the given index.
112+
113+
Note that accessing an alement of a non-const vector will trigger the
114+
copy-on-write logic and may allocate, whereas accessing an element of
115+
a `const` vector will not.
116+
*/
117+
ref const(T) opIndex(size_t index) const return { return m_table[index]; }
118+
/// ditto
119+
ref T opIndex(size_t index) return { makeUnique(); return m_table[index]; }
120+
121+
/** Accesses a slice of elements.
122+
123+
Note that accessing an alement of a non-const vector will trigger the
124+
copy-on-write logic and may allocate, whereas accessing an element of
125+
a `const` vector will not.
126+
*/
127+
const(T)[] opSlice(size_t from, size_t to) const return { return m_table[from .. to]; }
128+
/// ditto
129+
T[] opSlice(size_t from, size_t to) return { makeUnique(); return m_table[from .. to]; }
130+
131+
/** Returns a slice of all elements of the vector.
132+
133+
Note that accessing an alement of a non-const vector will trigger the
134+
copy-on-write logic and may allocate, whereas accessing an element of
135+
a `const` vector will not.
136+
*/
137+
const(T)[] opSlice() const return { return m_table[0 .. length]; }
138+
/// ditto
139+
T[] opSlice() return { makeUnique(); return m_table[0 .. length]; }
140+
141+
static size_t allocationCount(size_t count)
142+
{
143+
return nextPOT(max(count, 16, 1024/T.sizeof));
144+
}
145+
146+
private void makeUnique()
147+
@trusted {
148+
if (!m_table.isUnique())
149+
m_table = m_table.dup;
150+
}
151+
}
152+
153+
154+
@safe nothrow unittest {
155+
Vector!int v;
156+
assert(v.length == 0);
157+
v.length = 1;
158+
assert(v.length == 1);
159+
assert(v[0] == 0);
160+
v[0] = 2;
161+
assert(v[0] == 2);
162+
v ~= 3;
163+
assert(v.length == 2);
164+
assert(v[1] == 3);
165+
166+
const w = v;
167+
assert(w.length == 2);
168+
assert(w[] == [2, 3]);
169+
assert(w[].ptr is (cast(const)v)[].ptr);
170+
171+
v.length = 3;
172+
assert(v.length == 3);
173+
assert(w.length == 2);
174+
assert(w[].ptr is (cast(const)v)[].ptr);
175+
176+
v[0] = 2;
177+
assert(w[].ptr !is (cast(const)v)[].ptr);
178+
assert(w[0] == 2);
179+
}
180+
181+
private size_t nextPOT(size_t n) @safe nothrow @nogc
182+
{
183+
foreach_reverse (i; 0 .. size_t.sizeof*8) {
184+
size_t ni = cast(size_t)1 << i;
185+
if (n & ni) {
186+
return n & (ni-1) ? ni << 1 : ni;
187+
}
188+
}
189+
return 1;
190+
}
191+
192+
unittest {
193+
assert(nextPOT(1) == 1);
194+
assert(nextPOT(2) == 2);
195+
assert(nextPOT(3) == 4);
196+
assert(nextPOT(4) == 4);
197+
assert(nextPOT(5) == 8);
198+
}

0 commit comments

Comments
 (0)