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 }