diff --git a/README.md b/README.md index c733438..a67e9c3 100644 --- a/README.md +++ b/README.md @@ -12,45 +12,44 @@ Eventy ### The _engine_ The first thing every Eventy-based application will need is an instance of the `Engine`. -This provides the user with the basic event-loop functionality that eventy provides. It's -the core of the whole framework that exists to have event-triggers ingested into its -_queues_, checking those _queues_ and one by one dispatching each _signal handler_ that -is associated with each queue on each item in the queue. +This provides the user with a single object instance of the [`Engine` class]() by which +the user can register _event types_, _signal handlers_ for said events and the ability +to trigger or _push_ events into the engine. The simplest way to get a new _engine_ up and running is as follow: ```d Engine engine = new Engine(); -engine.start(); ``` -This will create a new engine initializing all of its internals and then start it as well. +This will create a new engine initializing all of its internals such that it is ready for +use. -### Queues +### Event types -_Queues_ are as they sound, a list containing items. Each queue has a unique ID which we -can choose. The items of each queue will be the _events_ that are pushed into the _engine_. -An _event_ has an ID associated with it which tells the _engine_ which queue it must be -added to! +_Event types_ are effectively just numbers. The use of these is to be able to connect events +pushed into the engine with their respective signal handlers (which are registered to handle +one or more event types). -Let's create two queues, with IDs `1` and `2`: +Let's create two event types, with IDs `1` and `2`: ```d -engine.addQueue(1); -engine.addQueue(2); +engine.addEventType(new EventType(1)); +engine.addEventType(new EventType(2)); ``` -This will tell the engine to create two new queues with tags `1` and `2` respectively. +This will tell the engine to create two new event types with tags `1` and `2` respectively. -### Event handlers +### Signal handlers -We're almost done. So far we have created a new _engine_ for handling our queues and -the triggering of events. What is missing is something to _handle those queues_ when -they have something added to them, we call this an _"event handler"_ in computer science -but this is Eventy, and in Eventy this is known as a `Signal`. +We're almost done. So far we have created a new _engine_ for handling our event tyoes and +the triggering of events. What is missing is something to _handle those event types_ when +an event of one of those types is pushed into the engine. Such handlers are referred to as +_signal handlers_ and in Eventy these are instances of the [`Signal` class](). -We're going to create a signal that can handle both the queues and perform the same task -for both of them. We do this by creating a class that inherits from the `Signal` base type: +We're going to create a signal that can handle both of the event types `1` and `2` that we +registered earlier on. We can do this by creating a class that inherits from the `Signal` +base class: ```d class SignalHandler1 : Signal @@ -63,15 +62,15 @@ class SignalHandler1 : Signal public override void handler(Event e) { import std.stdio; - writeln("Running event", e.id); + writeln("Running event", e.getID()); } } ``` We need to tell the `Signal` class two things: -1. What _queue IDs_ it will handle -2. What to _run_ for said queues +1. What _event typess_ it will handle +2. What to _run_ for said event types --- @@ -92,7 +91,7 @@ two IDs, namely `1` and `2`. As for _what to run_, that is specified by overriding the `void handler(Event)` method in the `Signal` class. In our case we make it write to the console the ID of the event (which would end up either being `1` or `2` seeing as this handler is only registered -for those queue IDs). +for those event types). ```d import std.stdio; @@ -120,8 +119,6 @@ engine.push(eTest); eTest = new Event(2); engine.push(eTest); - - ``` You will then see something like this: @@ -138,6 +135,20 @@ Running event1 Running event2 ``` -The reason is it depends on which process gets shceduled by the Linux kernel first, this -is because new threads (special types of processes) are spanwed on the dispatch of each -event. +Despite us pushing the events into the engine in the order of `1` and _then_ `2`, the +scheduling of such threads is up to the Linux kernel and hence one could be run before +the other. + +--- + +## Release notes + +### `v0.4.0` + +``` +Completely overhauled Eventy system for the v0.4.0 release + +Removed the event-loop for a better system (for now) whereby we just dispatch signal handlers on the call to `push(Event)`. + +In a future release I hope to bring the event loop back but in a signal-based manner, such that we can support deferred events and priorities and such +``` \ No newline at end of file diff --git a/source/eventy/engine.d b/source/eventy/engine.d index 6a5c5c6..cf29e08 100644 --- a/source/eventy/engine.d +++ b/source/eventy/engine.d @@ -1,6 +1,6 @@ module eventy.engine; -import eventy.queues : Queue; +import eventy.types : EventType; import eventy.signal : Signal; import eventy.event : Event; import eventy.config; @@ -9,20 +9,20 @@ import eventy.exceptions; import std.container.dlist; import core.sync.mutex : Mutex; import core.thread : Thread, dur, Duration; +import std.conv : to; unittest { import std.stdio; Engine engine = new Engine(); - engine.start(); /** * Let the event engine know what typeIDs are * allowed to be queued */ - engine.addQueue(1); - engine.addQueue(2); + engine.addEventType(new EventType(1)); + engine.addEventType(new EventType(2)); /** * Create a new Signal Handler that will handles @@ -38,7 +38,7 @@ unittest public override void handler(Event e) { - writeln("Running event", e.id); + writeln("Running event", e.getID()); } } @@ -60,7 +60,7 @@ unittest writeln("done with main thread code"); - while(engine.hasPendingEvents()) {} + while(engine.hasEventsRunning()) {} /* TODO: Before shutting down, actually test it out (i.e. all events ran) */ engine.shutdown(); @@ -72,14 +72,13 @@ unittest EngineSettings customSettings = {holdOffMode: HoldOffMode.YIELD}; Engine engine = new Engine(customSettings); - engine.start(); /** * Let the event engine know what typeIDs are * allowed to be queued */ - engine.addQueue(1); - engine.addQueue(2); + engine.addEventType(new EventType(1)); + engine.addEventType(new EventType(2)); /** * Create a new Signal Handler that will handles @@ -95,7 +94,7 @@ unittest public override void handler(Event e) { - writeln("Running event", e.id); + writeln("Running event", e.getID()); } } @@ -117,7 +116,7 @@ unittest writeln("done with main thread code"); - while(engine.hasPendingEvents()) {} + while(engine.hasEventsRunning()) {} /* TODO: Before shutting down, actually test it out (i.e. all events ran) */ engine.shutdown(); @@ -133,14 +132,12 @@ unittest * handlers, add signal handlers, among many * other things */ -public final class Engine : Thread +public final class Engine { - /* TODO: Or use a queue data structure */ /* Registered queues */ - private DList!(Queue) queues; - private Mutex queueLock; + private DList!(EventType) eventTypes; + private Mutex eventTypesLock; - /* TODO: Or use a queue data structure */ /* Registered signal handlers */ private DList!(Signal) handlers; private Mutex handlerLock; @@ -164,9 +161,7 @@ public final class Engine : Thread */ this(EngineSettings settings) { - super(&run); - - queueLock = new Mutex(); + eventTypesLock = new Mutex(); handlerLock = new Mutex(); threadStoreLock = new Mutex(); @@ -238,74 +233,6 @@ public final class Engine : Thread handlerLock.unlock(); } - /** - * The main event loop - * - * This checks at a certain interval (see HoldOffMode) if - * there are any events in any of the queues, if so, - * the dispatcher for said event type is called - */ - private void run() - { - running = true; - - while (running) - { - /** - * Lock the queue-set - * - * TODO: Maybe add sleep support here too? - */ - while (!queueLock.tryLock_nothrow()) - { - // Don't waste time spinning on mutex, yield if failed - if(!settings.agressiveTryLock) - { - yield(); - } - } - - foreach (Queue queue; queues) - { - /* If the queue has evenets queued */ - if (queue.hasEvents()) - { - /* TODO: Add different dequeuing techniques */ - - /* Pop the first Event */ - Event headEvent = queue.popEvent(); - - /* Get all signal-handlers for this event type */ - Signal[] handlersMatched = getSignalsForEvent(headEvent); - - /* Dispatch the signal handlers */ - dispatch(handlersMatched, headEvent); - - } - } - - /* Unlock the queue set */ - queueLock.unlock(); - - /* Activate hold off (dependening on the type) */ - if(settings.holdOffMode == HoldOffMode.YIELD) - { - /* Yield to stop mutex starvation */ - yield(); - } - else if(settings.holdOffMode == HoldOffMode.SLEEP) - { - /* Sleep the thread (for given time) to stop mutex starvation */ - sleep(settings.sleepTime); - } - else - { - /* This should never happen */ - assert(false); - } - } - } - /** * Shuts down the event engine */ @@ -319,11 +246,8 @@ public final class Engine : Thread /* Wait for any pendings events (if configured) */ if(settings.gracefulShutdown) { - while(hasPendingEvents()) {} + while(hasEventsRunning()) {} } - - /* Stop the loop */ - running = false; } /** @@ -335,12 +259,9 @@ public final class Engine : Thread */ private void dispatch(Signal[] signalSet, Event e) { - /* TODO: Add ability to dispatch on this thread */ - foreach (Signal signal; signalSet) { /* Create a new Thread */ - // Thread handlerThread = getThread(signal, e); DispatchWrapper handlerThread = new DispatchWrapper(signal, e); /** @@ -407,6 +328,29 @@ public final class Engine : Thread threadStoreLock.unlock(); } + /** + * Checks whether or not there are still events + * running at the time of calling + * + * Returns: true if there are events + * still running, false otherwise + */ + public bool hasEventsRunning() + { + /* Whether there are events running or not */ + bool has = false; + + /* Lock the thread store */ + threadStoreLock.lock(); + + has = !threadStore.empty(); + + /* Unlock the thread store */ + threadStoreLock.unlock(); + + return has; + } + /** * DispatchWrapper * @@ -455,7 +399,7 @@ public final class Engine : Thread /* Find all handlers matching */ foreach (Signal signal; handlers) { - if (signal.handles(e.id)) + if (signal.handles(e.getID())) { matchedHandlers ~= signal; } @@ -473,7 +417,8 @@ public final class Engine : Thread * * Params: * id = the event ID to check - * Returns: + * Returns: true if a signal handler does + * exist, false otherwise */ public bool isSignalExists(ulong id) { @@ -489,78 +434,100 @@ public final class Engine : Thread */ public void push(Event e) { - Queue matchedQueue = findQueue(e.id); + //TODO: New code goes below here + /** + * What we want to do here is to effectively + * wake up a checker thread and also (before that) + * perhaps we say what queue was modified + * + * THEN the checker thread goes to said queue and + * executes said event (dispatches it) and then sleep + * again till it is interrupted. We need Pids and kill etc for this + * + * Idea (2) + * + * If we cannot do a checker thread then we can spwan a thread here + * but then we get no control for priorities etc, although actually we could + * maybe? It depends, we don't want multiple dispathers at same time then + * (A checker thread would ensure we don't get this) + */ - if (matchedQueue) + /* Obtain all signal handlers for the given event */ + Signal[] handlersMatched = getSignalsForEvent(e); + + /* If we get signal handlers then dispatch them */ + if(handlersMatched.length) { - /* Append to the queue */ - matchedQueue.add(e); + dispatch(handlersMatched, e); + } + /* If there are no matching events */ + else + { + //TODO: Add default handler support + //TODO: Add error throwing in case where not true } } /** - * Creates a new queue with the given id + * Registers a new EventType with the engine * and then adds it. * - * Throws EventyException if the id is already - * in use by another queue + * Throws EventyException if the id of the given + * EventType is is already in use by another * * Params: - * id = the id of the neq eueue to create + * id = the id of the new event type to add * Throws: EventyException */ - public void addQueue(ulong id) + public void addEventType(EventType evType) { - /* Create a new queue with the given id */ - Queue newQueue = new Queue(id); - - /* Lock the queue collection */ - queueLock.lock(); + /* Lock the event types list */ + eventTypesLock.lock(); /* If no such queue exists then add it (recursive mutex used) */ - if (!findQueue(id)) + if (!findEventType(evType.getID())) { - /* Add the queue */ - queues ~= newQueue; + /* Add the event types list */ + eventTypes ~= evType; } else { - throw new EventyException("Failure to add queue with ID already in use"); + throw new EventyException("Failure to add EventType with id '"~to!(string)(evType.getID())~"\' as it is already in use"); } - /* Unlock the queue collection */ - queueLock.unlock(); + /* Unlock the event types list */ + eventTypesLock.unlock(); } /** - * Given an if, this will return the Queue + * Given an if, this will return the EventType * associated with said id * * Params: - * id = the id of the Queue - * Returns: The Queue if found, otherwise + * id = the id of the EventType + * Returns: The EventType if found, otherwise * null */ - public Queue findQueue(ulong id) + public EventType findEventType(ulong id) { - /* Lock the queue collection */ - queueLock.lock(); + /* Lock the EventType list */ + eventTypesLock.lock(); - /* Find the matching queue */ - Queue matchedQueue; - foreach (Queue queue; queues) + /* Find the matching EventType */ + EventType matchedEventType; + foreach (EventType eventType; eventTypes) { - if (queue.id == id) + if (eventType.getID() == id) { - matchedQueue = queue; + matchedEventType = eventType; break; } } - /* Unlock the queue collection */ - queueLock.unlock(); + /* Unlock the EventType list */ + eventTypesLock.unlock(); - return matchedQueue; + return matchedEventType; } /* TODO: Add coumentation */ @@ -569,34 +536,4 @@ public final class Engine : Thread /* TODO: Implement me */ return null; } - - - /** - * Checks if any of the queues in the event engine - * have any pending events in them waiting dispatch - * - * Returns: true if there are pending events, - * false otherwise - */ - public bool hasPendingEvents() - { - bool isPending = false; - - /* Lock the queues */ - queueLock.lock(); - - foreach (Queue queue; queues) - { - if (queue.hasEvents()) - { - isPending = true; - break; - } - } - - /* Unlock the queues */ - queueLock.unlock(); - - return isPending; - } } \ No newline at end of file diff --git a/source/eventy/event.d b/source/eventy/event.d index 4f8781c..e3f1f88 100644 --- a/source/eventy/event.d +++ b/source/eventy/event.d @@ -5,23 +5,30 @@ module eventy.event; * * An Event represents a trigger for a given signal(s) * handlers which associate with the given typeID - * - * It can optionally take a payload with it as well */ public class Event { - /** - * Creates a new Event, optionally taking with is a - * payload - */ - this(ulong typeID, ubyte[] payload = null) + /* The event's type id */ + private ulong id; + + /** + * Creates a new Event with the given typeID + * + * Params: + * typeID = the new Event's type ID + */ + this(ulong typeID) { this.id = typeID; - this.payload = payload; } - ulong id; - ubyte[] payload; - - // TODO: Remove the requirement for the payload + /** + * Returns the type ID of this Event + * + * Returns: The Event's type ID + */ + public final ulong getID() + { + return id; + } } diff --git a/source/eventy/package.d b/source/eventy/package.d index d11f790..c5d0926 100644 --- a/source/eventy/package.d +++ b/source/eventy/package.d @@ -3,6 +3,6 @@ module eventy; public import eventy.event; public import eventy.exceptions; public import eventy.engine; -public import eventy.queues; +public import eventy.types; public import eventy.signal; public import eventy.config; \ No newline at end of file diff --git a/source/eventy/queues.d b/source/eventy/queues.d deleted file mode 100644 index de72426..0000000 --- a/source/eventy/queues.d +++ /dev/null @@ -1,75 +0,0 @@ -module eventy.queues; - -import eventy.event : Event; -import core.sync.mutex : Mutex; -import std.container.dlist; -import std.range; - -/** -* Queue -* -* Represents a queue with a given ID that can -* have Event-s enqueued to it -*/ -public final class Queue -{ - public ulong id; - /* TODO: Add queue of Event's here */ - - private DList!(Event) queue; - private Mutex queueLock; - - - this(ulong id) - { - this.id = id; - queueLock = new Mutex(); - } - - public DList!(Event).Range getKak() - { - return queue[]; - } - - public void add(Event e) - { - /* Lock the queue */ - queueLock.lock(); - - queue.insert(e); - - /* Unlock the queue */ - queueLock.unlock(); - } - - public bool hasEvents() - { - bool has; - - /* Lock the queue */ - queueLock.lock(); - - has = !(queue[]).empty(); - - /* Unlock the queue */ - queueLock.unlock(); - - return has; - } - - public Event popEvent() - { - Event poppedEvent; - - /* Lock the queue */ - queueLock.lock(); - - poppedEvent = (queue[]).front(); - queue.removeFront(); - - /* Unlock the queue */ - queueLock.unlock(); - - return poppedEvent; - } -} \ No newline at end of file diff --git a/source/eventy/types.d b/source/eventy/types.d new file mode 100644 index 0000000..e35a620 --- /dev/null +++ b/source/eventy/types.d @@ -0,0 +1,40 @@ +module eventy.types; + +import eventy.event : Event; +import core.sync.mutex : Mutex; +import std.container.dlist; +import std.range; + +/** +* EventType +* +* Represents a type of event. Every Event has an EventType +* and Signal(s)-handlers register to one or more of these +* types to handle +*/ +public final class EventType +{ + /* The EventType's ID */ + private ulong id; + + /** + * Instantiates a new EventType with the given id + * + * Params: + * id = The EventType's id + */ + this(ulong id) + { + this.id = id; + } + + /** + * Returns the id of this EventType + * + * Returns: The id of this EventType + */ + public ulong getID() + { + return id; + } +} \ No newline at end of file