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