1 module eventy.engine;
2 
3 import eventy.types : EventType;
4 import eventy.signal : Signal;
5 import eventy.event : Event;
6 import eventy.config;
7 import eventy.exceptions;
8 
9 import std.container.dlist;
10 import core.sync.mutex : Mutex;
11 import core.thread : Thread, dur, Duration;
12 import std.conv : to;
13 
14 unittest
15 {
16     import std.stdio;
17 
18     Engine engine = new Engine();
19 
20     /**
21     * Let the event engine know what typeIDs are
22     * allowed to be queued
23     */
24     engine.addEventType(new EventType(1));
25     engine.addEventType(new EventType(2));
26 
27     /**
28     * Create a new Signal Handler that will handles
29     * event types `1` and `2` with the given `handler()`
30     * function
31     */
32     class SignalHandler1 : Signal
33     {
34         this()
35         {
36             super([1, 2]);
37         }
38 
39         public override void handler(Event e)
40         {
41             writeln("Running event", e.getID());
42         }
43     }
44 
45     /**
46     * Tell the event engine that I want to register
47     * the following handler for its queues `1` and `2`
48     */
49     Signal j = new SignalHandler1();
50     engine.addSignalHandler(j);
51 
52     Event eTest = new Event(1);
53     engine.push(eTest);
54 
55     eTest = new Event(2);
56     engine.push(eTest);
57 
58     Thread.sleep(dur!("seconds")(2));
59     engine.push(eTest);
60 
61     writeln("done with main thread code");
62 
63     while(engine.hasEventsRunning()) {}
64 
65     /* TODO: Before shutting down, actually test it out (i.e. all events ran) */
66     engine.shutdown();
67 }
68 
69 unittest
70 {
71     import std.stdio;
72 
73     EngineSettings customSettings = {holdOffMode: HoldOffMode.YIELD};
74     Engine engine = new Engine(customSettings);
75 
76     /**
77     * Let the event engine know what typeIDs are
78     * allowed to be queued
79     */
80     engine.addEventType(new EventType(1));
81     engine.addEventType(new EventType(2));
82 
83     /**
84     * Create a new Signal Handler that will handles
85     * event types `1` and `2` with the given `handler()`
86     * function
87     */
88     class SignalHandler1 : Signal
89     {
90         this()
91         {
92             super([1, 2]);
93         }
94 
95         public override void handler(Event e)
96         {
97             writeln("Running event", e.getID());
98         }
99     }
100 
101     /**
102     * Tell the event engine that I want to register
103     * the following handler for its queues `1` and `2`
104     */
105     Signal j = new SignalHandler1();
106     engine.addSignalHandler(j);
107 
108     Event eTest = new Event(1);
109     engine.push(eTest);
110 
111     eTest = new Event(2);
112     engine.push(eTest);
113 
114     Thread.sleep(dur!("seconds")(2));
115     engine.push(eTest);
116 
117     writeln("done with main thread code");
118 
119     while(engine.hasEventsRunning()) {}
120 
121     /* TODO: Before shutting down, actually test it out (i.e. all events ran) */
122     engine.shutdown();
123 }
124 
125 /**
126 * Engine
127 *
128 * An instance of this represents an engine that
129 * can, at any time, handle the delivery of new
130 * events, trigger the correct signal handlers
131 * for the respective events, remove signal
132 * handlers, add signal handlers, among many
133 * other things
134 */
135 public final class Engine
136 {
137     /* Registered queues */
138     private DList!(EventType) eventTypes;
139     private Mutex eventTypesLock;
140 
141     /* Registered signal handlers */
142     private DList!(Signal) handlers;
143     private Mutex handlerLock;
144 
145     /* Engine configuration */
146     private EngineSettings settings;
147 
148     /* Whether engine is running or not */
149     private bool running;
150 
151     /* Dispatched threads */
152     private DList!(DispatchWrapper) threadStore;
153     private Mutex threadStoreLock;
154 
155     /** 
156      * Instantiates a new Eventy engine with the provided
157      * configuration
158      *
159      * Params:
160      *   settings = The EngineSettings to use
161      */
162     this(EngineSettings settings)
163     {
164         eventTypesLock = new Mutex();
165         handlerLock = new Mutex();
166         threadStoreLock = new Mutex();
167 
168         this.settings = settings;
169     }
170 
171     /** 
172      * Instantiates a new Eventy engine with the default
173      * settings
174      */
175     this()
176     {
177         EngineSettings defaultSettings;
178 
179         /* Yield if a lock fails (prevent potential thread starvation) */
180         defaultSettings.agressiveTryLock = false;
181 
182         // FIXME: Investigate ways to lower load average
183         // /* Make the event engine loop sleep (1) and for 50ms (2) (TODO: Adjust this) */
184         // defaultSettings.holdOffMode = HoldOffMode.SLEEP;
185         // defaultSettings.sleepTime = dur!("msecs")(50);
186 
187         /* Use yeilding for most responsiveness */
188         defaultSettings.holdOffMode = HoldOffMode.YIELD;
189 
190         /* Do not gracefully shutdown */
191         defaultSettings.gracefulShutdown = false;
192 
193         this(defaultSettings);
194     }
195 
196     /** 
197      * Returns the current configuration paremeters being
198      * used by the engine
199      *
200      * Returns: The EngineSettings struct
201      */
202     public EngineSettings getConfig()
203     {
204         return settings;
205     }
206 
207     /** 
208      * Updates the current configuration of the engine
209      *
210      * Params:
211      *   newSettings = The new EngineSettings struct to use
212      */
213     public void setConfig(EngineSettings newSettings)
214     {
215         this.settings = newSettings;
216     }
217 
218     /** 
219      * Attaches a new signal handler to the engine
220      *
221      * Params:
222      *   e = the signal handler to add
223      */
224     public void addSignalHandler(Signal e)
225     {
226         /* Lock the signal-set */
227         handlerLock.lock();
228 
229         /* Add the new handler */
230         handlers ~= e;
231 
232         /* Unlock the signal-set */
233         handlerLock.unlock();
234     }
235 
236     /** 
237      * Shuts down the event engine
238      */
239     public void shutdown()
240     {
241         /* TODO: Insert a lock here, that dispatch should adhere too as well */
242 
243         /* FIXME: We should prevent adding of queues during shutdown */
244         /* FIXME: We should prevent pushing of events during shutdown */
245 
246         /* Wait for any pendings events (if configured) */
247         if(settings.gracefulShutdown)
248         {
249             while(hasEventsRunning()) {}
250         }
251     }
252 
253     /** 
254      * Creates a new thread per signal and dispatches the event to them
255      *
256      * Params:
257      *   signalSet = The signal handlers to use for dispatching
258      *   e = the Event to be dispatched to each handler
259      */
260     private void dispatch(Signal[] signalSet, Event e)
261     {
262         foreach (Signal signal; signalSet)
263         {
264             /* Create a new Thread */
265             DispatchWrapper handlerThread = new DispatchWrapper(signal, e);
266 
267             /**
268             * TODO
269             *
270             * When we call `shutdown()` there may very well be a case of
271             * where the threadStoreLock unlocks after the clean up
272             * loop, but storeThread hangs here during that time,
273             * then proceeds to start the thread, we should therefore,
274             * either block on running changed (solution 1, not as granular)
275             *
276             * Solution 2: Block on dispatch being called <- use this method rather
277             * But still needs a running check, it must not go ahead if running is now
278             * false
279             */
280 
281             /* Store the thread */
282             storeThread(handlerThread);
283 
284             /* Start the thread */
285             handlerThread.start();
286         }
287     }
288 
289     /** 
290      * Adds a thread to the thread store
291      *
292      * Params:
293      *   t = the thread to add
294      */
295     private void storeThread(DispatchWrapper t)
296     {
297         /**
298         * TODO: This can only be implemented if we use
299         * wrapper threads that exit, and we can signal
300         * removal from thread store then
301         */
302 
303         /* Lock the thread store from editing */
304         threadStoreLock.lock();
305 
306         /* Add the thread */
307         threadStore ~= t;
308 
309         /* Unlock the thread store for editing */
310         threadStoreLock.unlock();
311     }
312 
313     /** 
314      * Removes a thread from the thread store
315      *
316      * Params:
317      *   t = the thread to remove
318      */
319     private void removeThread(DispatchWrapper t)
320     {
321         /* Lock the thread store from editing */
322         threadStoreLock.lock();
323 
324         /* Remove the thread */
325         threadStore.linearRemoveElement(t);
326 
327         /* Unlock the thread store for editing */
328         threadStoreLock.unlock();
329     }
330 
331     /** 
332      * Checks whether or not there are still events
333      * running at the time of calling
334      *
335      * Returns: <code>true</code> if there are events
336      * still running, <code>false</code> otherwise
337      */
338     public bool hasEventsRunning()
339     {
340         /* Whether there are events running or not */
341         bool has = false;
342 
343         /* Lock the thread store */
344         threadStoreLock.lock();
345 
346         has = !threadStore.empty();
347 
348         /* Unlock the thread store */
349         threadStoreLock.unlock();
350 
351         return has;
352     }
353 
354     /** 
355      * DispatchWrapper
356      *
357      * Effectively a thread but with the Signal,
358      * Event included with clean-up routines
359      */
360     private class DispatchWrapper : Thread
361     {
362         private Signal signal;
363         private Event e;
364 
365         this(Signal signal, Event e)
366         {
367             super(&run);
368             this.signal = signal;
369             this.e = e;
370         }
371 
372         private void run()
373         {
374             /* Run the signal handler */
375             signal.handler(e);
376 
377             /* Remove myself from the thread store */
378             removeThread(this);
379         }
380     }
381 
382     /** 
383      * Returns all the signal handlers responsible
384      * for handling the type of Event provided
385      *
386      * Params:
387      *   e = the Event type to match to
388      * Returns: A Signal[] containing each handler
389      *          registered to handle type <code>e</code>
390      */
391     public Signal[] getSignalsForEvent(Event e)
392     {
393         /* Matched handlers */
394         Signal[] matchedHandlers;
395 
396         /* Lock the signal-set */
397         handlerLock.lock();
398 
399         /* Find all handlers matching */
400         foreach (Signal signal; handlers)
401         {
402             if (signal.handles(e.getID()))
403             {
404                 matchedHandlers ~= signal;
405             }
406         }
407 
408         /* Unlock the signal-set */
409         handlerLock.unlock();
410 
411         return matchedHandlers;
412     }
413 
414     /** 
415      * Checks if there is a signal handler that handles
416      * the given event id
417      *
418      * Params:
419      *   id = the event ID to check
420      * Returns: <code>true</code> if a signal handler does
421      *          exist, <code>false</code> otherwise
422      */
423     public bool isSignalExists(ulong id)
424     {
425     	return getSignalsForEvent(new Event(id)).length != 0;
426     }
427 
428     /** 
429      * Pushes the given Event into the engine
430      * for eventual dispatch
431      *
432      * Params:
433      *   e = the event to push
434      */
435     public void push(Event e)
436     {
437         //TODO: New code goes below here
438         /** 
439          * What we want to do here is to effectively
440          * wake up a checker thread and also (before that)
441          * perhaps we say what queue was modified
442          *
443          * THEN the checker thread goes to said queue and
444          * executes said event (dispatches it) and then sleep
445          * again till it is interrupted. We need Pids and kill etc for this
446          *
447          * Idea (2)
448          *
449          * If we cannot do a checker thread then we can spwan a thread here
450          * but then we get no control for priorities etc, although actually we could
451          * maybe? It depends, we don't want multiple dispathers at same time then
452          * (A checker thread would ensure we don't get this)
453          */
454 
455         /* Obtain all signal handlers for the given event */
456         Signal[] handlersMatched = getSignalsForEvent(e);
457 
458         /* If we get signal handlers then dispatch them */
459         if(handlersMatched.length)
460         {
461             dispatch(handlersMatched, e);
462         }
463         /* If there are no matching events */
464         else
465         {
466             //TODO: Add default handler support
467             //TODO: Add error throwing in case where not true
468         }
469     }
470 
471     /** 
472      * Registers a new EventType with the engine
473      * and then adds it.
474      * 
475      * Throws EventyException if the id of the given
476      * EventType is is already in use by another
477      *
478      * Params:
479      *   id = the id of the new event type to add
480      * Throws: EventyException
481      */
482     public void addEventType(EventType evType)
483     {
484         /* Lock the event types list */
485         eventTypesLock.lock();
486 
487         /* If no such queue exists then add it (recursive mutex used) */
488         if (!findEventType(evType.getID()))
489         {
490             /* Add the event types list */
491             eventTypes ~= evType;
492         }
493         else
494         {
495             throw new EventyException("Failure to add EventType with id '"~to!(string)(evType.getID())~"\' as it is already in use");
496         }
497 
498         /* Unlock the event types list */
499         eventTypesLock.unlock();
500     }
501 
502     /** 
503      * Given an if, this will return the EventType
504      * associated with said id
505      *
506      * Params:
507      *   id = the id of the EventType
508      * Returns: The EventType if found, otherwise
509      *          <code>null</code>
510      */
511     public EventType findEventType(ulong id)
512     {
513         /* Lock the EventType list */
514         eventTypesLock.lock();
515 
516         /* Find the matching EventType */
517         EventType matchedEventType;
518         foreach (EventType eventType; eventTypes)
519         {
520             if (eventType.getID() == id)
521             {
522                 matchedEventType = eventType;
523                 break;
524             }
525         }
526 
527         /* Unlock the EventType list */
528         eventTypesLock.unlock();
529 
530         return matchedEventType;
531     }
532 
533     /* TODO: Add coumentation */
534     private ulong[] getTypes()
535     {
536         /* TODO: Implement me */
537         return null;
538     }
539 }