1 /* 2 * BSD 3-Clause License 3 * 4 * Copyright (c) 2016, Mango-Engine Team 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions are met: 9 * 10 * * Redistributions of source code must retain the above copyright notice, this 11 * list of conditions and the following disclaimer. 12 * 13 * * Redistributions in binary form must reproduce the above copyright notice, 14 * this list of conditions and the following disclaimer in the documentation 15 * and/or other materials provided with the distribution. 16 * 17 * * Neither the name of the copyright holder nor the names of its 18 * contributors may be used to endorse or promote products derived from 19 * this software without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 module mango_engine.util; 33 34 import mango_stl.misc : Lock; 35 36 import std.concurrency; 37 import std.container.dlist; 38 import std.conv; 39 import core.atomic; 40 41 alias SyncLock = Lock; 42 43 template InterfaceClassFactory(string type, string clazz, string params) { 44 const char[] InterfaceClassFactory = " 45 import mango_engine.mango : BackendType, currentBackendType; 46 47 final switch(currentBackendType) { 48 case BackendType.BACKEND_OPENGL: 49 version(mango_GLBackend) { 50 import mango_engine.graphics.opengl.gl_" ~ type ~ "; 51 52 return new GL" ~ clazz ~ "(" ~ params ~ "); 53 } else { 54 throw new Exception(\"No backend has been compiled in!\"); 55 } 56 case BackendType.BACKEND_VULKAN: 57 throw new Exception(\"No support for vulkan yet!\"); 58 } 59 "; 60 } 61 62 template LoadLibraryTemplate(string libName, string suffix, string winName) { 63 const char[] LoadLibraryTemplate = " 64 version(Windows) { 65 try { 66 Derelict" ~ suffix ~ ".load(); 67 logger.logDebug(\"Loaded " ~ libName ~ "\"); 68 } catch(Exception e) { 69 logger.logDebug(\"Failed to load library " ~ libName ~ ", searching in provided libs\"); 70 try { 71 Derelict" ~ suffix ~ ".load(\"lib/" ~ winName ~ ".dll\"); 72 logger.logDebug(\"Loaded " ~ libName ~ "\"); 73 } catch(Exception e) { 74 throw new Exception(\"Failed to load library " ~ libName ~ ":\" ~ e.classinfo.name); 75 } 76 } 77 } else { 78 try { 79 Derelict" ~ suffix ~ ".load(); 80 logger.logDebug(\"Loaded " ~ libName ~ "\"); 81 } catch(Exception e) { 82 throw new Exception(\"Failed to load library " ~ libName ~ ":\" ~ e.classinfo.name); 83 } 84 } 85 "; 86 } 87 88 /// Utility class to manage a group of threads. 89 class ThreadPool { 90 immutable size_t workerNumber; 91 92 private struct Worker { 93 private shared Tid _tid; 94 private shared bool _busy = false; 95 96 @property shared Tid tid() @trusted nothrow { return cast(Tid) _tid; } 97 98 @property shared bool busy() @trusted nothrow { return cast(bool) _busy; } 99 @property shared void busy(bool busy) @trusted nothrow { _busy = cast(shared) busy; } 100 101 this(Tid tid, bool busy = false) @trusted nothrow { 102 this._tid = cast(shared) tid; 103 this._busy = cast(shared) busy; 104 } 105 } 106 107 private SyncLock workerLock; 108 private SyncLock lock2; 109 110 private shared bool doStop = false; 111 private shared size_t workerCounter = 0; 112 private shared Worker[size_t] workers; 113 114 this(in size_t workerNumber) @trusted { 115 this.workerNumber = workerNumber; 116 117 for(size_t i = 0; i < workerNumber; i++) { 118 this.workers[i] = cast(shared) Worker(spawn(&spawnWorker, cast(shared) i, cast(shared) this)); 119 } 120 121 workerLock = new SyncLock(); 122 lock2 = new SyncLock(); 123 } 124 125 void submitWork(WorkDelegate work, string debugTest = "none") @trusted { 126 if(doStop) 127 return; 128 129 debug(mango_concurrencyInfo) { 130 import std.stdio; 131 writeln("Submitting to workers: ", debugTest); 132 } 133 134 synchronized(this) { 135 136 /*foreach(id, ref worker; this.workers) { 137 // Prioritize sending work to free workers 138 if(!worker.busy) { 139 send(worker.tid, Work(work)); 140 worker.busy = true; 141 return; 142 } 143 }*/ 144 145 // All workers busy 146 if(workerCounter >= workerNumber) { 147 workerCounter = 0; // Reset workerCounter 148 } 149 150 // Send to the next worker. workerCounter distributes evenly work among the busy workers. 151 send(workers[workerCounter].tid, Work(work)); 152 153 atomicOp!"+="(this.workerCounter, 1); 154 } 155 } 156 157 shared package void notifyBusy(in size_t id, in bool busy) @safe { 158 if(id > this.workers.length) return; 159 this.workers[id].busy = busy; 160 } 161 162 /// Each thread finishes it's current task and immediately stops. 163 void stopImmediate() { 164 synchronized(workerLock) { 165 doStop = true; 166 foreach(id, worker; this.workers) { 167 prioritySend(worker.tid, "stop"); 168 } 169 } 170 } 171 } 172 173 alias WorkDelegate = void delegate() @system; 174 175 package shared struct Work { 176 WorkDelegate work; 177 } 178 179 class ThreadWorker { 180 immutable size_t id; 181 182 private shared ThreadPool pool; 183 184 private bool running = true; 185 186 private DList!Work workQueue; 187 188 this(in size_t id, shared(ThreadPool) pool) @safe nothrow { 189 this.id = id; 190 this.pool = pool; 191 this.workQueue = DList!Work(); 192 } 193 194 void doRun() @trusted { 195 import std.datetime; 196 import core.thread; 197 198 while(running) { 199 receive( 200 (string s) { 201 if(s == "stop") { 202 running = false; 203 } 204 }, 205 (Work work) { 206 //this.workQueue.insertBack(work); 207 //this.pool.notifyBusy(this.id, true); 208 work.work(); 209 //this.pool.notifyBusy(this.id, false); 210 } 211 ); 212 213 /*if(!this.workQueue.empty) { 214 this.pool.notifyBusy(this.id, true); 215 216 Work work = this.workQueue.front; 217 this.workQueue.removeFront(); 218 work.work(); 219 220 if(this.workQueue.empty) { 221 this.pool.notifyBusy(this.id, false); 222 } else this.pool.notifyBusy(this.id, true); 223 }*/ 224 } 225 226 debug(mango_concurrencyInfo) { 227 import std.stdio; 228 writeln("Worker ", id, " exiting"); 229 } 230 } 231 } 232 233 private void spawnWorker(shared(size_t) id, shared(ThreadPool) pool) @system { 234 import core.thread : Thread; 235 import mango_engine.mango : GLOBAL_LOGGER; 236 237 Thread.getThis().name = "WorkerThread-" ~ to!string(id); 238 239 GLOBAL_LOGGER.logDebug("Starting worker #" ~ to!string(id)); 240 241 ThreadWorker worker = new ThreadWorker(id, pool); 242 try { 243 worker.doRun(); 244 } catch(OwnerTerminated e) { 245 GLOBAL_LOGGER.logDebug("Worker thread crashed (Main Thread terminated)."); 246 } catch(Exception e) { 247 GLOBAL_LOGGER.logError("A worker thread has crashed!"); 248 GLOBAL_LOGGER.logException("Worker thread #" ~ to!string(id) ~ " exception caught, crashed", e); 249 } 250 } 251 252 version(Windows) { 253 immutable string PATH_SEPERATOR = "\\"; 254 } else { 255 immutable string PATH_SEPERATOR = "/"; 256 } 257 258 string getTempDirectoryPath() @trusted { 259 version(Windows) { 260 import core.sys.windows.winbase : GetTempPath, DWORD; 261 import blocksound.util : toDString; 262 263 void[] data = new void[128]; 264 DWORD length = GetTempPath(128, data); 265 return toDString(cast(char[]) data[0..length]); 266 } else version(Posix) { 267 import core.stdc.stdlib : getenv; 268 import blocksound.util : toCString, toDString; 269 270 auto env = getenv(toCString("TMPDIR")); 271 auto dir = toDString(env); 272 if(dir == "") { 273 return "/tmp"; 274 } else { 275 return dir; 276 } 277 } else { 278 return "./tmp"; 279 } 280 } 281 282 string getTimestamp() @safe { 283 import std.datetime : SysTime, Clock; 284 import std.conv : to; 285 286 SysTime c = Clock.currTime(); 287 return to!string(c.day) ~ "-" ~ to!string(c.month) 288 ~ "-" ~ to!string(c.year) ~ "_" ~ to!string(c.hour) ~ ":" ~ to!string(c.minute) 289 ~ ":" ~ to!string(c.second); 290 } 291 292 string getOSString() @safe nothrow { 293 import std.system : os, OS; 294 295 switch(os) { 296 case OS.win32: 297 return "Windows (32-bit)"; 298 case OS.win64: 299 return "Windows (64-bit)"; 300 case OS.osx: 301 return "OSX"; 302 case OS.linux: 303 return "Linux"; 304 default: 305 return "Other"; 306 } 307 } 308 309 /++ 310 Reads a whole file into a string. 311 312 Params: 313 filename = The file to be read. 314 315 Returns: The file's contents. 316 Throws: Exception if the file does not exist. 317 +/ 318 string readFileToString(in string filename) @safe { 319 import std.file : exists, readText; 320 if(exists(filename)) { 321 auto text = readText(filename); 322 return text; 323 } else throw new Exception("File does not exist!"); 324 }