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 }