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