1 /++
2 Resource loader module
3 
4 Using the loader, you can load various resources that
5 will be kept in memory. They can be accessed by their
6 name or path, they can also be unloaded from memory.
7 
8 Please note that it is unnecessary to reload `download` since it does
9 not exist, it is implemented using the download-upload method. It will
10 load the resource from the .temp folder.
11 
12 Macros:
13     LREF = <a href="#$1">$1</a>
14     HREF = <a href="$1">$2</a>
15 
16 Authors: $(HREF https://github.com/TodNaz,TodNaz)
17 Copyright: Copyright (c) 2020 - 2021, TodNaz.
18 License: $(HREF https://github.com/TodNaz/Tida/blob/master/LICENSE,MIT)
19 +/
20 module tida.loader;
21 
22 import std.path : baseName, stripExtension;
23 import std.file : exists,mkdir;
24 
25 __gshared Loader _loader;
26 
27 /// Loader instance.
28 Loader loader() @trusted
29 {
30     return _loader;
31 }
32 
33 /// Resource descriptor
34 struct Resource
35 {
36 public:
37     Object object; /// Object
38     string type; /// Type name object
39     string path; /// Releative path object
40     string name; /// Local name object
41     bool isFont = false;
42 
43 @trusted:
44     /++
45     Initializes a resource. For this, he saves the name of the type and later,
46     according to this type, he can determine further calls to it.
47 
48     Params:
49         resource = Object resource.
50     +/
51     void init(T)(T resource)
52     {
53         object = resource;
54         type = typeid(T).toString;
55     }
56 
57     /++
58     The method to get the object.
59     If the object turns out to be the wrong one, the contract will work.
60     +/
61     T get(T)()
62     in(typeid(T).toString == type)
63     do
64     {
65         return cast(T) object;
66     }
67 
68     void free()
69     {
70         destroy(object);
71     }
72 }
73 
74 /++
75 Resource loader. Loads resources, fonts and more
76 and keeps it in memory.
77 +/
78 class Loader
79 {
80     import std.path;
81     import std.exception : enforce;
82 
83 private:
84     Resource[] resources;
85 
86 public @safe:
87     /++
88     Will load the resource, having only its path as
89     input. The entire loading implementation lies with
90     the resource itself. The manager will simply keep
91     this resource in memory.
92 
93     Params:
94         T = Data type.
95         path = Path to the file.
96         name = Name.
97 
98     Retunrs:
99         T
100 
101     Throws: `LoadException` if the loader determines
102         that the file does not exist. There may be other
103         errors while loading, see their documentation,
104         for example `Image`.
105 
106     Example:
107     ---
108     Image img = loader.load!Image("a.png");
109     ---
110     +/
111     T load(T)(immutable string path,string name = "null")
112     {
113         if (this.get!T(path) !is null)
114             return this.get!T(path);
115 
116         T obj = new T();
117         Resource res;
118 
119         synchronized
120         {
121             enforce!Exception(path.exists, "Not find file `" ~ path ~ "`!");
122 
123             if(name == "null")
124                 name = path.baseName.stripExtension;
125 
126             obj.load(path);
127 
128             res.path = path;
129             res.name = name;
130             res.init!T(obj);
131 
132             this.resources ~= (res);
133         }
134 
135         return obj;
136     }
137 
138     /++
139     Loads multiple resources in one fell swoop using an associated array.
140 
141     Params:
142         T = Data type.
143         paths = Paths and names for loading resources.
144 
145     Throws: `LoadException` if the loader determines
146         that the file does not exist. There may be other
147         errors while loading, see their documentation,
148         for example `Image`.
149 
150     Example:
151     ---
152     loader.load!Image([
153         "op1" : "image.png",
154         "op2" : "image2.png"
155     ]);
156     ---
157     +/
158     void load(T)(immutable string[string] paths)
159     {
160         foreach (key; paths.keys)
161         {
162             this.load!T(paths[key],key);
163         }
164     }
165 
166     private size_t pos(T)(T res)
167     {
168         foreach (size_t i; 0 .. resources.length)
169         {
170             if (resources[i].object is res)
171             {
172                 return i;
173             }
174         }
175 
176         throw new Exception("Unknown resource");
177     }
178 
179     /++
180     Frees the resource from memory by calling the `free`
181     construct on the resource if it has unreleased pointers
182     and so on, and later removes the resource from the array,
183     letting the garbage collector destroy this object.
184 
185     Params:
186         T = Resource class
187         path = Name or Path to file resource
188 
189     Example:
190     ---
191     loader.free!Image("myImage");
192     ---
193     +/
194     void free(T)(immutable string path) @trusted
195     {
196         auto obj = get!T(path);
197 
198         if (obj is null)
199             return;
200 
201         resources.remove(pos(obj));
202         synchronized destroy(obj);
203     }
204 
205     /++
206     Frees the resource from memory by calling the `free`
207     construct on the resource if it has unreleased pointers
208     and so on, and later removes the resource from the array,
209     letting the garbage collector destroy this object.
210 
211     Params:
212         T = Resource class
213         obj = Resource object
214 
215     Example:
216     ---
217     auto myImage = loader.load!Image(...);
218     loader.free(myImage);
219         ---
220     +/
221     void free(T)(T obj) @trusted
222     {
223         if (obj is null)
224             return;
225 
226         resources.remove(pos!T(obj));
227         synchronized destroy(obj);
228     }
229 
230     /++
231     Returns a resource by name or path.
232 
233     Params:
234         name = name resource(or path)
235 
236     Returns:
237         `null` if the resource is not found.
238         If found, will return a `T` of the
239         appropriate size.
240     +/
241     T get(T)(immutable string name)
242     {
243         foreach (e; this.resources)
244         {
245             if (e.path == name)
246                 return e.get!T;
247 
248             if (e.name == name)
249                 return e.get!T;
250         }
251 
252         return null;
253     }
254 
255     /++
256     Will add a resource that was not loaded through the manager.
257     Please note that it must have a path and a name.
258 
259     Params:
260         res = Resource.
261     +/
262     void add(Resource res)
263     {
264         this.resources ~= (res);
265     }
266 
267     ~this() @safe
268     {
269         foreach (res; resources)
270         {
271             res.free();
272         }
273     }
274 }