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.event.core;
33 
34 import mango_engine.game;
35 import mango_engine.exception;
36 import mango_engine.util;
37 
38 import std.conv;
39 
40 /// Represents a callable event with properties.
41 abstract class Event {
42 
43 }
44 
45 /// Event that is fired each tick.
46 class TickEvent : Event {
47     immutable ulong currentTick;
48 
49     this(in ulong currentTick) @safe nothrow pure {
50         this.currentTick = currentTick;
51     }
52 }
53 
54 /// Event that is fired when the engine begins to clean up.
55 class EngineCleanupEvent : Event {
56 
57 }
58 
59 alias HookDelegate = void delegate(Event e) @system;
60 
61 struct EventHook {
62     /// The function delegate to be ran.
63     immutable HookDelegate hook;
64     /// If the delegate can be ran in a separate worker thread.
65     immutable bool runAsync = true;
66 }
67 
68 class EventManager {
69     private ThreadPool pool;
70     private GameManager game;
71 
72     private shared SyncLock hookLock;
73     private shared EventHook[][string] hooks;
74 
75     private shared SyncLock evtQueueLock;
76     private shared size_t evtQueueCounter = 0;
77     private shared Event[size_t] evtQueue;
78 
79     this(GameManager game) @safe {
80         import core.cpuid;
81         this.game = game;
82         game.logger.logInfo("This CPU has " ~ to!string(coresPerCPU()) ~ " cores avaliable. Assigning one worker thread to each.");
83         this.pool = new ThreadPool(coresPerCPU()); 
84 
85         this.evtQueueLock = new shared SyncLock();
86         this.hookLock = new shared SyncLock();
87 
88         registerEventHook(EngineCleanupEvent.classinfo.name,
89             EventHook(&this.stop, false) 
90         );
91     }
92 
93     private void stop(Event e) @trusted {
94         pool.stopImmediate();
95     }
96 
97     /++
98         Adds an event to the firing queue. The event
99         will be fired on the next EventManager update
100         pass.
101     +/
102     void fireEvent(Event event) @trusted {
103         import core.atomic : atomicOp;
104         synchronized(this.evtQueueLock) {
105             this.evtQueue[atomicOp!"+="(this.evtQueueCounter, 1)] = cast(shared) event;
106         }
107     }
108 
109     void registerEventHook(in string eventType, EventHook hook) @trusted {
110         synchronized(this.hookLock) {
111             hooks[eventType] ~= cast(shared) hook;
112         }
113     }
114 
115     /// Update function called by GameManager
116     void update() @trusted {
117         synchronized(this.evtQueueLock) {
118             if(this.evtQueue.length < 1) return; // If the queue is empty, return
119 
120             size_t[] toRemove;
121             foreach(size_t key, shared(Event) event; this.evtQueue) {
122                 if(event.classinfo.name in hooks) { // Check if there is a hook(s) for the event type
123                     foreach(EventHook hook; hooks[event.classinfo.name]) {
124                         if(hook.runAsync) { // Check if we can run this in a worker
125                             pool.submitWork(() {
126                                 hook.hook(cast(Event) event);
127                             });
128                         } else {
129                             hook.hook(cast(Event) event);
130                         }
131                     }
132                 } else {
133                 }
134                 toRemove ~= key; // Add the event's key to the remove list.
135             }
136 
137             foreach(size_t key; toRemove) { // Remove all the processed events
138                 this.evtQueue.remove(key);
139             }
140         }
141     }
142 }