1 /**
2  * Optimized copying appender, no chaining
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <vladimir@thecybershadow.net>
12  */
13 
14 module ae.utils.appender;
15 
16 import core.memory;
17 import std.traits;
18 
19 struct FastAppender(I)
20 {
21 	static assert(T.sizeof == 1, "TODO");
22 
23 private:
24 	enum PAGE_SIZE = 4096;
25 	enum MIN_SIZE  = PAGE_SIZE / 2 + 1; // smallest size that can expand
26 
27 	alias Unqual!I T;
28 
29 	T* cursor, start, end;
30 
31 	void reserve(size_t len)
32 	{
33 		auto size = cursor-start;
34 		auto newSize = size + len;
35 		auto capacity = end-start;
36 
37 		if (start)
38 		{
39 			auto extended = GC.extend(start, newSize, newSize * 2);
40 			if (extended)
41 			{
42 				end = start + extended;
43 				return;
44 			}
45 		}
46 
47 		auto newCapacity = newSize < MIN_SIZE ? MIN_SIZE : newSize * 2;
48 
49 		auto bi = GC.qalloc(newCapacity * T.sizeof, (typeid(T[]).next.flags & 1) ? 0 : GC.BlkAttr.NO_SCAN);
50 		auto newStart = cast(T*)bi.base;
51 		newCapacity = bi.size;
52 
53 		newStart[0..size] = start[0..size];
54 		start = newStart;
55 		cursor = start + size;
56 		end = start + newCapacity;
57 	}
58 
59 public:
60 	/// Preallocate
61 	this(size_t capacity)
62 	{
63 		reserve(capacity);
64 	}
65 
66 	/// Start with a given buffer
67 	this(I[] arr)
68 	{
69 		start = cursor = cast(T*)arr.ptr;
70 		end = start + arr.length;
71 	}
72 
73 	void put(U...)(U items)
74 		if (CanPutAll!U)
75 	{
76 		// TODO: check for static if length is 1
77 		auto cursorEnd = cursor;
78 		foreach (item; items)
79 			static if (is(typeof(cursor[0] = item)))
80 				cursorEnd++;
81 			else
82 			// TODO: is this too lax? it allows passing static arrays by value
83 			static if (is(typeof(cursor[0..1] = item[0..1])))
84 				cursorEnd += item.length;
85 			else
86 				static assert(0, "Can't put " ~ typeof(item).stringof);
87 
88 		if (cursorEnd > end)
89 		{
90 			auto len = cursorEnd - cursor;
91 			reserve(len);
92 			cursorEnd = cursor + len;
93 		}
94 		auto cursor = this.cursor;
95 		this.cursor = cursorEnd;
96 
97 		static if (items.length == 1)
98 		{
99 			alias items[0] item;
100 			static if (is(typeof(cursor[0] = item)))
101 				cursor[0] = item;
102 			else
103 				cursor[0..item.length] = item[];
104 		}
105 		else
106 		{
107 			foreach (item; items)
108 				static if (is(typeof(cursor[0] = item)))
109 					*cursor++ = item;
110 				else
111 				static if (is(typeof(cursor[0..1] = item[0..1])))
112 				{
113 					cursor[0..item.length] = item[];
114 					cursor += item.length;
115 				}
116 		}
117 	}
118 
119 	/// Unsafe. Use together with preallocate().
120 	void uncheckedPut(U...)(U items)
121 		if (CanPutAll!U)
122 	{
123 		auto cursor = this.cursor;
124 
125 		foreach (item; items)
126 			static if (is(typeof(cursor[0] = item)))
127 				*cursor++ = item;
128 			else
129 			static if (is(typeof(cursor[0..1] = item[0..1])))
130 			{
131 				cursor[0..item.length] = item;
132 				cursor += item.length;
133 			}
134 
135 		this.cursor = cursor;
136 	}
137 
138 	void preallocate(size_t len)
139 	{
140 		if (end - cursor < len)
141 			reserve(len);
142 	}
143 
144 	T[] allocate(size_t len)
145 	{
146 		auto cursorEnd = cursor + len;
147 		if (cursorEnd > end)
148 		{
149 			reserve(len);
150 			cursorEnd = cursor + len;
151 		}
152 		auto result = cursor[0..len];
153 		cursor = cursorEnd;
154 		return result;
155 	}
156 
157 	template CanPutAll(U...)
158 	{
159 		static if (U.length==0)
160 			enum bool CanPutAll = true;
161 		else
162 		{
163 			enum bool CanPutAll =
164 				(
165 					is(typeof(cursor[0   ] = U[0].init      )) ||
166 				 	is(typeof(cursor[0..1] = U[0].init[0..1]))
167 				) && CanPutAll!(U[1..$]);
168 		}
169 	}
170 
171 	void opOpAssign(string op, U)(U item)
172 		if (op=="~" && is(typeof(put!U)))
173 	{
174 		put(item);
175 	}
176 
177 	I[] get()
178 	{
179 		return cast(I[])start[0..cursor-start];
180 	}
181 
182 	@property size_t length()
183 	{
184 		return cursor-start;
185 	}
186 
187 	static if (is(I == T)) // mutable types only
188 	{
189 		/// Does not resize. Use preallocate for that.
190 		@property void length(size_t value)
191 		{
192 			cursor = start + value;
193 			assert(cursor <= end);
194 		}
195 
196 		/// Effectively empties the data, but preserves the storage for reuse.
197 		/// Same as setting length to 0.
198 		void clear()
199 		{
200 			cursor = start;
201 		}
202 	}
203 }