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.util;
36 
37 import std.conv;
38 import std.container.dlist;
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     /// The current tick the GameManager is on.
48     immutable ulong currentTick;
49 
50     this(in ulong currentTick) @safe nothrow pure {
51         this.currentTick = currentTick;
52     }
53 }
54 
55 /// Event that is fired when the GameManager.run() method is called.
56 class GameManagerStartEvent : Event {
57 
58 }
59 
60 /// Event that is fired when the engine begins to clean up.
61 class EngineCleanupEvent : Event {
62 
63 }
64 
65 /// Alias for a delegate used for an event hook.
66 alias HookDelegate = void delegate(Event evt) @system;
67 
68 /// Represents a hook that is called for a specific event.
69 struct EventHook {
70     /// The function delegate to be ran.
71     immutable HookDelegate hook;
72     /// If the delegate can be ran in a separate worker thread.
73     immutable bool runAsync = true;
74 }
75 
76 /// Handles all Event related activities.
77 class EventManager {
78 	__gshared {
79 		private ThreadPool pool;
80 		private GameManager game;
81 		
82 		private DList!Event eventQueue;
83 	}
84 	
85 	private shared SyncLock hookLock;
86 	private shared EventHook[][string] hooks;
87 	
88 	this(GameManager game) @trusted {
89 		import core.cpuid : coresPerCPU;
90 		
91 		this.game = game;
92 		this.game.logger.logInfo("This CPU has " ~ to!string(coresPerCPU()) ~ " cores avaliable. Assigning one worker thread to each.");
93 		this.pool = new ThreadPool(coresPerCPU());
94 		this.eventQueue = DList!Event();
95 		
96 		this.hookLock = new SyncLock();
97 		
98 		this.registerEventHook(EngineCleanupEvent.classinfo.name, EventHook(&this.stop, false));
99 	}
100 	
101 	
102 	private void stop(Event evt) @trusted {
103         this.pool.stopImmediate();
104     }
105 
106     /++
107         Adds an event to the firing queue. The event
108         will be fired on the next EventManager update
109         pass.
110 
111         Params:
112                 event =  The Event to be fired.    
113     +/
114     void fireEvent(Event event) @trusted {
115         this.eventQueue.insertBack(event);
116     }
117 
118     /++
119         Registers an EventHook for a specific Event.
120         This hook will be called when the event is fired.
121 
122         Params:
123                 eventType =     The event's full class name.
124                                 This is given by [EventClass].classinfo.name
125 
126                 hook =          The EventHook to be called when
127                                 the Event is fired. 
128     +/
129     void registerEventHook(in string eventType, EventHook hook) @trusted {
130         synchronized(this.hookLock) {
131             this.hooks[eventType] ~= cast(shared) hook;
132         }
133     }
134 
135     /// Update function called by GameManager
136     void update(TickEvent te, size_t limit = 25) @trusted {
137     	if(limit == 0) {
138     		limit = size_t.max;
139     	}
140 
141         bool a = false;
142 
143         if(te is null)
144             goto handleEvents;
145 
146         if(te.classinfo.name in this.hooks) {
147             foreach(EventHook hook; this.hooks[te.classinfo.name]) {
148                 // TODO: work around issue #8
149                 hook.hook(cast(Event) te);
150                 /*if(hook.runAsync) { // Check if we can run this in a worker
151                     pool.submitWork(() {
152                         hook.hook(cast(Event) te);
153                     }, te.classinfo.name);
154                 } else {
155                     hook.hook(cast(Event) te);
156                 }*/
157             }
158         }
159 
160 handleEvents:   
161         if(this.eventQueue.empty) return;
162 
163     	while(!this.eventQueue.empty && (limit-- > 0)) {
164             synchronized(this.hookLock) {
165                 Event event = this.eventQueue.front;
166                 this.eventQueue.removeFront();
167 
168                 if(event.classinfo.name in this.hooks) {
169                     foreach(EventHook hook; this.hooks[event.classinfo.name]) {
170                         if(hook.runAsync) { // Check if we can run this in a worker
171                             pool.submitWork(() {
172                                 /*debug {
173                                     import std.stdio;
174                                     writeln("Executing: ", event.classinfo.name);
175                                 }*/
176                                 hook.hook(cast(Event) event);
177                             }, event.classinfo.name);
178                         } else {
179                             hook.hook(cast(Event) event);
180                         }
181                     }
182                 }
183             }
184     	}
185     }
186 }