Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,120 @@
|
||||
# Event Bus
|
||||
|
||||
A module to provide eventing capabilities using pub/sub.
|
||||
|
||||
## API
|
||||
|
||||
1. `new[T]()` - create a new `EventBus`
|
||||
2. `EventBus.new[T]()` - create a new `EventBus`
|
||||
|
||||
### Structs:
|
||||
|
||||
**EventBus:**
|
||||
|
||||
1. `publish(name T, sender voidptr, args voidptr)` - publish an event with provided
|
||||
Params & name
|
||||
2. `clear_all()` - clear all subscribers
|
||||
3. `has_subscriber(name T)` - check if a subscriber to an event exists
|
||||
|
||||
**Subscriber:**
|
||||
|
||||
1. `subscribe(name T, handler EventHandlerFn)` - subscribe to an event
|
||||
2. `subscribe_once(name T, handler EventHandlerFn)` - subscribe only once to an event
|
||||
3. `subscribe_method(name T, handler EventHandlerFn, receiver voidptr)` - subscribe to
|
||||
an event and also set the `receiver` as a parameter.
|
||||
Since it's not yet possible to send methods as parameters, this is a workaround.
|
||||
4. `is_subscribed(name T)` - check if we are subscribed to an event
|
||||
5. `unsubscribe(name T)` - unsubscribe from an event
|
||||
|
||||
**Event Handler Signature:**
|
||||
|
||||
The function given to `subscribe`, `subscribe_method` and `subscribe_once` must match this:
|
||||
|
||||
```v oksyntax
|
||||
fn cb(receiver voidptr, args voidptr, sender voidptr) {
|
||||
}
|
||||
|
||||
// Since V can map structs to voidptr, this also works
|
||||
struct ClickEvent {
|
||||
x int
|
||||
y int
|
||||
}
|
||||
|
||||
// Example case where publisher sends ClickEvent as args.
|
||||
fn on_press(receiver voidptr, e &ClickEvent, sender voidptr) {
|
||||
println(e.x)
|
||||
// your code here...
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
For **usage across modules**
|
||||
[check the example](https://github.com/vlang/v/tree/master/examples/eventbus).
|
||||
|
||||
> **Note**
|
||||
> As a general rule, you will need to **subscribe before publishing**.
|
||||
|
||||
**main.v**
|
||||
|
||||
```v oksyntax
|
||||
module main
|
||||
|
||||
import eventbus
|
||||
|
||||
// initialize it globally
|
||||
const eb = eventbus.new[string]()
|
||||
|
||||
fn main() {
|
||||
// get a mutable reference to the subscriber
|
||||
mut sub := eb.subscriber
|
||||
// subscribe to the 'error' event
|
||||
sub.subscribe('error', on_error)
|
||||
// start the work
|
||||
do_work()
|
||||
}
|
||||
|
||||
// the event handler
|
||||
fn on_error(receiver voidptr, e &AnError, work &Work) {
|
||||
println('error occurred on ${work.hours}. Error: ${e.message}')
|
||||
}
|
||||
```
|
||||
|
||||
**work.v**
|
||||
|
||||
```v oksyntax
|
||||
module main
|
||||
|
||||
import eventbus
|
||||
|
||||
struct Work {
|
||||
hours int
|
||||
}
|
||||
|
||||
struct AnError {
|
||||
message string
|
||||
}
|
||||
|
||||
fn do_work() {
|
||||
work := Work{20}
|
||||
// get a mutable Params instance & put some data into it
|
||||
error := &AnError{'Error: no internet connection.'}
|
||||
// publish the event
|
||||
eb.publish('error', work, error)
|
||||
}
|
||||
```
|
||||
|
||||
### Notes:
|
||||
|
||||
1. Each `EventBus` instance has its own registry (i.e. there is no global event registry
|
||||
so you can't just subscribe to an event wherever you are.
|
||||
2. Each `EventBus` has a `Subscriber` instance which will need to be either exposed or you can make
|
||||
small public helper functions specific to your module like (`onPress`, `onError`) and etc.
|
||||
3. The `eventbus` module has some helpers to ease getting/setting of Params
|
||||
(since V doesn't support empty interfaces yet or reflection) so use them (see usage above).
|
||||
|
||||
**The rationale behind separating Subscriber & Publisher:**
|
||||
|
||||
This is mainly for security because if publisher & subscriber are both passed around,
|
||||
a client can easily publish events acting as the server.
|
||||
So a client should only be able to use the Subscriber methods.
|
||||
@@ -0,0 +1,154 @@
|
||||
module eventbus
|
||||
|
||||
pub type EventHandlerFn = fn (receiver voidptr, args voidptr, sender voidptr)
|
||||
|
||||
pub struct Publisher[T] {
|
||||
mut:
|
||||
registry &Registry[T] = unsafe { nil }
|
||||
}
|
||||
|
||||
pub struct Subscriber[T] {
|
||||
mut:
|
||||
registry &Registry[T] = unsafe { nil }
|
||||
}
|
||||
|
||||
pub struct Registry[T] {
|
||||
mut:
|
||||
events []EventHandler[T]
|
||||
}
|
||||
|
||||
pub struct EventHandler[T] {
|
||||
name T
|
||||
handler EventHandlerFn = unsafe { nil }
|
||||
receiver voidptr = unsafe { nil }
|
||||
once bool
|
||||
}
|
||||
|
||||
pub struct EventBus[T] {
|
||||
pub mut:
|
||||
registry &Registry[T] = unsafe { nil }
|
||||
publisher &Publisher[T] = unsafe { nil }
|
||||
subscriber &Subscriber[T] = unsafe { nil }
|
||||
}
|
||||
|
||||
// EventBus.new[T] create a new eventbus with event type T.
|
||||
pub fn EventBus.new[T]() &EventBus[T] {
|
||||
registry := &Registry[T]{
|
||||
events: []
|
||||
}
|
||||
return &EventBus[T]{registry, &Publisher[T]{registry}, &Subscriber[T]{registry}}
|
||||
}
|
||||
|
||||
// new[T] create a new eventbus with event type T.
|
||||
pub fn new[T]() &EventBus[T] {
|
||||
registry := &Registry[T]{
|
||||
events: []
|
||||
}
|
||||
return &EventBus[T]{registry, &Publisher[T]{registry}, &Subscriber[T]{registry}}
|
||||
}
|
||||
|
||||
// publish publishes an event with provided Params & name.
|
||||
pub fn (eb &EventBus[T]) publish(name T, sender voidptr, args voidptr) {
|
||||
mut publisher := eb.publisher
|
||||
publisher.publish(name, sender, args)
|
||||
}
|
||||
|
||||
// clear_all clears all subscribers.
|
||||
pub fn (eb &EventBus[T]) clear_all() {
|
||||
mut publisher := eb.publisher
|
||||
publisher.clear_all()
|
||||
}
|
||||
|
||||
// has_subscriber check if a subscriber to an event exists.
|
||||
pub fn (eb &EventBus[T]) has_subscriber(name T) bool {
|
||||
return eb.registry.check_subscriber(name)
|
||||
}
|
||||
|
||||
const dedup_buffer_len = 20
|
||||
|
||||
// publish an event with provided Params & name.
|
||||
fn (mut pb Publisher[T]) publish(name T, sender voidptr, args voidptr) {
|
||||
// println('Publisher.publish(name=${name} sender=${sender} args=${args})')
|
||||
invalid := 0
|
||||
mut handled_receivers := unsafe { [dedup_buffer_len]voidptr{init: &invalid} } // handle duplicate bugs TODO fix properly + perf
|
||||
mut j := 0
|
||||
mut found_onces := 0
|
||||
for event in pb.registry.events {
|
||||
if event.name == name {
|
||||
if event.once {
|
||||
found_onces++
|
||||
}
|
||||
if event.receiver in handled_receivers {
|
||||
continue
|
||||
}
|
||||
event.handler(event.receiver, args, sender)
|
||||
handled_receivers[j] = event.receiver
|
||||
j = (j + 1) % dedup_buffer_len
|
||||
}
|
||||
}
|
||||
if found_onces > 0 {
|
||||
pb.registry.events = pb.registry.events.filter(!(it.name == name && it.once))
|
||||
}
|
||||
}
|
||||
|
||||
// clear_all clear all subscribers.
|
||||
fn (mut p Publisher[T]) clear_all() {
|
||||
p.registry.events.clear()
|
||||
}
|
||||
|
||||
// subscribe subscribe to an event `name`.
|
||||
pub fn (mut s Subscriber[T]) subscribe(name T, handler EventHandlerFn) {
|
||||
s.registry.events << EventHandler[T]{
|
||||
name: name
|
||||
handler: handler
|
||||
}
|
||||
}
|
||||
|
||||
// subscribe_method subscribe to an event `name` and also set the `receiver` as a parameter.
|
||||
pub fn (mut s Subscriber[T]) subscribe_method(name T, handler EventHandlerFn, receiver voidptr) {
|
||||
s.registry.events << EventHandler[T]{
|
||||
name: name
|
||||
handler: handler
|
||||
receiver: receiver
|
||||
}
|
||||
}
|
||||
|
||||
// unsubscribe_method unsubscribe a receiver for only one method.
|
||||
pub fn (mut s Subscriber[T]) unsubscribe_method(name T, receiver voidptr) {
|
||||
s.registry.events = s.registry.events.filter(!(it.name == name && it.receiver == receiver))
|
||||
}
|
||||
|
||||
// unsubscribe_receiver unsubscribes a receiver from all events.
|
||||
pub fn (mut s Subscriber[T]) unsubscribe_receiver(receiver voidptr) {
|
||||
s.registry.events = s.registry.events.filter(it.receiver != receiver)
|
||||
}
|
||||
|
||||
// subscribe_once subscribe only once to an event `name`.
|
||||
pub fn (mut s Subscriber[T]) subscribe_once(name T, handler EventHandlerFn) {
|
||||
s.registry.events << EventHandler[T]{
|
||||
name: name
|
||||
handler: handler
|
||||
once: true
|
||||
}
|
||||
}
|
||||
|
||||
// is_subscribed check if we are subscribed to an event `name`.
|
||||
pub fn (s &Subscriber[T]) is_subscribed(name T) bool {
|
||||
return s.registry.check_subscriber(name)
|
||||
}
|
||||
|
||||
// is_subscribed_method checks whether a receiver was already subscribed for any events.
|
||||
pub fn (s &Subscriber[T]) is_subscribed_method(name T, receiver voidptr) bool {
|
||||
return s.registry.events.any(it.name == name && it.receiver == receiver)
|
||||
}
|
||||
|
||||
// unsubscribe unsubscribe from an event `name`.
|
||||
pub fn (mut s Subscriber[T]) unsubscribe(name T, handler EventHandlerFn) {
|
||||
// v := voidptr(handler)
|
||||
s.registry.events = s.registry.events.filter(!(it.name == name && it.handler == handler))
|
||||
}
|
||||
|
||||
// Registry Methods
|
||||
fn (r &Registry[T]) check_subscriber(name T) bool {
|
||||
return r.events.any(it.name == name)
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
@[has_globals]
|
||||
module main
|
||||
|
||||
import eventbus
|
||||
|
||||
struct EventData {
|
||||
data string
|
||||
n int
|
||||
}
|
||||
|
||||
struct FakeReceiver {
|
||||
ok bool
|
||||
}
|
||||
|
||||
fn test_eventbus_string() {
|
||||
ev_data := &EventData{'hello', 1}
|
||||
mut eb := eventbus.new[string]()
|
||||
eb.subscriber.subscribe_once('on_test', on_test_without_receiver)
|
||||
assert eb.has_subscriber('on_test')
|
||||
assert !eb.has_subscriber('not_exist')
|
||||
assert eb.subscriber.is_subscribed('on_test')
|
||||
eprintln('> publishing to on_test 1')
|
||||
reset_ncalls()
|
||||
eb.publish('on_test', eb, ev_data)
|
||||
assert calls_of_on_test_without_receiver == 1
|
||||
reset_ncalls()
|
||||
assert !eb.has_subscriber('on_test')
|
||||
assert !eb.subscriber.is_subscribed('on_test')
|
||||
// subscribe 3 times more:
|
||||
eb.subscriber.subscribe('on_test', on_test_without_receiver)
|
||||
eb.subscriber.subscribe('on_test', on_test_without_receiver)
|
||||
eb.subscriber.subscribe('on_test', on_test_without_receiver)
|
||||
assert eb.has_subscriber('on_test')
|
||||
assert eb.subscriber.is_subscribed('on_test')
|
||||
eprintln('> publishing to on_test 2')
|
||||
reset_ncalls()
|
||||
eb.publish('on_test', eb, &EventData{'hello', 2})
|
||||
assert calls_of_on_test_without_receiver == 1, 'even though we had more than 1 subscription, to the same event, the callback should be called just once because of the deduplication'
|
||||
reset_ncalls()
|
||||
eb.clear_all()
|
||||
assert !eb.has_subscriber('on_test')
|
||||
assert !eb.subscriber.is_subscribed('on_test')
|
||||
// check that unsubscription actually remove the handler:
|
||||
reset_ncalls()
|
||||
eb.publish('on_test', eb, &EventData{'hello', 3})
|
||||
assert calls_of_on_test_without_receiver == 0
|
||||
reset_ncalls()
|
||||
}
|
||||
|
||||
enum Events {
|
||||
event_1
|
||||
event_2
|
||||
event_3
|
||||
}
|
||||
|
||||
fn test_eventbus_enum() {
|
||||
reset_ncalls()
|
||||
ev_data := &EventData{'hello', 1}
|
||||
mut eb := eventbus.EventBus.new[Events]()
|
||||
eprintln('> subscribing once on_test for Events enum bus ...')
|
||||
eb.subscriber.subscribe_once(Events.event_1, on_test_without_receiver)
|
||||
assert eb.has_subscriber(Events.event_1)
|
||||
assert !eb.has_subscriber(Events.event_2)
|
||||
assert eb.subscriber.is_subscribed(Events.event_1)
|
||||
eprintln('> publishing even_1 ...')
|
||||
eb.publish(Events.event_1, eb, ev_data)
|
||||
assert !eb.has_subscriber(Events.event_1)
|
||||
assert !eb.subscriber.is_subscribed(Events.event_1)
|
||||
eb.subscriber.subscribe(Events.event_1, on_test_without_receiver)
|
||||
assert eb.has_subscriber(Events.event_1)
|
||||
assert eb.subscriber.is_subscribed(Events.event_1)
|
||||
eb.clear_all()
|
||||
assert !eb.has_subscriber(Events.event_1)
|
||||
assert !eb.subscriber.is_subscribed(Events.event_1)
|
||||
|
||||
assert calls_of_on_test_without_receiver == 1
|
||||
assert call_of_on_test_with_receiver == 0
|
||||
}
|
||||
|
||||
fn test_eventbus_int() {
|
||||
reset_ncalls()
|
||||
ev_data := &EventData{'hello', 1}
|
||||
mut eb := eventbus.EventBus.new[int]()
|
||||
eprintln('> subscribing once on_test for int bus ...')
|
||||
eb.subscriber.subscribe_once(9999, on_test_without_receiver)
|
||||
assert eb.has_subscriber(9999)
|
||||
assert !eb.has_subscriber(1111)
|
||||
assert eb.subscriber.is_subscribed(9999)
|
||||
eb.publish(9999, eb, ev_data)
|
||||
assert !eb.has_subscriber(9999)
|
||||
assert !eb.subscriber.is_subscribed(9999)
|
||||
eb.subscriber.subscribe(9999, on_test_without_receiver)
|
||||
assert eb.has_subscriber(9999)
|
||||
assert eb.subscriber.is_subscribed(9999)
|
||||
eb.clear_all()
|
||||
assert !eb.has_subscriber(9999)
|
||||
assert !eb.subscriber.is_subscribed(9999)
|
||||
|
||||
assert calls_of_on_test_without_receiver == 1
|
||||
assert call_of_on_test_with_receiver == 0
|
||||
}
|
||||
|
||||
fn test_subscribe_method() {
|
||||
reset_ncalls()
|
||||
// given
|
||||
mut eb := eventbus.new[string]()
|
||||
r := FakeReceiver{}
|
||||
assert !eb.subscriber.is_subscribed_method('on_test_with_receiver', r)
|
||||
// when
|
||||
eb.subscriber.subscribe_method('on_test_with_receiver', on_test_with_receiver, r)
|
||||
// then
|
||||
assert eb.subscriber.is_subscribed_method('on_test_with_receiver', r)
|
||||
|
||||
assert calls_of_on_test_without_receiver == 0
|
||||
assert call_of_on_test_with_receiver == 0
|
||||
}
|
||||
|
||||
fn test_unsubscribe_method() {
|
||||
reset_ncalls()
|
||||
// given
|
||||
mut eb := eventbus.new[string]()
|
||||
r := FakeReceiver{}
|
||||
r2 := FakeReceiver{}
|
||||
// when
|
||||
eb.subscriber.subscribe_method('on_test_with_receiver', on_test_with_receiver, r)
|
||||
eb.subscriber.subscribe_method('on_test_with_receiver', on_test_with_receiver, r2)
|
||||
eb.subscriber.unsubscribe_method('on_test_with_receiver', r)
|
||||
// then
|
||||
assert !eb.subscriber.is_subscribed_method('on_test_with_receiver', r)
|
||||
assert eb.subscriber.is_subscribed_method('on_test_with_receiver', r2)
|
||||
|
||||
assert calls_of_on_test_without_receiver == 0
|
||||
assert call_of_on_test_with_receiver == 0
|
||||
}
|
||||
|
||||
fn test_publish() {
|
||||
reset_ncalls()
|
||||
// given
|
||||
ev_data := &EventData{'hello', 1}
|
||||
mut eb := eventbus.new[string]()
|
||||
r1 := FakeReceiver{}
|
||||
r2 := FakeReceiver{}
|
||||
// when
|
||||
eb.subscriber.subscribe_once('on_test', on_test_without_receiver)
|
||||
eprintln('> publishing once 1 ...')
|
||||
eb.publish('on_test', eb, ev_data)
|
||||
// then
|
||||
assert !eb.subscriber.is_subscribed('on_test')
|
||||
assert calls_of_on_test_without_receiver == 1
|
||||
|
||||
eb.subscriber.subscribe_method('on_test', on_test_with_receiver, r1) // subscribe r1 3 times
|
||||
eb.subscriber.subscribe_method('on_test', on_test_with_receiver, r1)
|
||||
eb.subscriber.subscribe_method('on_test', on_test_with_receiver, r1)
|
||||
eb.subscriber.subscribe_method('on_test', on_test_with_receiver, r2) // subscribe r2 3 times
|
||||
eb.subscriber.subscribe_method('on_test', on_test_with_receiver, r2)
|
||||
eb.subscriber.subscribe_method('on_test', on_test_with_receiver, r2)
|
||||
eb.publish('on_test', eb, &EventData{'hello', 123})
|
||||
|
||||
assert calls_of_on_test_without_receiver == 1 // this should not change, since the subscription was done through subscribe_once.
|
||||
assert call_of_on_test_with_receiver == 2 // make sure that even though we subscribed several times, only 2 calls are made (once per receiver)
|
||||
}
|
||||
|
||||
fn test_publish_with_receiver() {
|
||||
reset_ncalls()
|
||||
// given
|
||||
mut eb := eventbus.new[string]()
|
||||
ev_data := &EventData{'hello', 1}
|
||||
r := FakeReceiver{}
|
||||
// when
|
||||
eb.subscriber.subscribe_method('on_test_with_receiver', on_test_with_receiver, r)
|
||||
eb.publish('on_test_with_receiver', eb, ev_data)
|
||||
|
||||
assert calls_of_on_test_without_receiver == 0
|
||||
assert call_of_on_test_with_receiver == 1
|
||||
}
|
||||
|
||||
fn test_unsubscribe_reveiver() {
|
||||
reset_ncalls()
|
||||
// given
|
||||
mut eb := eventbus.new[string]()
|
||||
r := &FakeReceiver{}
|
||||
// when
|
||||
eb.subscriber.subscribe_method('on_test_with_receiver', on_test_with_receiver, r)
|
||||
eb.subscriber.subscribe_method('on_test', on_test_without_receiver, r)
|
||||
eb.subscriber.unsubscribe_receiver(r)
|
||||
assert !eb.subscriber.is_subscribed_method('on_test_with_receiver', r)
|
||||
assert !eb.subscriber.is_subscribed_method('on_test', r)
|
||||
|
||||
assert calls_of_on_test_without_receiver == 0
|
||||
assert call_of_on_test_with_receiver == 0
|
||||
}
|
||||
|
||||
fn on_test_without_receiver(receiver voidptr, ev &EventData, sender voidptr) {
|
||||
calls_of_on_test_without_receiver++
|
||||
eprintln('>> ${@LOCATION:-80s} | receiver: ${voidptr(receiver):16} | sender: ${sender:16} | ev.data: ${ev.data} | ev.n: ${ev.n}')
|
||||
assert receiver == 0
|
||||
assert sender != 0
|
||||
assert ev.data == 'hello'
|
||||
}
|
||||
|
||||
fn on_test_with_receiver(receiver &FakeReceiver, ev &EventData, sender voidptr) {
|
||||
call_of_on_test_with_receiver++
|
||||
eprintln('>> ${@LOCATION:-80s} | receiver: ${voidptr(receiver):16} | sender: ${sender:16} | ev.data: ${ev.data} | ev.n: ${ev.n}')
|
||||
assert receiver.ok == false
|
||||
assert sender != 0
|
||||
assert ev.data == 'hello'
|
||||
}
|
||||
|
||||
__global calls_of_on_test_without_receiver = 0
|
||||
__global call_of_on_test_with_receiver = 0
|
||||
|
||||
fn reset_ncalls() {
|
||||
calls_of_on_test_without_receiver = 0
|
||||
call_of_on_test_with_receiver = 0
|
||||
}
|
||||
Reference in new issue
Block a user