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 }