1 /*
2  * DQt - D bindings for the Qt Toolkit
3  *
4  * GNU Lesser General Public License Usage
5  * This file may be used under the terms of the GNU Lesser
6  * General Public License version 3 as published by the Free Software
7  * Foundation and appearing in the file LICENSE.LGPL3 included in the
8  * packaging of this file. Please review the following information to
9  * ensure the GNU Lesser General Public License version 3 requirements
10  * will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
11  */
12 module qt.core.arraydatapointer;
13 extern(C++):
14 
15 import qt.config;
16 import qt.core.arraydata;
17 import qt.core.arraydataops;
18 import qt.core.global;
19 import qt.core.pair;
20 import qt.core.typeinfo;
21 import qt.helpers;
22 
23 struct QArrayDataPointer(T)
24 {
25 private:
26     alias Data = QTypedArrayData!(T);
27     alias DataOps = QArrayDataOps!(T);
28 
29 public:
30 /+    enum {
31         pass_parameter_by_value =
32                 /+ std:: +/is_arithmetic!(T).value || /+ std:: +/is_pointer!(T).value || /+ std:: +/is_enum!(T).value
33     }
34 
35     }+/
36 
37 //    alias parameter_type = /+ std:: +/conditional!(pass_parameter_by_value, T, ref const(T)).type;
38     alias parameter_type = T; // TODO: ref
39 
40     /+this()/+ noexcept+/
41     {
42         this.d = null;
43         this.ptr = null;
44         this.size = 0;
45     }+/
46 
47     /*this(ref const(QArrayDataPointer) other)/+ noexcept+/
48     {
49         this.d = other.d;
50         this.ptr = other.ptr;
51         this.size = other.size;
52 
53         ref_();
54     }*/
55     this(this)
56     {
57         ref_();
58     }
59 
60     this(Data* header, T* adata, qsizetype n = 0)/+ noexcept+/
61     {
62         this.d = header;
63         this.ptr = adata;
64         this.size = n;
65     }
66 
67     /+ explicit +/this(qt.core.pair.QPair!(QTypedArrayData!(T)*, T*) adata, qsizetype n = 0)/+ noexcept+/
68     {
69         this.d = adata.first;
70         this.ptr = adata.second;
71         this.size = n;
72     }
73 
74     static QArrayDataPointer fromRawData(const(T)* rawData, qsizetype length)/+ noexcept+/
75     {
76         (mixin(Q_ASSERT(q{rawData || !length})));
77         return QArrayDataPointer( null, const_cast!(T*)(rawData), length) ;
78     }
79 
80     /+ref QArrayDataPointer operator =(ref const(QArrayDataPointer) other)/+ noexcept+/
81     {
82         auto tmp = QArrayDataPointer(other);
83         this.swap(tmp);
84         return this;
85     }+/
86 
87     /+ QArrayDataPointer(QArrayDataPointer &&other) noexcept
88         : d(other.d), ptr(other.ptr), size(other.size)
89     {
90         other.d = nullptr;
91         other.ptr = nullptr;
92         other.size = 0;
93     } +/
94 
95     /+ QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_MOVE_AND_SWAP(QArrayDataPointer) +/
96 
97     /*ref DataOps opUnary(string op)()/+ noexcept+/ if(op == "*")
98     {
99         return *static_cast!(DataOps*)(&this);
100     }*/
101 
102     /+DataOps* operator ->()/+ noexcept+/
103     {
104         return static_cast!(DataOps*)(&this);
105     }+/
106 
107     /*ref const(DataOps) opUnary(string op)() const/+ noexcept+/ if(op == "*")
108     {
109         return *static_cast!(const(DataOps)*)(&this);
110     }*/
111 
112     /+const(DataOps)* operator ->() const/+ noexcept+/
113     {
114         return static_cast!(const(DataOps)*)(&this);
115     }+/
116 
117     ~this()
118     {
119         if (!deref()) {
120             DataOps.destroyAll(this);
121             Data.deallocate(cast(QArrayData*)d);
122         }
123     }
124 
125     bool isNull() const/+ noexcept+/
126     {
127         return !ptr;
128     }
129 
130     T* data()/+ noexcept+/ { return ptr; }
131     const(T)* data() const/+ noexcept+/ { return ptr; }
132 
133     T* begin()/+ noexcept+/ { return data(); }
134     T* end()/+ noexcept+/ { return data() + size; }
135     const(T)* begin() const/+ noexcept+/ { return data(); }
136     const(T)* end() const/+ noexcept+/ { return data() + size; }
137     const(T)* constBegin() const/+ noexcept+/ { return data(); }
138     const(T)* constEnd() const/+ noexcept+/ { return data() + size; }
139 
140     void swap(ref QArrayDataPointer other) /+ noexcept +/
141     {
142         import std.algorithm;
143         std.algorithm.swap(d, other.d);
144         std.algorithm.swap(ptr, other.ptr);
145         std.algorithm.swap(size, other.size);
146     }
147 
148     void clear()/+ noexcept(is_nothrow_destructible<T>.value)+/
149     {
150         QArrayDataPointer tmp;
151         swap(tmp);
152     }
153 
154     void detach()(QArrayDataPointer* old = null)
155     {
156         if (needsDetach())
157             reallocateAndGrow(QArrayData.GrowthPosition.GrowsAtEnd, 0, old);
158     }
159 
160     /*! \internal
161 
162         Detaches this (optionally) and grows to accommodate the free space for
163         \a n elements at the required side. The side is determined from \a pos.
164 
165         \a data pointer can be provided when the caller knows that \a data
166         points into range [this->begin(), this->end()). In case it is, *data
167         would be updated so that it continues to point to the element it was
168         pointing to before the data move. if \a data does not point into range,
169         one can/should pass \c nullptr.
170 
171         Similarly to \a data, \a old, pointer to a default-constructed QADP, can
172         be provided when the caller expects to e.g. copy the data from this to
173         itself:
174         \code
175         QList<T> list(5);
176         qsizetype pos = getArbitraryPos();
177         list.insert(pos, list.begin(), list.end());
178         \endcode
179 
180         The default rule would be: \a data and \a old must either both be valid
181         pointers, or both equal to \c nullptr.
182     */
183     void detachAndGrow()(QArrayData.GrowthPosition where, qsizetype n, const(T)** data,
184                            QArrayDataPointer* old)
185     {
186         const(bool) detach = needsDetach();
187         bool readjusted = false;
188         if (!detach) {
189             if (!n || (where == QArrayData.GrowthPosition.GrowsAtBeginning && freeSpaceAtBegin() >= n)
190                 || (where == QArrayData.GrowthPosition.GrowsAtEnd && freeSpaceAtEnd() >= n))
191                 return;
192             readjusted = tryReadjustFreeSpace(where, n, data);
193             (mixin(Q_ASSERT(q{!readjusted
194                          || (where == QArrayData.GrowthPosition.GrowsAtBeginning && freeSpaceAtBegin() >= n)
195                          || (where == QArrayData.GrowthPosition.GrowsAtEnd && freeSpaceAtEnd() >= n)})));
196         }
197 
198         if (!readjusted)
199             reallocateAndGrow(where, n, old);
200     }
201 
202     /*! \internal
203 
204         Reallocates to accommodate the free space for \a n elements at the
205         required side. The side is determined from \a pos. Might also shrink
206         when n < 0.
207     */
208     /+ Q_NEVER_INLINE +/ void reallocateAndGrow()(QArrayData.GrowthPosition where, qsizetype n,
209                                               QArrayDataPointer* old = null)
210     {
211         static if (QTypeInfo!(T).isRelocatable && T.alignof <= 8/* /+ std:: +/max_align_t.alignof*/) {
212             if (where == QArrayData.GrowthPosition.GrowsAtEnd && !old && !needsDetach() && n > 0) {
213                 DataOps.reallocate(this, constAllocatedCapacity() - freeSpaceAtEnd() + n, QArrayData.AllocationOption.Grow); // fast path
214                 return;
215             }
216         }
217 
218         auto dp = /*QArrayDataPointer*/(allocateGrow(this, n, where));
219         if (n > 0)
220             mixin(Q_CHECK_PTR(q{dp.data()}));
221         if (where == QArrayData.GrowthPosition.GrowsAtBeginning) {
222             (mixin(Q_ASSERT(q{dp.freeSpaceAtBegin() >= n})));
223         } else {
224             (mixin(Q_ASSERT(q{dp.freeSpaceAtEnd() >= n})));
225         }
226         if (size) {
227             qsizetype toCopy = size;
228             if (n < 0)
229                 toCopy += n;
230             if (needsDetach() || old)
231                 DataOps.copyAppend(dp, begin(), begin() + toCopy);
232             else
233                 DataOps.moveAppend(dp, begin(), begin() + toCopy);
234             (mixin(Q_ASSERT(q{dp.size == toCopy})));
235         }
236 
237         swap(dp);
238         if (old)
239             old.swap(dp);
240     }
241 
242     /*! \internal
243 
244         Attempts to relocate [begin(), end()) to accommodate the free space for
245         \a n elements at the required side. The side is determined from \a pos.
246 
247         Returns \c true if the internal data is moved. Returns \c false when
248         there is no point in moving the data or the move is impossible. If \c
249         false is returned, it is the responsibility of the caller to figure out
250         how to accommodate the free space for \a n elements at \a pos.
251 
252         This function expects that certain preconditions are met, e.g. the
253         detach is not needed, n > 0 and so on. This is intentional to reduce the
254         number of if-statements when the caller knows that preconditions would
255         be satisfied.
256 
257         \sa reallocateAndGrow
258     */
259     bool tryReadjustFreeSpace(QArrayData.GrowthPosition pos, qsizetype n, const(T)** data = null)
260     {
261         (mixin(Q_ASSERT(q{!this.needsDetach()})));
262         (mixin(Q_ASSERT(q{n > 0})));
263         (mixin(Q_ASSERT(q{(pos == QArrayData.GrowthPosition.GrowsAtEnd && this.freeSpaceAtEnd() < n)
264                      || (pos == QArrayData.GrowthPosition.GrowsAtBeginning && this.freeSpaceAtBegin() < n)})));
265 
266         const(qsizetype) capacity = this.constAllocatedCapacity();
267         const(qsizetype) freeAtBegin = this.freeSpaceAtBegin();
268         const(qsizetype) freeAtEnd = this.freeSpaceAtEnd();
269 
270         qsizetype dataStartOffset = 0;
271         // algorithm:
272         //   a. GrowsAtEnd: relocate if space at begin AND size < (capacity * 2) / 3
273         //      [all goes to free space at end]:
274         //      new free space at begin = 0
275         //
276         //   b. GrowsAtBeginning: relocate if space at end AND size < capacity / 3
277         //      [balance the free space]:
278         //      new free space at begin = n + (total free space - n) / 2
279         if (pos == QArrayData.GrowthPosition.GrowsAtEnd && freeAtBegin >= n
280             && ((3 * this.size) < (2 * capacity))) {
281             // dataStartOffset = 0; - done in declaration
282         } else if (pos == QArrayData.GrowthPosition.GrowsAtBeginning && freeAtEnd >= n
283                    && ((3 * this.size) < capacity)) {
284             // total free space == capacity - size
285             dataStartOffset = n + qMax(0, (capacity - this.size - n) / 2);
286         } else {
287             // nothing to do otherwise
288             return false;
289         }
290 
291         relocate(dataStartOffset - freeAtBegin, data);
292 
293         (mixin(Q_ASSERT(q{(pos == QArrayData.GrowthPosition.GrowsAtEnd && this.freeSpaceAtEnd() >= n)
294                      || (pos == QArrayData.GrowthPosition.GrowsAtBeginning && this.freeSpaceAtBegin() >= n)})));
295         return true;
296     }
297 
298     /*! \internal
299 
300         Relocates [begin(), end()) by \a offset and updates \a data if it is not
301         \c nullptr and points into [begin(), end()).
302     */
303     void relocate(qsizetype offset, const(T)** data = null)
304     {
305         //import qt.core.containertools_impl;
306 
307         T* res = this.ptr + offset;
308         // /+ QtPrivate:: +/qt.core.containertools_impl.q_relocate_overlap_n(this.ptr, this.size, res);
309         if (offset > 0) {
310             foreach_reverse (i; 0..this.size) {
311                 this.ptr[i + offset] = this.ptr[i];
312             }
313         }
314         else {
315             foreach (i; 0..this.size) {
316                 this.ptr[i + offset] = this.ptr[i];
317             }
318         }
319         // first update data pointer, then this->ptr
320         if (data && *data >= this.begin() && *data < this.end())
321             *data += offset;
322         this.ptr = res;
323     }
324 
325     // forwards from QArrayData
326     qsizetype allocatedCapacity()/+ noexcept+/ { return d ? d.allocatedCapacity() : 0; }
327     qsizetype constAllocatedCapacity() const/+ noexcept+/ { return d ? d.constAllocatedCapacity() : 0; }
328     void ref_()/+ noexcept+/ { if (d) d.ref_(); }
329     bool deref()/+ noexcept+/ { return !d || d.deref(); }
330     bool isMutable() const/+ noexcept+/ { return cast(bool)(d); }
331     bool isShared() const/+ noexcept+/ { return !d || d.isShared(); }
332     bool isSharedWith(ref const(QArrayDataPointer) other) const/+ noexcept+/ { return d && d == other.d; }
333     bool needsDetach() const/+ noexcept+/ { return !d || d.needsDetach(); }
334     qsizetype detachCapacity(qsizetype newSize) const/+ noexcept+/ { return d ? d.detachCapacity(newSize) : newSize; }
335     const(Data.ArrayOptions) flags() const/+ noexcept+/ { return d ? d.flags : Data.ArrayOptions.ArrayOptionDefault; }
336     void setFlag(Data.ArrayOptions f)/+ noexcept+/ { assert(d); d.flags |= f; }
337     void clearFlag(Data.ArrayOptions f)/+ noexcept+/ { if (d) d.flags &= ~f; }
338 
339     Data* d_ptr()/+ noexcept+/ { return d; }
340     void setBegin(T* begin)/+ noexcept+/ { ptr = begin; }
341 
342     qsizetype freeSpaceAtBegin() const/+ noexcept+/
343     {
344         if (d is null)
345             return 0;
346         return this.ptr - Data.dataStart(cast(QArrayData*)d, Data.AlignmentDummy.alignof);
347     }
348 
349     qsizetype freeSpaceAtEnd() const/+ noexcept+/
350     {
351         if (d is null)
352             return 0;
353         return d.constAllocatedCapacity() - freeSpaceAtBegin() - this.size;
354     }
355 
356     // allocate and grow. Ensure that at the minimum requiredSpace is available at the requested end
357     static QArrayDataPointer allocateGrow(ref const(QArrayDataPointer) from, qsizetype n, QArrayData.GrowthPosition position)
358     {
359         // calculate new capacity. We keep the free capacity at the side that does not have to grow
360         // to avoid quadratic behavior with mixed append/prepend cases
361 
362         // use qMax below, because constAllocatedCapacity() can be 0 when using fromRawData()
363         qsizetype minimalCapacity = qMax(from.size, from.constAllocatedCapacity()) + n;
364         // subtract the free space at the side we want to allocate. This ensures that the total size requested is
365         // the existing allocation at the other side + size + n.
366         minimalCapacity -= (position == QArrayData.GrowthPosition.GrowsAtEnd) ? from.freeSpaceAtEnd() : from.freeSpaceAtBegin();
367         qsizetype capacity = from.detachCapacity(minimalCapacity);
368         const(bool) grows = capacity > from.constAllocatedCapacity();
369         auto headerAndDataPtr = Data.allocate(capacity, grows ? QArrayData.AllocationOption.Grow : QArrayData.AllocationOption.KeepSize);
370         auto header = headerAndDataPtr.first;
371         auto dataPtr = headerAndDataPtr.second;
372         const(bool) valid = header !is null && dataPtr !is null;
373         if (!valid)
374             return QArrayDataPointer(header, dataPtr);
375 
376         // Idea: * when growing backwards, adjust pointer to prepare free space at the beginning
377         //       * when growing forward, adjust by the previous data pointer offset
378         dataPtr += (position == QArrayData.GrowthPosition.GrowsAtBeginning)
379                 ? n + qMax(0, (header.alloc - from.size - n) / 2)
380                 : from.freeSpaceAtBegin();
381         header.flags = from.flags();
382         return QArrayDataPointer(header, dataPtr);
383     }
384 
385     /+ friend bool operator==(const QArrayDataPointer &lhs, const QArrayDataPointer &rhs) noexcept
386     {
387         return lhs.data() == rhs.data() && lhs.size == rhs.size;
388     } +/
389 
390     /+ friend bool operator!=(const QArrayDataPointer &lhs, const QArrayDataPointer &rhs) noexcept
391     {
392         return lhs.data() != rhs.data() || lhs.size != rhs.size;
393     } +/
394 
395     Data* d = null;
396     T* ptr = null;
397     qsizetype size = 0;
398 }
399 
400 /+pragma(inline, true) void qSwap(T)(/+ QArrayDataPointer<T> &p1, QArrayDataPointer<T> &p2 +/)/+ noexcept+/
401 {
402     p1.swap(p2);
403 }+/
404 
405 ////////////////////////////////////////////////////////////////////////////////
406 //  Q_ARRAY_LITERAL
407 
408 // The idea here is to place a (read-only) copy of header and array data in an
409 // mmappable portion of the executable (typically, .rodata section).
410 
411 // Hide array inside a lambda
412 /+ #define Q_ARRAY_LITERAL(Type, ...) \
413     ([]() -> QArrayDataPointer<Type> { \
414         static Type const data[] = { __VA_ARGS__ }; \
415         return QArrayDataPointer<Type>::fromRawData(const_cast<Type *>(data), std::size(data)); \
416     }()) +/
417 /**/
418