1 /*
2 Copyright (c) 2017-2019 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, string filename)
216     {
217         if (!fileExists(filename))
218         {
219             writefln("Error: cannot find file \"%s\"", filename);
220             return false;
221         }
222 
223         auto fstrm = fs.openForInput(filename);
224 
225         bool res = asset.loadThreadSafePart(filename, fstrm, fs, this);
226         if (!res)
227         {
228             writefln("Error: failed to load asset \"%s\"", filename);
229         }
230 
231         Delete(fstrm);
232         return res;
233     }
234 
235     void threadFunc()
236     {
237         foreach(filename, asset; assetsByFilename)
238         {
239             nextLoadingPercentage += 1.0f / cast(float)(assetsByFilename.length);
240 
241             if (!asset.threadSafePartLoaded)
242             {
243                 asset.threadSafePartLoaded = loadAssetThreadSafePart(asset, filename);
244                 asset.threadUnsafePartLoaded = false;
245             }
246         }
247     }
248 
249     void loadThreadSafePart()
250     {
251         nextLoadingPercentage = 0.0f;
252         monitorTimer = 0.0;
253         loadingThread.start();
254     }
255 
256     bool isLoading()
257     {
258         return loadingThread.isRunning;
259     }
260 
261     bool loadThreadUnsafePart()
262     {
263         bool res = true;
264         foreach(filename, asset; assetsByFilename)
265         //if (!asset.threadUnsafePartLoaded)
266         if (asset.threadSafePartLoaded)
267         {
268             res = asset.loadThreadUnsafePart();
269             asset.threadUnsafePartLoaded = res;
270             if (!res)
271             {
272                 writefln("Error: failed to load asset \"%s\"", filename);
273                 break;
274             }
275         }
276         else
277         {
278             res = false;
279             break;
280         }
281         return res;
282     }
283 
284     bool fileExists(string filename)
285     {
286         FileStat stat;
287         return fs.stat(filename, stat);
288     }
289 
290     void updateMonitor(double dt)
291     {
292         if (liveUpdate)
293         {
294             monitorTimer += dt;
295             if (monitorTimer >= liveUpdatePeriod)
296             {
297                 monitorTimer = 0.0;
298                 foreach(filename, asset; assetsByFilename)
299                     monitorCheck(filename, asset);
300             }
301         }
302     }
303 
304     protected void monitorCheck(string filename, Asset asset)
305     {
306         FileStat currentStat;
307         if (fs.stat(filename, currentStat))
308         {
309             if (!asset.monitorInfo.fileExists)
310             {
311                 asset.monitorInfo.fileExists = true;
312             }
313             else if (currentStat.modificationTimestamp >
314                      asset.monitorInfo.lastStat.modificationTimestamp ||
315                      currentStat.sizeInBytes !=
316                      asset.monitorInfo.lastStat.sizeInBytes)
317             {
318                 reloadAsset(filename);
319                 asset.monitorInfo.lastStat = currentStat;
320                 eventManager.generateFileChangeEvent(filename);
321             }
322         }
323         else
324         {
325             if (asset.monitorInfo.fileExists)
326             {
327                 asset.monitorInfo.fileExists = false;
328             }
329         }
330     }
331 }