1 /*
2 Copyright (c) 2017-2018 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.stream;
34 import dlib.core.thread;
35 import dlib.container.dict;
36 import dlib.filesystem.filesystem;
37 import dlib.filesystem.stdfs;
38 import dlib.image.unmanaged;
39 import dlib.image.hdri;
40 
41 import dagon.core.event;
42 import dagon.core.ownership;
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     UnmanagedImageFactory imageFactory;
72     UnmanagedHDRImageFactory hdrImageFactory;
73     Thread loadingThread;
74 
75     bool liveUpdate = false;
76     double liveUpdatePeriod = 5.0;
77 
78     protected double monitorTimer = 0.0;
79 
80     float nextLoadingPercentage = 0.0f;
81     
82     EventManager eventManager;
83 
84     this(EventManager emngr, Owner o = null)
85     {
86         super(o);
87 
88         assetsByFilename = New!(Dict!(Asset, string));
89         fs = New!VirtualFileSystem();
90         fs.mount(".");
91         imageFactory = New!UnmanagedImageFactory();
92         hdrImageFactory = New!UnmanagedHDRImageFactory();
93 
94         loadingThread = New!Thread(&threadFunc);
95         
96         eventManager = emngr;
97     }
98 
99     ~this()
100     {
101         Delete(assetsByFilename);
102         Delete(fs);
103         Delete(imageFactory);
104         Delete(hdrImageFactory);
105         Delete(loadingThread);
106     }
107 
108     void mountDirectory(string dir)
109     {
110         fs.mount(dir);
111     }
112 
113     void mountBoxFile(string filename)
114     {
115         BoxFileSystem boxfs = New!BoxFileSystem(fs.openForInput(filename), true);
116         fs.mount(boxfs);
117     }
118 
119     void mountBoxFileDirectory(string filename, string dir)
120     {
121         BoxFileSystem boxfs = New!BoxFileSystem(fs.openForInput(filename), true, dir);
122         fs.mount(boxfs);
123     }
124 
125     bool assetExists(string name)
126     {
127         if (name in assetsByFilename)
128             return true;
129         else
130             return false;
131     }
132 
133     Asset addAsset(Asset asset, string name)
134     {
135         if (!(name in assetsByFilename))
136         {
137             assetsByFilename[name] = asset;
138             if (fs.stat(name, asset.monitorInfo.lastStat))
139                 asset.monitorInfo.fileExists = true;
140         }
141         return asset;
142     }
143 
144     Asset preloadAsset(Asset asset, string name)
145     {
146         if (!(name in assetsByFilename))
147         {
148             assetsByFilename[name] = asset;
149             if (fs.stat(name, asset.monitorInfo.lastStat))
150                 asset.monitorInfo.fileExists = true;
151         }
152 
153         asset.release();
154         asset.threadSafePartLoaded = false;
155         asset.threadUnsafePartLoaded = false;
156 
157         asset.threadSafePartLoaded = loadAssetThreadSafePart(asset, name);
158         if (asset.threadSafePartLoaded)
159             asset.threadUnsafePartLoaded = asset.loadThreadUnsafePart();
160 
161         return asset;
162     }
163 
164     void reloadAsset(string name)
165     {
166         auto asset = assetsByFilename[name];
167 
168         asset.release();
169         asset.threadSafePartLoaded = false;
170         asset.threadUnsafePartLoaded = false;
171 
172         asset.threadSafePartLoaded = loadAssetThreadSafePart(asset, name);
173         if (asset.threadSafePartLoaded)
174             asset.threadUnsafePartLoaded = asset.loadThreadUnsafePart();
175     }
176 
177     Asset getAsset(string name)
178     {
179         if (name in assetsByFilename)
180             return assetsByFilename[name];
181         else
182             return null;
183     }
184 
185     void removeAsset(string name)
186     {
187         Delete(assetsByFilename[name]);
188         assetsByFilename.remove(name);
189     }
190 
191     void releaseAssets()
192     {
193         clearOwnedObjects();
194         Delete(assetsByFilename);
195         assetsByFilename = New!(Dict!(Asset, string));
196 
197         Delete(loadingThread);
198         loadingThread = New!Thread(&threadFunc);
199     }
200 
201     bool loadAssetThreadSafePart(Asset asset, string filename)
202     {
203         if (!fileExists(filename))
204         {
205             writefln("Error: cannot find file \"%s\"", filename);
206             return false;
207         }
208             
209         auto fstrm = fs.openForInput(filename);
210         
211         bool res = asset.loadThreadSafePart(filename, fstrm, fs, this);
212         if (!res)
213         {
214             writefln("Error: failed to load asset \"%s\"", filename);
215         }
216             
217         Delete(fstrm);
218         return res;
219     }
220 
221     void threadFunc()
222     {
223         foreach(filename, asset; assetsByFilename)
224         {
225             nextLoadingPercentage += 1.0f / cast(float)(assetsByFilename.length);
226 
227             if (!asset.threadSafePartLoaded)
228             {
229                 asset.threadSafePartLoaded = loadAssetThreadSafePart(asset, filename);
230                 asset.threadUnsafePartLoaded = false;
231             }
232         }
233     }
234 
235     void loadThreadSafePart()
236     {
237         nextLoadingPercentage = 0.0f;
238         monitorTimer = 0.0;
239         loadingThread.start();
240     }
241 
242     bool isLoading()
243     {
244         return loadingThread.isRunning;
245     }
246 
247     bool loadThreadUnsafePart()
248     {
249         bool res = true;
250         foreach(filename, asset; assetsByFilename)
251         //if (!asset.threadUnsafePartLoaded)
252         if (asset.threadSafePartLoaded)
253         {
254             res = asset.loadThreadUnsafePart();
255             asset.threadUnsafePartLoaded = res;
256             if (!res)
257             {
258                 writefln("Error: failed to load asset \"%s\"", filename);
259                 break;
260             }
261         }
262         else
263         {
264             res = false;
265             break;
266         }
267         return res;
268     }
269 
270     bool fileExists(string filename)
271     {
272         FileStat stat;
273         return fs.stat(filename, stat);
274     }
275 
276     void updateMonitor(double dt)
277     {
278         if (liveUpdate)
279         {
280             monitorTimer += dt;
281             if (monitorTimer >= liveUpdatePeriod)
282             {
283                 monitorTimer = 0.0;
284                 foreach(filename, asset; assetsByFilename)
285                     monitorCheck(filename, asset);
286             }
287         }
288     }
289 
290     protected void monitorCheck(string filename, Asset asset)
291     {
292         FileStat currentStat;
293         if (fs.stat(filename, currentStat))
294         {
295             if (!asset.monitorInfo.fileExists)
296             {
297                 asset.monitorInfo.fileExists = true;
298             }
299             else if (currentStat.modificationTimestamp > 
300                      asset.monitorInfo.lastStat.modificationTimestamp ||
301                      currentStat.sizeInBytes != 
302                      asset.monitorInfo.lastStat.sizeInBytes)
303             {
304                 reloadAsset(filename);
305                 asset.monitorInfo.lastStat = currentStat;
306                 eventManager.generateAssetReloadEvent(asset);
307             }
308         }
309         else
310         {
311             if (asset.monitorInfo.fileExists)
312             {
313                 asset.monitorInfo.fileExists = false;
314             }
315         }
316     }
317 }
318