1 
2 module handle.manager;
3 
4 import handle.handle;
5 
6 import std.traits;
7 
8 class HandleManager(T, size_t entryCount = 2 ^^ 12) if(entryCount > 0)
9 {
10 private:
11     struct HandleEntry
12     {
13         T object;
14 
15         ushort counter = 1;
16         ushort nextFreeIndex;
17 
18         bool active;
19 
20         this(ushort nextFreeIndex)
21         {
22             this.nextFreeIndex = nextFreeIndex;
23         }
24 
25         bool matches(Handle!T handle) const
26         {
27             return active && handle.counter == counter;
28         }
29     }
30 
31 private:
32     ushort _activeCount    = 0;
33     ushort _firstFreeIndex = 0;
34 
35     HandleEntry[entryCount] _entries;
36 
37 public:
38     this()
39     {
40         clear;
41     }
42 
43     /++
44      + Stores an element in the handle manager, returning a unique handle for
45      + that element.
46      +
47      + Params:
48      +   object - The element to store in the handle manager.
49      +
50      + Returns:
51      +   A handle referring to the element that was stored in the handle manager.
52      ++/
53     Handle!T add(T object)
54     in
55     {
56         assert(_activeCount < entryCount);
57     }
58     body
59     {
60         auto next = _firstFreeIndex;
61         assert(next < entryCount);
62         assert(!_entries[next].active);
63 
64         // Also update the next-free-index for fast allocation.
65         _firstFreeIndex = _entries[next].nextFreeIndex;
66         _entries[next].nextFreeIndex = 0;
67 
68         _entries[next].counter++;
69         _entries[next].active = true;
70         _entries[next].object = object;
71         _activeCount++;
72 
73         return Handle!T(next, _entries[next].counter);
74     }
75 
76     @property
77     enum size_t capacity = entryCount;
78 
79     /++
80      + Clears the handle manager, returning it to its initial state.
81      ++/
82     void clear()
83     {
84         _activeCount    = 0;
85         _firstFreeIndex = 0;
86 
87         foreach(ushort index; 1 .. entryCount)
88         {
89             _entries[index - 1] = HandleEntry(index);
90         }
91 
92         // Last entry does a wrap-around.
93         _entries[$ - 1] = HandleEntry(0);
94     }
95 
96     /++
97      + Retrieves an element from the handle manager by its handle. If no element
98      + exists at the handle, or the handle is no longer valid, null is returned.
99      + If the type of the stored element cannot store null, an exception is
100      + raised instead.
101      +
102      + Params:
103      +   handle - The handle of the element being accessed.
104      +
105      + Returns:
106      +   An element stored in the handle manager.
107      ++/
108     T get(Handle!T handle) const
109     {
110         T value;
111 
112         if(get(handle, value))
113         {
114             return value;
115         }
116         else
117         {
118             // Check if the type can store a null.
119             static if(isAssignable!(T, typeof(null)))
120             {
121                 return null;
122             }
123             else
124             {
125                 assert(0, "No value exists at handle.");
126             }
127         }
128     }
129 
130     /++
131      + Retrieves an element from the handle manager by its handle, storing the
132      + result in an out parameter.
133      +
134      + Params:
135      +   handle - The handle of the element being accessed.
136      +   object - The out parameter in which the result is stored.
137      +
138      + Returns:
139      +   true if the handle was valid and referred to an element, false otherwise.
140      ++/
141     bool get(Handle!T handle, out T object) const
142     in
143     {
144         assert(handle.index < entryCount);
145     }
146     body
147     {
148         if(_entries[handle.index].matches(handle))
149         {
150             object = _entries[handle.index].object;
151             return true;
152         }
153 
154         return false;
155     }
156 
157     /++
158      + Returns the number of elements currently stored in the handle manager.
159      +
160      + Returns:
161      +   The length of the handle manager.
162      ++/
163     @property
164     size_t length() const
165     {
166         return _activeCount;
167     }
168 
169     T opIndex(Handle!T handle) const
170     {
171         return get(handle);
172     }
173 
174     T opIndexAssign(T object, Handle!T handle)
175     {
176         if(replace(handle, object))
177         {
178             return object;
179         }
180         else
181         {
182             assert(0, "No value exists at handle.");
183         }
184     }
185 
186     /++
187      + Removes an element from the handle manager.
188      +
189      + Params:
190      +   handle - The handle of the element being removed.
191      +
192      + Returns:
193      +   true if the handle was valid and an element was removed, false otherwise.
194      ++/
195     bool remove(Handle!T handle)
196     in
197     {
198         assert(handle.index < entryCount);
199     }
200     body
201     {
202         if(_entries[handle.index].matches(handle))
203         {
204             // Clear the reference as well to assist the GC.
205             _entries[handle.index].nextFreeIndex = _firstFreeIndex;
206             _entries[handle.index].active = false;
207             _entries[handle.index].object = T.init;
208 
209             // Also update next-free-index.
210             _firstFreeIndex = handle.index;
211             _activeCount--;
212 
213             return true;
214         }
215 
216         return false;
217     }
218 
219     /++
220      + Replaces the element referred to by a handle in the handle manager.
221      +
222      + Params:
223      +   handle - The handle of the element being replaced.
224      +   object - The new value of the handle.
225      +
226      + Returns:
227      +   true if the handle was valid and an element was replaced, false otherwise.
228      ++/
229     bool replace(Handle!T handle, T object)
230     in
231     {
232         assert(handle.index < entryCount);
233     }
234     body
235     {
236         if(_entries[handle.index].matches(handle))
237         {
238             _entries[handle.index].object = object;
239             return true;
240         }
241 
242         return false;
243     }
244 }
245 
246 unittest
247 {
248     auto manager = new HandleManager!(string, 2);
249 
250     auto handle1 = manager.add("foo");
251     auto handle2 = manager.add("bar");
252 
253     assert(manager.get(handle1) == "foo");
254     assert(manager.get(handle2) == "bar");
255     assert(manager.length == 2);
256 
257     assert(manager.replace(handle2, "baz"));
258     assert(manager.get(handle2) == "baz");
259     assert(manager.length == 2);
260 
261     assert(manager.remove(handle2));
262     assert(manager.get(handle2) is null);
263     assert(manager.get(handle1) == "foo");
264     assert(manager.length == 1);
265 }