1 /*
2 Copyright (c) 2017-2020 Timur Gafarov
3 
4 Boost Software License - Version 1.0 - August 17th, 2003
5 Permission is hereby granted, free of charge, to any person or organization
6 obtaining a copy of the software and accompanying documentation covered by
7 this license (the "Software") to use, reproduce, display, distribute,
8 execute, and transmit the Software, and to prepare derivative works of the
9 Software, and to permit third-parties to whom the Software is furnished to
10 do so, all subject to the following:
11 
12 The copyright notices in the Software and this entire statement, including
13 the above license grant, this restriction and the following disclaimer,
14 must be included in all copies of the Software, in whole or in part, and
15 all derivative works of the Software, unless such copies or derivative
16 works are solely in the form of machine-executable object code generated by
17 a source language processor.
18 
19 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
22 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
23 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
24 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 DEALINGS IN THE SOFTWARE.
26 */
27 
28 module dagon.resource.asset;
29 
30 import std.stdio;
31 
32 import dlib.core.memory;
33 import dlib.core.ownership;
34 import dlib.core.stream;
35 import dlib.core.thread;
36 import dlib.container.dict;
37 import dlib.filesystem.filesystem;
38 import dlib.filesystem.stdfs;
39 import dlib.image.unmanaged;
40 import dlib.image.hdri;
41 
42 import dagon.core.event;
43 import dagon.core.vfs;
44 import dagon.resource.boxfs;
45 
46 struct MonitorInfo
47 {
48     FileStat lastStat;
49     bool fileExists = false;
50 }
51 
52 abstract class Asset: Owner
53 {
54     this(Owner o)
55     {
56         super(o);
57     }
58 
59     MonitorInfo monitorInfo;
60     bool threadSafePartLoaded = false;
61     bool threadUnsafePartLoaded = false;
62     bool loadThreadSafePart(string filename, InputStream istrm, ReadOnlyFileSystem fs, AssetManager mngr);
63     bool loadThreadUnsafePart();
64     void release();
65 }
66 
67 class AssetManager: Owner
68 {
69     Dict!(Asset, string) assetsByFilename;
70     VirtualFileSystem fs;
71     StdFileSystem stdfs;
72     UnmanagedImageFactory imageFactory;
73     UnmanagedHDRImageFactory hdrImageFactory;
74     Thread loadingThread;
75 
76     bool liveUpdate = false;
77     double liveUpdatePeriod = 5.0;
78 
79     protected double monitorTimer = 0.0;
80 
81     float nextLoadingPercentage = 0.0f;
82 
83     EventManager eventManager;
84 
85     this(EventManager emngr, Owner o = null)
86     {
87         super(o);
88 
89         assetsByFilename = New!(Dict!(Asset, string));
90         fs = New!VirtualFileSystem();
91         stdfs = New!StdFileSystem();
92         fs.mount(stdfs);
93         fs.mount(".");
94         imageFactory = New!UnmanagedImageFactory();
95         hdrImageFactory = New!UnmanagedHDRImageFactory();
96 
97         loadingThread = New!Thread(&threadFunc);
98 
99         eventManager = emngr;
100     }
101 
102     ~this()
103     {
104         Delete(assetsByFilename);
105         Delete(fs);
106         Delete(imageFactory);
107         Delete(hdrImageFactory);
108         Delete(loadingThread);
109     }
110 
111     void mountDirectory(string dir)
112     {
113         fs.mount(dir);
114     }
115 
116     void mountBoxFile(string filename)
117     {
118         BoxFileSystem boxfs = New!BoxFileSystem(fs.openForInput(filename), true);
119         fs.mount(boxfs);
120     }
121 
122     void mountBoxFileDirectory(string filename, string dir)
123     {
124         BoxFileSystem boxfs = New!BoxFileSystem(fs.openForInput(filename), true, dir);
125         fs.mount(boxfs);
126     }
127 
128     bool assetExists(string name)
129     {
130         if (name in assetsByFilename)
131             return true;
132         else
133             return false;
134     }
135 
136     Asset addAsset(Asset asset, string name)
137     {
138         if (!(name in assetsByFilename))
139         {
140             assetsByFilename[name] = asset;
141             if (fs.stat(name, asset.monitorInfo.lastStat))
142                 asset.monitorInfo.fileExists = true;
143         }
144         return asset;
145     }
146 
147     Asset preloadAsset(Asset asset, string name)
148     {
149         if (!(name in assetsByFilename))
150         {
151             assetsByFilename[name] = asset;
152             if (fs.stat(name, asset.monitorInfo.lastStat))
153                 asset.monitorInfo.fileExists = true;
154         }
155 
156         asset.release();
157         asset.threadSafePartLoaded = false;
158         asset.threadUnsafePartLoaded = false;
159 
160         asset.threadSafePartLoaded = loadAssetThreadSafePart(asset, name);
161         if (asset.threadSafePartLoaded)
162             asset.threadUnsafePartLoaded = asset.loadThreadUnsafePart();
163 
164         return asset;
165     }
166 
167     void reloadAsset(Asset asset, string filename)
168     {
169         asset.release();
170         asset.threadSafePartLoaded = false;
171         asset.threadUnsafePartLoaded = false;
172 
173         asset.threadSafePartLoaded = loadAssetThreadSafePart(asset, filename);
174         if (asset.threadSafePartLoaded)
175             asset.threadUnsafePartLoaded = asset.loadThreadUnsafePart();
176     }
177 
178     void reloadAsset(string name)
179     {
180         auto asset = assetsByFilename[name];
181 
182         asset.release();
183         asset.threadSafePartLoaded = false;
184         asset.threadUnsafePartLoaded = false;
185 
186         asset.threadSafePartLoaded = loadAssetThreadSafePart(asset, name);
187         if (asset.threadSafePartLoaded)
188             asset.threadUnsafePartLoaded = asset.loadThreadUnsafePart();
189     }
190 
191     Asset getAsset(string name)
192     {
193         if (name in assetsByFilename)
194             return assetsByFilename[name];
195         else
196             return null;
197     }
198 
199     void removeAsset(string name)
200     {
201         Delete(assetsByFilename[name]);
202         assetsByFilename.remove(name);
203     }
204 
205     void releaseAssets()
206     {
207         clearOwnedObjects();
208         Delete(assetsByFilename);
209         assetsByFilename = New!(Dict!(Asset, string));
210 
211         Delete(loadingThread);
212         loadingThread = New!Thread(&threadFunc);
213     }
214     
215     bool loadAssetThreadSafePart(Asset asset, ubyte[] buffer, string filename)
216     {
217         ArrayStream arrStrm = New!ArrayStream(buffer);
218         bool res = loadAssetThreadSafePart(asset, arrStrm, filename);
219         Delete(arrStrm);
220         return res;
221     }
222     
223     bool loadAssetThreadSafePart(Asset asset, InputStream istrm, string filename)
224     {
225         bool res = asset.loadThreadSafePart(filename, istrm, fs, this);
226         if (!res)
227         {
228             writefln("Error: failed to load asset \"%s\"", filename);
229         }
230         return res;
231     }
232 
233     bool loadAssetThreadSafePart(Asset asset, string filename)
234     {
235         if (!fileExists(filename))
236         {
237             writefln("Error: cannot find file \"%s\"", filename);
238             return false;
239         }
240 
241         auto fstrm = fs.openForInput(filename);
242         bool res = loadAssetThreadSafePart(asset, fstrm, filename);
243         Delete(fstrm);
244         return res;
245     }
246 
247     void threadFunc()
248     {
249         foreach(filename, asset; assetsByFilename)
250         {
251             nextLoadingPercentage += 1.0f / cast(float)(assetsByFilename.length);
252 
253             if (!asset.threadSafePartLoaded)
254             {
255                 asset.threadSafePartLoaded = loadAssetThreadSafePart(asset, filename);
256                 asset.threadUnsafePartLoaded = false;
257             }
258         }
259     }
260 
261     void loadThreadSafePart()
262     {
263         nextLoadingPercentage = 0.0f;
264         monitorTimer = 0.0;
265         loadingThread.start();
266     }
267 
268     bool isLoading()
269     {
270         return loadingThread.isRunning;
271     }
272 
273     bool loadThreadUnsafePart()
274     {
275         bool res = true;
276         foreach(filename, asset; assetsByFilename)
277         //if (!asset.threadUnsafePartLoaded)
278         if (asset.threadSafePartLoaded)
279         {
280             res = asset.loadThreadUnsafePart();
281             asset.threadUnsafePartLoaded = res;
282             if (!res)
283             {
284                 writefln("Error: failed to load asset \"%s\"", filename);
285                 break;
286             }
287         }
288         else
289         {
290             res = false;
291             break;
292         }
293         return res;
294     }
295 
296     bool fileExists(string filename)
297     {
298         FileStat stat;
299         return fs.stat(filename, stat);
300     }
301 
302     void updateMonitor(double dt)
303     {
304         if (liveUpdate)
305         {
306             monitorTimer += dt;
307             if (monitorTimer >= liveUpdatePeriod)
308             {
309                 monitorTimer = 0.0;
310                 foreach(filename, asset; assetsByFilename)
311                     monitorCheck(filename, asset);
312             }
313         }
314     }
315 
316     protected void monitorCheck(string filename, Asset asset)
317     {
318         FileStat currentStat;
319         if (fs.stat(filename, currentStat))
320         {
321             if (!asset.monitorInfo.fileExists)
322             {
323                 asset.monitorInfo.fileExists = true;
324             }
325             else if (currentStat.modificationTimestamp >
326                      asset.monitorInfo.lastStat.modificationTimestamp ||
327                      currentStat.sizeInBytes !=
328                      asset.monitorInfo.lastStat.sizeInBytes)
329             {
330                 reloadAsset(filename);
331                 asset.monitorInfo.lastStat = currentStat;
332                 eventManager.generateFileChangeEvent(filename);
333             }
334         }
335         else
336         {
337             if (asset.monitorInfo.fileExists)
338             {
339                 asset.monitorInfo.fileExists = false;
340             }
341         }
342     }
343 }