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 }