1 /*******************************************************************************
2 
3     Wrapper for a common boilerplate to imitate queue using dynamic array.
4 
5     Provides IQueue!(T) interfaces, stores items internally using dynamic
6     array of items. Will grow indefinitely as long as there is any spare
7     memory. Popped items will initially be marked as "free" and occasionally
8     whole array will be shifted to optimized used memory space.
9 
10     Copyright:
11         Copyright (c) 2018 dunnhumby Germany GmbH.
12         All rights reserved.
13 
14     License:
15         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
16         Alternatively, this file may be distributed under the terms of the Tango
17         3-Clause BSD License (see LICENSE_BSD.txt for details).
18 
19 *******************************************************************************/
20 
21 module ocean.util.container.queue.DynamicQueue;
22 
23 import ocean.core.Buffer;
24 import ocean.core.array.Mutation : removeShift;
25 import ocean.util.container.queue.model.IQueue;
26 
27 version (unittest)
28 {
29     import ocean.core.Test;
30 }
31 
32 /// ditto
33 class DynamicQueue ( T ) : IQueue!(T)
34 {
35     /// Underlying item storage
36     private Buffer!(T) buffer;
37     /// Marks oldest stored item offset (== the one that will be popped next)
38     private size_t oldest;
39 
40     /// When set to 'true`, will automatically shift all items in the underlying
41     /// array to utilize freed space. It is a recommended default.
42     public bool auto_shrink = true;
43 
44     /***************************************************************************
45 
46         Removes all items from the queue.
47 
48     ***************************************************************************/
49 
50     override public void clear ( )
51     {
52         this.buffer.reset();
53         this.oldest = 0;
54     }
55 
56     /***************************************************************************
57 
58         Reserves space for an element of the size T.sizeof at the queue and
59         returns a pointer to it.
60         The value of the element must then be copied to the location pointed to
61         before calling push() or pop() the next time.
62 
63         Returns:
64             pointer to the element pushed into the queue or null if the queue is
65             full.
66 
67     ***************************************************************************/
68 
69     override public T* push ( )
70     {
71         if (this.auto_shrink)
72         {
73             if (this.oldest > this.buffer.length / 3)
74                 this.shrink();
75         }
76 
77         this.buffer.length = this.buffer.length + 1;
78         return &this.buffer[][$-1];
79     }
80 
81     /***************************************************************************
82 
83         Pushes an element into the queue.
84 
85         Params:
86             element = element to push (will be left unchanged)
87 
88         Returns:
89             true on success or false if the queue is full.
90 
91     ***************************************************************************/
92 
93     override public bool push ( T element )
94     {
95         *this.push() = element;
96         return true;
97     }
98 
99     /***************************************************************************
100 
101         Pops an element from the queue and returns a pointer to that element.
102         The value of the element must then be copied from the location pointed
103         to before calling push() or pop() the next time.
104 
105         Returns:
106             pointer to the element popped from the queue or null if the queue is
107             empty.
108 
109     ***************************************************************************/
110 
111     override public T* pop ( )
112     {
113         if (this.buffer.length <= this.oldest)
114             return null;
115 
116         return &this.buffer[][this.oldest++];
117     }
118 
119     /***************************************************************************
120 
121         Returns:
122             the number of items in the queue
123 
124     ***************************************************************************/
125 
126     override public size_t length ( )
127     {
128         return this.buffer.length - this.oldest;
129     }
130 
131     /***************************************************************************
132 
133         Returns:
134             number of bytes stored in queue
135 
136     ***************************************************************************/
137 
138     override public ulong used_space ( )
139     {
140         return this.length * T.sizeof;
141     }
142 
143     /***************************************************************************
144 
145         Returns:
146             number of bytes free in queue
147 
148     ***************************************************************************/
149 
150     override public ulong free_space ( )
151     {
152         return this.oldest * T.sizeof;
153     }
154 
155     /***************************************************************************
156 
157         Returns:
158             total number of bytes used by queue (used space + free space)
159 
160     ***************************************************************************/
161 
162     override public ulong total_space ( )
163     {
164         return this.buffer.length * T.sizeof;
165     }
166 
167     /***************************************************************************
168 
169         Tells whether the queue is empty.
170 
171         Returns:
172             true if the queue is empty
173 
174     ***************************************************************************/
175 
176     public bool is_empty ( )
177     {
178         return this.oldest == this.buffer.length;
179     }
180 
181     /***************************************************************************
182 
183         Finds out whether the provided number of bytes will fit in the queue.
184 
185         Params:
186             bytes = size of item to check
187 
188         Returns:
189             true if the bytes fits, else false
190 
191     ***************************************************************************/
192 
193     public bool willFit ( size_t bytes )
194     {
195         return true;
196     }
197 
198     /***************************************************************************
199 
200         Shifts lefts currently used slots of the underlying array to optimize
201         memory usage.
202 
203     ***************************************************************************/
204 
205     public void shrink ()
206     {
207         removeShift(this.buffer, 0, this.oldest);
208         this.oldest = 0;
209     }
210 }
211 
212 ///
213 unittest
214 {
215     auto queue = new DynamicQueue!(int);
216     queue.push(1);
217     queue.push(2);
218     queue.push(3);
219     test!("==")(*queue.pop(), 1);
220     test!("==")(*queue.pop(), 2);
221     test!("==")(*queue.pop(), 3);
222 }
223 
224 unittest
225 {
226     static struct S { int field; }
227     auto queue = new DynamicQueue!(S);
228 
229     queue.shrink();
230 
231     queue.push(S(1));
232     test!("==")(queue.length, 1);
233 
234     queue.clear();
235     test!("==")(queue.length, 0);
236 
237     test!("is")(queue.pop(), null);
238     queue.push(S(2));
239     test!("==")(*queue.pop(), S(2));
240 
241     test!("==")(queue.used_space(), 0);
242     for (int i; i < 100; ++i)
243         queue.push(S(i));
244     for (int i; i < 10; ++i)
245         queue.pop();
246     test!("==")(queue.length(), 90);
247     test!("==")(queue.used_space(), 90 * S.sizeof);
248     test!("==")(queue.free_space(), 10 * S.sizeof);
249     test!("==")(queue.total_space(), 100 * S.sizeof);
250     queue.shrink();
251     test!("==")(queue.total_space(), 90 * S.sizeof);
252     test!("==")(*queue.pop(), S(10));
253 
254     test!("==")(queue.is_empty(), false);
255     test!("==")(queue.willFit(size_t.max), true);
256 }