generated from supranim/starter
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: George Lemon <georgelemon@protonmail.com>
- Loading branch information
1 parent
c986c2c
commit 0708559
Showing
2 changed files
with
91 additions
and
84 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,107 +1,113 @@ | ||
# Supranim's Event Emitter - Subscribe & listen for | ||
# various events within your application. | ||
# | ||
# This package is part of Supranim Hyper Framework. | ||
# | ||
# Supranim is a simple MVC-style web framework for building | ||
# fast web applications, REST API microservices and other cool things. | ||
# | ||
# (c) 2021 Events is released under MIT License | ||
# Made by Humans from OpenPeep | ||
# | ||
# https://github.com/supranim | ||
# https://supranim.com | ||
|
||
import std/[tables, macros, typeinfo] | ||
import std/[tables, sequtils, macros, typeinfo] | ||
from std/strutils import count, startsWith | ||
|
||
# import std/threadpool | ||
# import pkg/malebolgia | ||
|
||
export tables, typeinfo | ||
|
||
type | ||
RunType* = enum | ||
Anytime, Once | ||
RunType* = enum | ||
Anytime, Once | ||
|
||
Arg* = ref object | ||
value*: Any | ||
|
||
Arg* = ref object | ||
value*: Any | ||
Callback* = proc(args: seq[Arg]) {.nimcall.} | ||
|
||
Callback* = proc(args: varargs[Arg] = @[]) {.nimcall.} | ||
Listener* = tuple[id: string, runCallable: Callback, runType: RunType] | ||
|
||
Listener* = tuple[id: string, runCallable: Callback, runType: RunType] | ||
EventEmitter = ref object | ||
## Main object that holds all events and listeners, | ||
## available as a Singleton as ``Event`` | ||
subscribers: Table[string, seq[Listener]] | ||
# m: Master | ||
|
||
EventEmitter = object | ||
## Main object that holds all events and listeners, | ||
## available as a Singleton as ``Event`` | ||
subscribers: Table[string, seq[Listener]] | ||
|
||
EventEmitterError = object of CatchableError | ||
EventEmitterError = object of CatchableError | ||
|
||
when compileOption("threads"): | ||
var Event* {.threadvar.}: EventEmitter | ||
else: | ||
var Event*: EventEmitter | ||
# when compileOption("threads"): | ||
# var Event* {.threadvar.}: EventEmitter | ||
# else: | ||
var Event*: EventEmitter | ||
|
||
proc init*[E: EventEmitter](e: var E) = | ||
Event = EventEmitter() | ||
proc init*(e: var EventEmitter) = | ||
Event = EventEmitter() | ||
|
||
template newArg*(argValue: auto): untyped = | ||
## Create a new Arg object instance based on | ||
## ``Any`` value from ``std/typeinfo`` | ||
var | ||
vany: Any | ||
val = argValue | ||
proc initNewArg(v: Any): Arg = | ||
return Arg(value: v) | ||
vany = toAny(val) | ||
initNewArg(vany) | ||
|
||
proc registerListener[E: EventEmitter](emitter: var E, key: string, handler: Callback, runType: RunType) = | ||
## Main proc for registering listeners to a specific events by ``key``. | ||
## TODO allow only lowercase keys, separated by dot, e.g. "user.update.account" | ||
# if count(listener.id, '*') != 1: | ||
# raise newException(EventEmitterError, "A key cannot contain more than one wildcard") # for now | ||
if not emitter.subscribers.hasKey(key): | ||
emitter.subscribers[key] = newSeq[Listener]() | ||
emitter.subscribers[key].add (id: key, runCallable: handler, runType: runType) | ||
|
||
template listen*[E: EventEmitter](emitter: var E, key: string, handler: Callback): untyped = | ||
## Subscribe to a specific event with a runnable callback. | ||
## | ||
## You may want to register a listener using the ``*`` | ||
## as a wildcard for ``key`` parameter. This allows you to | ||
## register same listener to multiple events. | ||
runnableExamples: | ||
Event.listen("account.update.email") do(args: varargs[Arg]): | ||
echo "Email address has been changed." | ||
|
||
Event.listen("account.*") do(args: varargs[Arg]): | ||
echo "Listening for any events related to `account.`" | ||
|
||
registerListener(emitter, key, handler, Anytime) | ||
|
||
template listenOnce*[E: EventEmitter](emitter: var E, key: string, handler: Callback): untyped = | ||
## Same as ``listen`` proc, the only difference is | ||
## that this listener can run only once | ||
registerListener(emitter, key, handler, Once) | ||
|
||
template emit*[E: EventEmitter](emitter: var E, eventId: string, args: varargs[Arg] = @[]): untyped = | ||
## Call an event by ``id`` and trigger all registered listeners | ||
## related to the event. You can pass one or more ``args`` | ||
## to listener callback using the ``newArg`` procedure. | ||
runnableExamples: | ||
Event.emit("account.update.email", newArg("new.address@example.com"), newArg("192.168.1.1")) | ||
|
||
if emitter.subscribers.hasKey(eventId): | ||
for i, listener in pairs(emitter.subscribers[eventId]): | ||
## Create a new Arg object instance based on | ||
## ``Any`` value from ``std/typeinfo`` | ||
var | ||
vany: Any | ||
val = argValue | ||
proc initNewArg(v: Any): Arg = | ||
return Arg(value: v) | ||
vany = toAny(val) | ||
initNewArg(vany) | ||
|
||
|
||
proc registerListener(emitter: var EventEmitter, key: string, handler: Callback, runType: RunType) = | ||
## Main proc for registering listeners to a specific events by ``key``. | ||
## TODO allow only lowercase keys, separated by dot, e.g. "user.update.account" | ||
# if count(listener.id, '*') != 1: | ||
# raise newException(EventEmitterError, "A key cannot contain more than one wildcard") # for now | ||
if not emitter.subscribers.hasKey(key): | ||
emitter.subscribers[key] = newSeq[Listener]() | ||
emitter.subscribers[key].add (id: key, runCallable: handler, runType: runType) | ||
|
||
proc listen*(emitter: var EventEmitter, key: string, handler: Callback) = | ||
## Subscribe to a specific event with a runnable callback. | ||
## | ||
## You may want to register a listener using the ``*`` | ||
## as a wildcard for ``key`` parameter. This allows you to | ||
## register same listener to multiple events. | ||
runnableExamples: | ||
Event.listen("account.update.email") do(args: seq[Arg]): | ||
echo "Email address has been changed." | ||
|
||
Event.listen("account.*") do(args: seq[Arg]): | ||
echo "Listening for any events related to `account.`" | ||
|
||
registerListener(emitter, key, handler, Anytime) | ||
|
||
template listenOnce*(emitter: var EventEmitter, key: string, handler: Callback) = | ||
## Same as ``listen`` proc, the only difference is | ||
## that this listener can run only once | ||
registerListener(emitter, key, handler, Once) | ||
|
||
proc runCallback(args: ptr seq[Arg], listen: ptr Listener) = | ||
{.gcsafe.}: | ||
listen[].runCallable(@[]) | ||
|
||
proc emit*(emitter: var EventEmitter, eventId: string, args: seq[Arg] = @[]) = | ||
## Call an event by ``id`` and trigger all registered listeners | ||
## related to the event. You can pass one or more ``args`` | ||
## to listener callback using the ``newArg`` procedure. | ||
runnableExamples: | ||
Event.emit("account.update.email", newArg("new.address@example.com"), newArg("192.168.1.1")) | ||
if emitter.subscribers.hasKey(eventId): | ||
for i, listener in pairs(emitter.subscribers[eventId]): | ||
let x = args | ||
# spawn runCallback(x.addr, listener.addr) | ||
runCallback(x.addr, listener.addr) | ||
if likely(listener.runType == Once): | ||
emitter.subscribers[eventId].delete(i) | ||
else: | ||
for subId, event in pairs(emitter.subscribers): | ||
if likely(subId[^1] == '*'): | ||
for i, listener in pairs(emitter.subscribers[subId]): | ||
if startsWith(eventId, subId[0 .. ^2]): | ||
listener.runCallable(args) | ||
if likely(listener.runType == Once): | ||
emitter.subscribers[eventId].delete(i) | ||
else: | ||
for subId, event in pairs(emitter.subscribers): | ||
if likely(subId[^1] == '*'): | ||
for i, listener in pairs(emitter.subscribers[subId]): | ||
if startsWith(eventId, subId[0 .. ^2]): | ||
listener.runCallable(args) | ||
if likely(listener.runType == Once): | ||
emitter.subscribers[subId].delete(i) | ||
else: continue | ||
else: continue | ||
if likely(listener.runType == Once): | ||
emitter.subscribers[subId].delete(i) | ||
else: continue | ||
else: continue |