1 /// 2 module isodi.display; 3 4 import isodi.bind; 5 import isodi.cell; 6 import isodi.pack; 7 import isodi.tests; 8 import isodi.anchor; 9 import isodi.camera; 10 import isodi.position; 11 12 13 @safe: 14 15 16 /// Display is the main class of Isodi which manages all Isodi resources. 17 /// 18 /// This class is abstract, as it should be overriden by the renderer. Use the `make` method to create a display with 19 /// the current renderer. 20 /// 21 /// Note: If the display is destroyed, all of its members will automatically be destroyed too, so references to display 22 /// objects will be dead. Make sure you clean them out before switching displays. 23 abstract class Display { 24 25 public { 26 27 /// Base cell size in the display, if supported by the renderer. 28 /// 29 /// Usually in pixels. 30 int cellSize = 100; 31 32 /// Active camera. 33 Camera camera; 34 35 /// Used pack list 36 PackList packs; 37 38 } 39 40 protected { 41 42 /// Registered cells 43 Cell[UniquePosition] cellMap; 44 45 /// Registered anchors 46 Anchor[size_t] anchorMap; 47 48 /// Registered models 49 Model[size_t] modelMap; 50 51 } 52 53 private { 54 55 /// ID of the last added anchor 56 size_t lastAnchor; 57 58 /// ID of the last added model 59 size_t lastModel; 60 61 } 62 63 /// 64 this() { 65 66 packs = PackList.make(); 67 68 } 69 70 ~this() @system { 71 72 clearDestroy(); 73 74 } 75 76 /// Create a display with the current renderer. 77 static Display make() { 78 79 return Renderer.createDisplay(); 80 81 } 82 83 /// Reload all resources in the display. Make sure to call `PackList.clearCache`. 84 abstract void reloadResources(); 85 86 /// Iterate on all cells 87 auto cells() { return cellMap.byValue; } 88 89 /// Iterate on all models 90 auto models() { return modelMap.byValue; } 91 92 /// Iterate on all anchors 93 auto anchors() { return anchorMap.byValue; } 94 95 /// Get the number of cells in the display. 96 size_t cellCount() { return cellMap.length; } 97 98 /// Get the number of models in the display. 99 size_t modelCount() { return modelMap.length; } 100 101 /// Get the number of anchors in the display. 102 size_t anchorCount() { return anchorMap.length; } 103 104 /// Clear all objects within the display. Leaves packs and cache in place. 105 void clear() @system { 106 107 cellMap.clear(); 108 modelMap.clear(); 109 anchorMap.clear(); 110 111 } 112 113 /// Destroy the contents of the display. 114 void clearDestroy() @system { 115 116 // Destroy all resources 117 foreach (cell; cells) cell.destroy(); 118 foreach (model; models) model.destroy(); 119 foreach (anchor; anchors) anchor.destroy(); 120 121 clear(); 122 123 } 124 125 /// Add a new cell to the display. Replaces the cell if one already exists. 126 /// Params: 127 /// position = Position of the cell in the display. 128 /// type = Type of the cell. 129 /// Returns: The created cell. 130 Cell addCell(const Position position, const string type) { 131 132 auto cell = Cell.make(this, position, type); 133 cellMap[position.toUnique] = cell; 134 return cell; 135 136 } 137 138 /// Get a cell at given position. 139 /// Params: 140 /// position = Position of the cell. 141 /// Returns: The cell at this position. `null` if not found. 142 Cell getCell(const UniquePosition position) { 143 144 return cellMap.get(position, null); 145 146 } 147 148 /// Remove the cell at given position. 149 /// 150 /// Returns: True if the cell was actually removed. 151 bool removeCell(UniquePosition position) { 152 153 return cellMap.remove(position); 154 155 } 156 157 /// Ditto. 158 bool removeCell(Position position) { 159 160 return removeCell(position.toUnique); 161 162 } 163 164 unittest { 165 166 auto display = TestRunner.makeDisplay; 167 168 // Add two sample cells 169 display.addCell(position(1, 2), "wood"); 170 display.addCell(position(2, 2), "wood"); 171 172 // Replace the first cell 173 display.addCell(position(1, 2), "grass"); 174 175 // Add a cell on a different layer 176 display.addCell(position(2, 2, 1), "grass"); 177 178 assert(display.getCell(UniquePosition(1, 2, 0)).type == "grass"); 179 assert(display.getCell(UniquePosition(2, 2, 0)).type == "wood"); 180 assert(display.getCell(UniquePosition(3, 2, 0)) is null); 181 182 // Remove the first cell 183 display.removeCell(position(1, 2)); 184 185 assert(display.getCell(UniquePosition(1, 2, 0)) is null); 186 187 } 188 189 /// Add a new model to the display. 190 /// Params: 191 /// position = Position to place the model on. 192 /// type = Skeleton for the model 193 Model addModel(const string type) { 194 195 // Create the model 196 auto model = Model.make(this, type); 197 modelMap[model.id] = model; 198 199 return model; 200 201 } 202 203 /// Ditto 204 Model addModel(Position position, const string type) { 205 206 auto model = addModel(type); 207 model.position = position; 208 return model; 209 210 } 211 212 /// Remove the given model from the map. 213 /// Returns: True if the model was actually removed. 214 bool removeModel(Model model) { 215 216 return modelMap.remove(model.id); 217 218 } 219 220 /// Add a new anchor to the display. 221 /// Returns: The created anchor. 222 Anchor addAnchor(Position position = Position.init) { 223 224 // Create the anchor 225 auto anchor = Anchor.make(this); 226 anchorMap[anchor.id] = anchor; 227 228 // Set the position 229 anchor.position = position; 230 231 return anchor; 232 233 } 234 235 /// Remove the given anchor from the map. 236 /// Returns: True if the anchor was actually removed. 237 bool removeAnchor(Anchor anchor) { 238 239 return anchorMap.remove(anchor.id); 240 241 } 242 243 } 244 245 mixin DisplayTest!((display) { 246 247 // Add a few cells 248 display.addCell(position(1, 1), "grass"); 249 display.addCell(position(1, 2), "grass"); 250 display.addCell(position(1, 3), "grass"); 251 252 // Place models on them 253 display.addModel(position(1, 1), "wraith-white"); 254 display.addModel(position(1, 2), "wraith-white"); 255 auto a = display.addModel(position(1, 3), "wraith-white"); 256 257 // Remove the third and fourth cell (different methods) 258 display.removeCell(position(1, 3)); 259 260 // Remove the third and fourth models 261 display.removeModel(a); 262 263 // Note: now the model can be safely deleted 264 (() @trusted => a.destroy)(); 265 266 // Also: need to test anchors, but there isn't really a visible way... should probably be handled by the renderer 267 // still TODO 268 269 });