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 }