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 }