1 /** 2 Resource 3 4 Copyright: (c) Enalye 2017 5 License: Zlib 6 Authors: Enalye 7 */ 8 9 module atelier.common.resource; 10 11 import std.typecons; 12 import std.file; 13 import std.path; 14 import std.algorithm : count; 15 import std.conv : to; 16 17 import atelier.core; 18 import atelier.render; 19 import atelier.audio; 20 21 private { 22 void*[string] _caches; 23 string[string] _cachesSubFolder; 24 } 25 26 void setResourceCache(T)(ResourceCache!T cache) { 27 static assert(!__traits(isAbstractClass, T), 28 "Fetch cannot instanciate the abstract class " ~ T.stringof); 29 _caches[T.stringof] = cast(void*) cache; 30 } 31 32 void getResourceCache(T)() { 33 static assert(!__traits(isAbstractClass, T), 34 "Fetch cannot instanciate the abstract class " ~ T.stringof); 35 auto cache = T.stringof in _caches; 36 assert(cache, "No cache declared of type " ~ T.stringof); 37 return cast(ResourceCache!T)(*cache); 38 } 39 40 /// Is an object of this name and of this type stored ? 41 bool canFetch(T)(string name) { 42 static assert(!__traits(isAbstractClass, T), 43 "Fetch cannot instanciate the abstract class " ~ T.stringof); 44 auto cache = T.stringof in _caches; 45 assert(cache, "No cache declared of type " ~ T.stringof); 46 return (cast(ResourceCache!T)*cache).canGet(name); 47 } 48 49 /// Is an set of this name and of this type stored ? 50 bool canFetchPack(T)(string name = ".") { 51 static assert(!__traits(isAbstractClass, T), 52 "Fetch cannot instanciate the abstract class " ~ T.stringof); 53 auto cache = T.stringof in _caches; 54 assert(cache, "No cache declared of type " ~ T.stringof); 55 return (cast(ResourceCache!T)*cache).canGetPack(name); 56 } 57 58 /// Returns a stored resource. 59 T fetch(T)(string name) { 60 static assert(!__traits(isAbstractClass, T), 61 "Fetch cannot instanciate the abstract class " ~ T.stringof); 62 auto cache = T.stringof in _caches; 63 assert(cache, "No cache declared of type " ~ T.stringof); 64 return (cast(ResourceCache!T)*cache).get(name); 65 } 66 67 /// Returns a stored resource. 68 T fetchPrototype(T)(string name) { 69 static assert(!__traits(isAbstractClass, T), 70 "Fetch cannot instanciate the abstract class " ~ T.stringof); 71 auto cache = T.stringof in _caches; 72 assert(cache, "No cache declared of type " ~ T.stringof); 73 return (cast(ResourceCache!T)*cache).getPrototype(name); 74 } 75 76 /// Returns all resources of a set. 77 T[] fetchPack(T)(string name = ".") { 78 static assert(!__traits(isAbstractClass, T), 79 "Fetch cannot instanciate the abstract class " ~ T.stringof); 80 auto cache = T.stringof in _caches; 81 assert(cache, "No cache declared of type " ~ T.stringof); 82 return (cast(ResourceCache!T)*cache).getPack(name); 83 } 84 85 /// Returns all resources' name of a set. 86 string[] fetchPackNames(T)(string name = ".") { 87 static assert(!__traits(isAbstractClass, T), 88 "Fetch cannot instanciate the abstract class " ~ T.stringof); 89 auto cache = T.stringof in _caches; 90 assert(cache, "No cache declared of type " ~ T.stringof); 91 return (cast(ResourceCache!T)*cache).getPackNames(name); 92 } 93 94 /// Returns all resources of a set as a tuple of the resource + its name. 95 Tuple!(T, string)[] fetchPackTuples(T)(string name = ".") { 96 static assert(!__traits(isAbstractClass, T), 97 "Fetch cannot instanciate the abstract class " ~ T.stringof); 98 auto cache = T.stringof in _caches; 99 assert(cache, "No cache declared of type " ~ T.stringof); 100 return (cast(ResourceCache!T)*cache).getPackTuples(name); 101 } 102 103 /// Returns everything of this type as tuples of the resources + their name. 104 Tuple!(T, string)[] fetchAllTuples(T)() { 105 static assert(!__traits(isAbstractClass, T), 106 "Fetch cannot instanciate the abstract class " ~ T.stringof); 107 auto cache = T.stringof in _caches; 108 assert(cache, "No cache declared of type " ~ T.stringof); 109 return (cast(ResourceCache!T)*cache).getAllTuples(); 110 } 111 112 /// Internal cache storing all loaded resources. 113 class ResourceCache(T) { 114 protected { 115 Tuple!(T, string)[] _data; 116 uint[string] _ids; 117 uint[][string] _packs; 118 } 119 120 this() { 121 } 122 123 bool canGet(string name) { 124 return (name in _ids) !is null; 125 } 126 127 bool canGetPack(string pack = ".") { 128 return (buildNormalizedPath(pack) in _packs) !is null; 129 } 130 131 T get(string name) { 132 auto p = (name in _ids); 133 assert(p, "Resource: no \'" ~ name ~ "\' loaded"); 134 return new T(_data[*p][0]); 135 } 136 137 T[] getPack(string pack = ".") { 138 pack = buildNormalizedPath(pack); 139 140 auto p = (pack in _packs); 141 assert(p, "Resource: no pack \'" ~ pack ~ "\' loaded"); 142 143 T[] result; 144 foreach (i; *p) 145 result ~= new T(_data[i][0]); 146 return result; 147 } 148 149 T getPrototype(string name) { 150 auto p = (name in _ids); 151 assert(p, "Resource: no \'" ~ name ~ "\' loaded"); 152 return _data[*p][0]; 153 } 154 155 string[] getPackNames(string pack = ".") { 156 pack = buildNormalizedPath(pack); 157 158 auto p = (pack in _packs); 159 assert(p, "Resource: no pack \'" ~ pack ~ "\' loaded"); 160 161 string[] result; 162 foreach (i; *p) 163 result ~= _data[i][1]; 164 return result; 165 } 166 167 Tuple!(T, string)[] getPackTuples(string pack = ".") { 168 pack = buildNormalizedPath(pack); 169 170 auto p = (pack in _packs); 171 assert(p, "Resource: no pack \'" ~ pack ~ "\' loaded"); 172 173 Tuple!(T, string)[] result; 174 foreach (i; *p) 175 result ~= _data[i]; 176 return result; 177 } 178 179 Tuple!(T, string)[] getAllTuples() { 180 return _data; 181 } 182 183 void set(T value, string tag, string pack = "") { 184 uint id = cast(uint) _data.length; 185 if (pack.length) 186 _packs[pack] ~= id; 187 _ids[tag] = id; 188 _data ~= tuple(value, tag); 189 } 190 } 191 192 class DataCache(T) : ResourceCache!T { 193 this(string path, string sub, string filter) { 194 path = buildPath(path, sub); 195 196 if (!exists(path) || !isDir(path)) 197 throw new Exception("The specified path is not a valid directory: \'" ~ path ~ "\'"); 198 auto files = dirEntries(path, filter, SpanMode.depth); 199 foreach (file; files) { 200 string relativeFileName = stripExtension(relativePath(file, path)); 201 string folder = dirName(relativeFileName); 202 uint id = cast(uint) _data.length; 203 204 _packs[folder] ~= id; 205 _ids[relativeFileName] = id; 206 _data ~= tuple(new T(file), relativeFileName); 207 } 208 } 209 } 210 211 private class SpriteCache(T) : ResourceCache!T { 212 this(string path, string sub, string filter, ResourceCache!Texture cache) { 213 path = buildPath(path, sub); 214 215 if (!exists(path) || !isDir(path)) 216 throw new Exception("The specified path is not a valid directory: \'" ~ path ~ "\'"); 217 auto files = dirEntries(path, filter, SpanMode.depth); 218 foreach (file; files) { 219 string relativeFileName = stripExtension(relativePath(file, path)); 220 string folder = dirName(relativeFileName); 221 222 auto texture = cache.get(relativeFileName); 223 loadJson(file, texture); 224 } 225 } 226 227 private void loadJson(string file, Texture texture) { 228 auto sheetJson = parseJSON(readText(file)); 229 foreach (string tag, JSONValue value; sheetJson.object) { 230 if ((tag in _ids) !is null) 231 throw new Exception("Duplicate sprite defined \'" ~ tag ~ "\' in \'" ~ file ~ "\'"); 232 T sprite = new T(texture); 233 234 //Clip 235 sprite.clip.x = getJsonInt(value, "x"); 236 sprite.clip.y = getJsonInt(value, "y"); 237 sprite.clip.z = getJsonInt(value, "w"); 238 sprite.clip.w = getJsonInt(value, "h"); 239 240 //Size/scale 241 sprite.size = to!Vec2f(sprite.clip.zw); 242 sprite.size *= Vec2f(getJsonFloat(value, "scalex", 1f), 243 getJsonFloat(value, "scaley", 1f)); 244 245 //Flip 246 bool flipH = getJsonBool(value, "fliph", false); 247 bool flipV = getJsonBool(value, "flipv", false); 248 249 if (flipH && flipV) 250 sprite.flip = Flip.BothFlip; 251 else if (flipH) 252 sprite.flip = Flip.HorizontalFlip; 253 else if (flipV) 254 sprite.flip = Flip.VerticalFlip; 255 else 256 sprite.flip = Flip.NoFlip; 257 258 //Center expressed in texels, it does the same thing as Anchor 259 Vec2f center = Vec2f(getJsonFloat(value, "centerx", -1f), 260 getJsonFloat(value, "centery", -1f)); 261 if (center.x > -.5f) //Temp 262 sprite.anchor.x = center.x / cast(float)(sprite.clip.z); 263 if (center.y > -.5f) 264 sprite.anchor.y = center.y / cast(float)(sprite.clip.w); 265 266 //Anchor, same as Center but uses a relative coordinate system where [.5,.5] is the center 267 if (center.x < 0f) //Temp 268 sprite.anchor.x = getJsonFloat(value, "anchorx", .5f); 269 if (center.y < 0f) 270 sprite.anchor.y = getJsonFloat(value, "anchory", .5f); 271 272 //Type 273 string type = getJsonStr(value, "type", "."); 274 275 //Register sprite 276 uint id = cast(uint) _data.length; 277 _packs[type] ~= id; 278 _ids[tag] = id; 279 _data ~= tuple(sprite, tag); 280 } 281 } 282 } 283 284 private class TilesetCache(T) : ResourceCache!T { 285 this(string path, string sub, string filter, ResourceCache!Texture cache) { 286 path = buildPath(path, sub); 287 288 if (!exists(path) || !isDir(path)) 289 throw new Exception("The specified path is not a valid directory: \'" ~ path ~ "\'"); 290 auto files = dirEntries(path, filter, SpanMode.depth); 291 foreach (file; files) { 292 string relativeFileName = stripExtension(relativePath(file, path)); 293 string folder = dirName(relativeFileName); 294 295 auto texture = cache.get(relativeFileName); 296 loadJson(file, texture); 297 } 298 } 299 300 private void loadJson(string file, Texture texture) { 301 auto sheetJson = parseJSON(readText(file)); 302 foreach (string tag, JSONValue value; sheetJson.object) { 303 if ((tag in _ids) !is null) 304 throw new Exception("Duplicate tileset defined \'" ~ tag ~ "\' in \'" ~ file ~ "\'"); 305 Vec4i clip; 306 int columns, lines, maxtiles; 307 308 //Max number of tiles the tileset cannot exceeds 309 maxtiles = getJsonInt(value, "tiles", -1); 310 311 //Upper left border of the tileset 312 clip.x = getJsonInt(value, "x", 0); 313 clip.y = getJsonInt(value, "y", 0); 314 315 //Tile size 316 clip.z = getJsonInt(value, "w"); 317 clip.w = getJsonInt(value, "h"); 318 319 columns = getJsonInt(value, "columns", 1); 320 lines = getJsonInt(value, "lines", 1); 321 322 string type = getJsonStr(value, "type", "."); 323 324 T tileset = new T(texture, clip, columns, lines, maxtiles); 325 tileset.scale = Vec2f(getJsonFloat(value, "scalex", 1f), 326 getJsonFloat(value, "scaley", 1f)); 327 328 //Flip 329 bool flipH = getJsonBool(value, "fliph", false); 330 bool flipV = getJsonBool(value, "flipv", false); 331 332 if (flipH && flipV) 333 tileset.flip = Flip.BothFlip; 334 else if (flipH) 335 tileset.flip = Flip.HorizontalFlip; 336 else if (flipV) 337 tileset.flip = Flip.VerticalFlip; 338 else 339 tileset.flip = Flip.NoFlip; 340 341 uint id = cast(uint) _data.length; 342 _packs[type] ~= id; 343 _ids[tag] = id; 344 _data ~= tuple(tileset, tag); 345 } 346 } 347 }