1 /**
2 Memory-pool-based dynamic arrays. Optimized for memory usage, can’t fragment the memory.
3 
4 Copyright:
5 Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.
6 Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md)
7 Copyright (c) 2017-2018 Godot-D contributors
8 Copyright (c) 2022-2022 Godot-DLang contributors
9 
10 License: $(LINK2 https://opensource.org/licenses/MIT, MIT License)
11 
12 
13 */
14 module godot.poolarrays;
15 
16 import godot.abi;
17 import godot.array;
18 import godot.api.types;
19 import godot.string;
20 import godot.color;
21 import godot.vector2;
22 import godot.vector3;
23 import godot.builtins;
24 
25 import std.range.primitives;
26 import std.meta, std.traits;
27 
28 private alias PackedArrayTypes = AliasSeq!(
29     ubyte,
30     int,
31     long,
32     float,
33     double,
34     String,
35     Vector2,
36     Vector3,
37     Color
38 );
39 
40 // used in GDNativeInterface.variant_get_ptr_destructor()
41 private alias PackedArrayVariantType = AliasSeq!(
42     GDNATIVE_VARIANT_TYPE_PACKED_BYTE_ARRAY,
43     GDNATIVE_VARIANT_TYPE_PACKED_INT32_ARRAY,
44     GDNATIVE_VARIANT_TYPE_PACKED_INT64_ARRAY,
45     GDNATIVE_VARIANT_TYPE_PACKED_FLOAT32_ARRAY,
46     GDNATIVE_VARIANT_TYPE_PACKED_FLOAT64_ARRAY,
47     GDNATIVE_VARIANT_TYPE_PACKED_STRING_ARRAY,
48     GDNATIVE_VARIANT_TYPE_PACKED_VECTOR2_ARRAY,
49     GDNATIVE_VARIANT_TYPE_PACKED_VECTOR3_ARRAY,
50     GDNATIVE_VARIANT_TYPE_PACKED_COLOR_ARRAY,
51 );
52 
53 private enum string nameOverride(T) = AliasSeq!(
54         "byte", "int32", "int64", "float32", "float64", "string",
55         "vector2", "vector3", "color")[staticIndexOf!(T, PackedArrayTypes)];
56 
57 private enum string bindNameOverride(T) = AliasSeq!(
58         "Byte", "Int32", "Int64", "Float32", "Float64", "String",
59         "Vector2", "Vector3", "Color")[staticIndexOf!(T, PackedArrayTypes)];
60 
61 private enum string typeName(T) = "packed_" ~ (nameOverride!T) ~ "_array";
62 private enum string readName(T) = "packed_" ~ (nameOverride!T) ~ "_array_operator_index_const";
63 private enum string writeName(T) = "packed_" ~ (nameOverride!T) ~ "_array_operator_index";
64 
65 alias PackedByteArray = PackedArray!ubyte;
66 alias PackedInt32Array = PackedArray!int;
67 alias PackedInt64Array = PackedArray!long;
68 alias PackedFloat32Array = PackedArray!float;
69 alias PackedFloat64Array = PackedArray!double;
70 alias PackedStringArray = PackedArray!String;
71 alias PackedVector2Array = PackedArray!Vector2;
72 //alias PackedVector2iArray = PackedArray!Vector2i;
73 alias PackedVector3Array = PackedArray!Vector3;
74 //alias PackedVector3iArray = PackedArray!Vector3i;
75 alias PackedColorArray = PackedArray!Color;
76 
77 /++
78 Copy-on-write array for some Godot types, allocated with a memory pool.
79 +/
80 struct PackedArray(T) {
81     //@nogc nothrow:
82 
83     static assert(staticIndexOf!(T, PackedArrayTypes) != -1,
84         "Cannot make a Godot PackedArray for a non-Godot type");
85 
86     // TODO: this is now gone, replace with real array
87     //mixin("package(godot) "~(typeName!T)~" _godot_array;");
88 
89     package(godot) union _PackedArray {
90         GDNativeTypePtr _godot_array;
91         mixin("Packed" ~ bindNameOverride!T ~ "Array_Bind _bind;");
92     }
93 
94     package(godot) _PackedArray _packed_array;
95     alias _packed_array this;
96 
97     alias VARIANT_TYPE = PackedArrayVariantType[staticIndexOf!(T, PackedArrayTypes)];
98 
99     this(this) {
100         import std.array;
101 
102         //mixin("auto n = _godot_api."~(typeName!T)~"_new_copy;");
103         auto ctor = _godot_api.variant_get_ptr_constructor(VARIANT_TYPE, 1);
104         const auto args = [_godot_array].staticArray;
105         ctor(_godot_array, args.ptr);
106 
107         //n(&_godot_array, &tmp);
108 
109     }
110 
111     package(godot) this(GDNativeTypePtr opaque) {
112         _godot_array = opaque;
113     }
114 
115     PackedArray opAssign(in PackedArray other) {
116         auto dtor = _godot_api.variant_get_ptr_destructor(VARIANT_TYPE);
117         auto ctor = _godot_api.variant_get_ptr_constructor(VARIANT_TYPE, 1);
118         dtor(&_godot_array);
119         ctor(&_godot_array, &other._godot_array);
120         return this;
121     }
122 
123     /++
124 	C API type to pass to/from C functions
125 	+/
126     static if (is(T == Vector2))
127         private alias InternalType = godot_vector2;
128     else static if (is(T == Vector3))
129         private alias InternalType = godot_vector3;
130     else static if (is(T == Color))
131         private alias InternalType = godot_color;
132     else
133         private alias InternalType = T;
134 
135     this(Array arr) {
136         auto n = _godot_api.variant_get_ptr_constructor(VARIANT_TYPE, 2);
137         n(&_godot_array, cast(void**)&arr._godot_array);
138     }
139 
140     ///
141     void pushBack(in ref PackedArray arr) {
142         _bind.appendArray(arr);
143         //mixin("auto a = _godot_api."~(typeName!T)~"_append_array;");
144         //a(&_godot_array, &arr._godot_array);
145     }
146 
147     deprecated("Use the concatenation operator ~= instead of append_array.") alias append_array = pushBack;
148 
149     void invert() {
150         _bind.reverse();
151         //mixin("auto i = _godot_api."~(typeName!T)~"_invert;");
152         //i(&_godot_array);
153     }
154 
155     void remove(size_t idx) {
156         _bind.removeAt(idx);
157         //mixin("auto r = _godot_api."~(typeName!T)~"_remove;");
158         //r(&_godot_array, cast(int)idx);
159     }
160 
161     void resize(size_t size) {
162         _bind.resize(size);
163         //mixin("auto r = _godot_api."~(typeName!T)~"_resize;");
164         //r(&_godot_array, cast(int)size);
165     }
166 
167     size_t size() const {
168         return _bind.size();
169         //mixin("auto s = _godot_api."~(typeName!T)~"_size;");
170         //return s(&_godot_array);
171     }
172 
173     alias length = size; // D-style name for size
174     alias opDollar = size;
175 
176     /// Returns: true if length is 0.
177     bool empty() const {
178         return length == 0;
179     }
180 
181     ~this() {
182         //auto d = _godot_api.variant_get_ptr_destructor(GDNATIVE_VARIANT_TYPE_PACKED_BYTE_ARRAY)
183         auto d = _godot_api.variant_get_ptr_destructor(VARIANT_TYPE);
184         d(&_godot_array);
185     }
186 
187     // a few functions are different for Strings than for the others:
188     //static if(is(T == String))
189     //{
190     //	void pushBack(in String data)
191     //	{
192     //		_godot_api.packed_string_array_push_back(&_godot_array, &data._godot_string);
193     //	}
194     //	void insert(size_t idx, in String data)
195     //	{
196     //		_godot_api.packed_string_array_insert(&_godot_array, cast(int)idx, &data._godot_string);
197     //	}
198     //	void set(size_t idx, in String data)
199     //	{
200     //		_godot_api.packed_string_array_operator_index(&_godot_array, cast(int)idx) = &data._godot_string;
201     //	}
202     //	void opIndexAssign(in String data, size_t idx)
203     //	{
204     //		_godot_api.packed_string_array_operator_index(&_godot_array, cast(int)idx) = &data._godot_string;
205     //	}
206     //	String opIndex(size_t idx) const
207     //	{
208     //		String ret = void;
209     //		ret._godot_string = godot_string(cast(size_t)  _godot_api.packed_string_array_operator_index_const(&_godot_array, cast(int)idx));
210     //		return ret;
211     //	}
212     //}
213     //else
214     //{
215     void pushBack(in T data) {
216         _bind.pushBack(data);
217         //mixin("auto p = _godot_api."~(typeName!T)~"_push_back;");
218         //static if(is(T==Vector2) || is(T==Vector3) || is(T==Color))
219         //	p(&_godot_array, cast(InternalType*)&data);
220         //else p(&_godot_array, data);
221     }
222 
223     void insert(size_t idx, in T data) {
224         _bind.insert(idx, data);
225         //mixin("auto i = _godot_api."~(typeName!T)~"_insert;");
226         //static if(is(T==Vector2) || is(T==Vector3) || is(T==Color))
227         //	i(&_godot_array, cast(int)idx, cast(InternalType*)&data);
228         //else i(&_godot_array, cast(int)idx, data);
229     }
230 
231     void set(size_t idx, in T data) {
232         _bind.set(idx, data);
233         //mixin("auto s = _godot_api."~(typeName!T)~"_set;");
234         //static if(is(T==Vector2) || is(T==Vector3) || is(T==Color))
235         //	s(&_godot_array, cast(int)idx, cast(InternalType*)&data);
236         //else s(&_godot_array, cast(int)idx, data);
237     }
238 
239     void opIndexAssign(in T data, size_t idx) {
240         _bind.set(idx, data);
241         //mixin("auto s = _godot_api."~(typeName!T)~"_set;");
242         //static if(is(T==Vector2) || is(T==Vector3) || is(T==Color))
243         //	s(&_godot_array, cast(int)idx, cast(InternalType*)&data);
244         //else s(&_godot_array, cast(int)idx, data);
245     }
246 
247     T opIndex(size_t idx) const {
248         mixin("auto g = _godot_api." ~ (typeName!T) ~ "_operator_index_const;");
249         static union V {
250             T t;
251             InternalType r;
252         }
253 
254         V v;
255         v.r = *cast(InternalType*) g(&_godot_array, cast(int) idx);
256         return v.t;
257     }
258     //}
259 
260     ///
261     alias append = pushBack;
262     ///
263     template opOpAssign(string op) if (op == "~" || op == "+") {
264         alias opOpAssign = pushBack;
265     }
266 
267     ///
268     PackedArray opBinary(string op)(in ref PackedArray other) const 
269             if (op == "~" || op == "+") {
270         PackedArray ret = this;
271         ret ~= other;
272         return ret;
273     }
274 
275     static if (is(T == String))
276         char* data() inout {
277             return cast(char*) _godot_array;
278         }
279     else
280         T* data() inout {
281             return cast(T*) _godot_array;
282         }
283 
284     // Superbelko: PoolVector was replaced by Vector, all PoolTypeArray's was replaced with PackedTypeArray 
285     //             which is simply Vector<Type> under the hood plus bells and whistles.
286     //             No need to keep this anymore, but ok. use raw pointer instead of Read.
287     /// Read/Write access locks with RAII.
288     version (none) static struct Access(bool write = false) {
289         private enum string rw = write ? "operator_index" : "operator_index_const";
290         private enum string RW = write ? "Write" : "Read";
291         static if (write)
292             private alias access = writeName!T;
293         else
294             private alias access = readName!T;
295 
296         private {
297             mixin(access ~ "* _access;");
298             T[] _data;
299         }
300 
301         static if (write) {
302             /// 
303             inout(T[]) data() inout {
304                 return _data;
305             }
306         } else {
307             /// 
308             const(T[]) data() const {
309                 return _data;
310             }
311         }
312         // TODO: `scope` for data to ensure it doesn't outlive `this`?
313         alias data this;
314 
315         this(PackedArray!T p) {
316             mixin("_access = _godot_api." ~ typeName!T ~ "_" ~ rw ~ "(&p._godot_array);");
317             mixin("void* _ptr = cast(void*)_godot_api." ~ access ~ "_ptr(_access);");
318             _data = (cast(T*) _ptr)[0 .. p.length];
319         }
320 
321         this(this) {
322             mixin("_access = _godot_api." ~ access ~ "_copy(_access);");
323         }
324 
325         void opAssign(const ref typeof(this) other) {
326             mixin("_godot_api." ~ access ~ "_destroy(_access);");
327             mixin("_access = _godot_api." ~ access ~ "_copy(other._access);");
328         }
329 
330         ~this() {
331             mixin("_godot_api." ~ access ~ "_destroy(_access);");
332         }
333     }
334 
335     version (none) {
336 
337         /// 
338         alias Read = Access!false;
339         /// Lock the array for read-only access to the underlying memory.
340         /// This is faster than using opIndex, which locks each time it's called.
341         Read read() const {
342             return Read(this);
343         }
344         /// 
345         alias Write = Access!true;
346         /// Lock the array for write access to the underlying memory.
347         /// This is faster than using opIndexAssign, which locks each time it's called.
348         Write write() {
349             return Write(this);
350         }
351     }
352 
353     /// Slice-like view of the PackedArray.
354     static struct Range {
355         private {
356             PackedArray* arr;
357             size_t start, end;
358         }
359 
360         bool empty() const {
361             return start == end;
362         }
363 
364         size_t length() const {
365             return end - start;
366         }
367 
368         alias opDollar = length;
369         T front() {
370             return (*arr)[start];
371         }
372 
373         void popFront() {
374             ++start;
375         }
376 
377         T back() {
378             return (*arr)[end - 1];
379         }
380 
381         void popBack() {
382             --end;
383         }
384 
385         T opIndex(size_t index) {
386             return (*arr)[index + start];
387         }
388 
389         Range save() {
390             return this;
391         }
392     }
393 
394     static assert(isRandomAccessRange!Range);
395 
396     /// Returns: a slice-like Range view over the array.
397     /// Note: Prefer `read()`/`write()`; Range locks the array on each individual access.
398     Range opSlice() {
399         return Range(&this, 0, length);
400     }
401     /// ditto
402     Range opSlice(size_t start, size_t end) {
403         return Range(&this, start, end);
404     }
405 }