improve eventbus

pull/3534/head
Abdullah Atta 2020-01-22 21:41:08 +05:00 committed by Alexander Medvednikov
parent 136c469ef7
commit 43ba6766ba
6 changed files with 115 additions and 274 deletions

View File

@ -2,7 +2,6 @@ module main
import ( import (
some_module some_module
eventbus
) )
fn main(){ fn main(){
@ -11,8 +10,6 @@ fn main(){
some_module.do_work() some_module.do_work()
} }
fn on_error(sender voidptr, p eventbus.Params) { fn on_error(sender voidptr, e &some_module.Error) {
work := *(*some_module.Work(sender)) println(e.message)
println(work.hours)
println(p.get_string("error"))
} }

View File

@ -13,19 +13,22 @@ pub struct Work {
hours int hours int
} }
pub struct Error {
pub:
message string
}
pub fn do_work(){ pub fn do_work(){
work := Work{20} work := Work{20}
mut params := eventbus.Params{}
for i in 0..20 { for i in 0..20 {
println("working...") println("working...")
if i == 15 { if i == 15 {
params.put_string("error", "CRASH!!") error := &Error{"There was an error."}
eb.publish("error", work, params) eb.publish("error", work, error)
eb.publish("error", work, params) eb.publish("error", work, error)
return return
} }
} }
} }
pub fn get_subscriber() eventbus.Subscriber { pub fn get_subscriber() eventbus.Subscriber {

View File

@ -10,27 +10,36 @@ A module to provide eventing capabilities using pub/sub.
**EventBus:** **EventBus:**
1. `publish(string, voidptr, Params)` - publish an event with provided Params & name 1. `publish(name string, sender voidptr, args voidptr)` - publish an event with provided Params & name
2. `clear_all()` - clear all subscribers 2. `clear_all()` - clear all subscribers
3. `has_subscriber(string)` - check if a subscriber to an event exists 3. `has_subscriber(name string)` - check if a subscriber to an event exists
**Subscriber:** **Subscriber:**
1. `subscribe(string, fn(voidptr, Params))` - subscribe to an event 1. `subscribe(name string, handler EventHandlerFn)` - subscribe to an event
2. `subscribe_once(string, fn(voidptr, Params))` - subscribe only once to an event 2. `subscribe_once(name string, handler EventHandlerFn)` - subscribe only once to an event
3. `is_subscribed(string)` - check if we are subscribed to an event 3. `subscribe_method(name string, handler EventHandlerFn, reciever voidptr)` - subscribe to an event and also recieve the `reciever` as a parameter. Since it's not yet possible to send methods as parameters, this is the workaround.
4. `unsubscribe(string)` - unsubscribe from an event 4. `is_subscribed(name string)` - check if we are subscribed to an event
5. `unsubscribe(name string)` - unsubscribe from an event
**Event Handler Signature:** **Event Handler Signature:**
The function given to `subscribe` and `subscribe_once` must match this: The function given to `subscribe`, `subscribe_method` and `subscribe_once` must match this:
```v ```v
fn(voidptr, Params){ fn(voidptr, voidptr, voidptr){
} }
// Example
fn onPress(sender voidptr, p Params){ // 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 onPress(sender voidptr, e &ClickEvent){
println(e.x)
//your code here... //your code here...
} }
``` ```
@ -62,13 +71,8 @@ fn main(){
} }
// the event handler // the event handler
fn on_error(sender voidptr, p eventbus.Params) { fn on_error(work &Work, e &Error) {
//cast the sender to the real type println('error occured on ${work.hours}. Error: ${e.message}')
//you can also make this mutable if required.
work := *(*Work(sender)) //a little verbose but works
error := p.get_string("error")
println('error occured on ${work.hours}. Error: ${error}')
} }
``` ```
@ -77,55 +81,23 @@ fn on_error(sender voidptr, p eventbus.Params) {
```v ```v
module main module main
import (
eventbus
)
struct Work{ struct Work{
hours int hours int
} }
struct Error {
message string
}
fn do_work(){ fn do_work(){
work := Work{20} work := Work{20}
// get a mutable Params instance & put some data into it // get a mutable Params instance & put some data into it
mut params := eventbus.Params{} error := &Error{"Error: no internet connection."}
params.put_string("error", "Error: no internet connection.")
// publish the event // publish the event
eb.publish("error", work, params) eb.publish("error", work, error)
} }
``` ```
### How to use `Params`:
```v
mut params := eventbus.Params{}
params.put_string("string", "some_string")
params.put_int("int", 20)
params.put_bool("bool", true)
// add maps and arrays of any type like this
arr := [1,2,3]
params.put_array("array", arr)
mp := {"hello": "world"}
params.put_map("map", mp)
//get and use the params like this
assert params.get_string("string") == "some_string"
assert params.get_int("int") == 20
assert params.get_bool("bool") == true
g_arr := params.get_array("array", 0)
assert g_arr[0] == 1
g_m := params.get_map("map", "")
assert g_m["hello"] == "world"
```
#### Caution when putting arrays:
Currently putting arrays and maps directly as parameters in `put_array` doesn't work, so make a variable first and use that.
### Notes: ### Notes:
1. Each `EventBus` instance has it's own registry (i.e. there is no global event registry so you can't just subscribe to an event wherever you are. 1. Each `EventBus` instance has it's own registry (i.e. there is no global event registry so you can't just subscribe to an event wherever you are.

View File

@ -1,55 +1,55 @@
module eventbus module eventbus
pub type EventHandlerFn fn(voidptr, voidptr, voidptr)
pub struct Publisher { pub struct Publisher {
mut: mut:
registry &Registry registry &Registry
} }
pub struct Subscriber { pub struct Subscriber {
mut: mut:
registry &Registry registry &Registry
} }
struct Registry{ struct Registry {
mut: mut:
names []string events []EventHandler
events []voidptr
once []string
} }
struct EventHandler { struct EventHandler {
func fn(voidptr, Params) name string
handler EventHandlerFn
receiver voidptr
once bool
} }
pub struct EventBus{ pub struct EventBus {
pub mut: pub mut:
registry &Registry registry &Registry
publisher &Publisher publisher &Publisher
pub: pub:
subscriber &Subscriber subscriber &Subscriber
} }
pub fn new() &EventBus{ pub fn new() &EventBus {
registry := &Registry{ registry := &Registry{
names: []
events: [] events: []
once: []
} }
return &EventBus{ return &EventBus{
registry, registry,&Publisher{
&Publisher{registry}, registry},&Subscriber{
&Subscriber{registry} registry}
} }
} }
// EventBus Methods // EventBus Methods
pub fn (eb &EventBus) publish(name string, sender voidptr, args voidptr) {
pub fn (eb &EventBus) publish(name string, sender voidptr, p Params) {
mut publisher := eb.publisher mut publisher := eb.publisher
publisher.publish(name, sender, p) publisher.publish(name, sender, args)
} }
pub fn (eb &EventBus) clear_all(){ pub fn (eb &EventBus) clear_all() {
mut publisher := eb.publisher mut publisher := eb.publisher
publisher.clear_all() publisher.clear_all()
} }
@ -59,80 +59,77 @@ pub fn (eb &EventBus) has_subscriber(name string) bool {
} }
// Publisher Methods // Publisher Methods
fn (pb mut Publisher) publish(name string, sender voidptr, args voidptr) {
fn (pb mut Publisher) publish(name string, sender voidptr, p Params){ for i, event in pb.registry.events {
//p.put_custom("sender", "any", sender) //add sender to params if event.name == name {
for i, n in pb.registry.names { if event.once {
if name == n {
eh := pb.registry.events[i]
once_index := pb.registry.once.index(pb.registry.names[i])
if once_index > -1 {
pb.registry.events.delete(i) pb.registry.events.delete(i)
pb.registry.names.delete(i)
pb.registry.once.delete(once_index)
} }
invoke(eh, sender, p) if event.receiver != voidptr(0) {
event.handler(event.receiver, args, sender)
} else {
event.handler(sender, args, voidptr(0))
}
} }
} }
} }
fn (p mut Publisher) clear_all(){ fn (p mut Publisher) clear_all() {
if p.registry.names.len == 0 {return} if p.registry.events.len == 0 {
for i := p.registry.names.len - 1; i >= 0; i-- { return
p.registry.delete_entry(i) }
for i := p.registry.events.len - 1; i >= 0; i-- {
p.registry.events.delete(i)
} }
} }
// Subscriber Methods // Subscriber Methods
pub fn (s mut Subscriber) subscribe(name string, handler EventHandlerFn) {
pub fn (s mut Subscriber) subscribe(name string, handler fn(voidptr, Params)){ s.registry.events << EventHandler {
s.registry.names << name name: name
v := voidptr(handler) handler: handler
s.registry.events << v receiver: voidptr(0)
}
} }
pub fn (s mut Subscriber) subscribe_once(name string, handler fn(voidptr, Params)){ pub fn (s mut Subscriber) subscribe_method(name string, handler EventHandlerFn, receiver voidptr) {
s.subscribe(name, handler) s.registry.events << EventHandler {
s.registry.once << name name: name
handler: handler
receiver: receiver
}
}
pub fn (s mut Subscriber) subscribe_once(name string, handler EventHandlerFn) {
s.registry.events << EventHandler {
name: name
handler: handler
receiver: voidptr(0)
once: true
}
} }
pub fn (s &Subscriber) is_subscribed(name string) bool { pub fn (s &Subscriber) is_subscribed(name string) bool {
return s.registry.check_subscriber(name) return s.registry.check_subscriber(name)
} }
pub fn (s mut Subscriber) unsubscribe(name string, handler fn(voidptr, Params)){ pub fn (s mut Subscriber) unsubscribe(name string, handler EventHandlerFn) {
v := voidptr(handler) // v := voidptr(handler)
for i, n in s.registry.names { for i, event in s.registry.events {
if name == n { if event.name == name {
eh := s.registry.events[i] if event.handler == handler {
if eh == v { s.registry.events.delete(i)
s.registry.delete_entry(i)
} }
} }
} }
} }
// Registry Methods // Registry Methods
fn (r &Registry) check_subscriber(name string) bool { fn (r &Registry) check_subscriber(name string) bool {
for n in r.names { for event in r.events {
if name == n {return true} if event.name == name {
return true
}
} }
return false return false
} }
fn (r mut Registry) delete_entry(index int) {
once_index := r.once.index(r.names[index])
if once_index > -1 {
r.once.delete(once_index)
}
r.events.delete(index)
r.names.delete(index)
}
// Helper Functions
fn invoke(p, sender voidptr, arr Params){
handler := EventHandler{p}.func
handler(sender, arr)
}

View File

@ -2,17 +2,19 @@ import (
eventbus eventbus
) )
struct EventData {
data string
}
fn test_eventbus(){ fn test_eventbus(){
ev_data := &EventData{'hello'}
mut eb := eventbus.new() mut eb := eventbus.new()
eb.subscriber.subscribe_once("on_test", on_test) eb.subscriber.subscribe_once("on_test", on_test)
assert eb.has_subscriber("on_test") assert eb.has_subscriber("on_test")
assert eb.subscriber.is_subscribed("on_test") assert eb.subscriber.is_subscribed("on_test")
mut params := eventbus.Params{} eb.publish("on_test", eb, ev_data)
params.put_string("eventbus", "vevent")
eb.publish("on_test", eb, params)
assert !eb.has_subscriber("on_test") assert !eb.has_subscriber("on_test")
assert !eb.subscriber.is_subscribed("on_test") assert !eb.subscriber.is_subscribed("on_test")
@ -28,37 +30,6 @@ fn test_eventbus(){
assert !eb.subscriber.is_subscribed("on_test") assert !eb.subscriber.is_subscribed("on_test")
} }
fn test_params(){ fn on_test(sender voidptr, ev &EventData) {
mut params := eventbus.Params{} assert ev.data == "hello"
params.put_string("string", "some_string")
params.put_int("int", 20)
params.put_bool("bool", true)
arr := [1,2,3]
params.put_array("array", arr)
mp := {"hello": "world"}
params.put_map("map", mp)
assert params.get_string("string") == "some_string"
assert params.get_int("int") == 20
assert params.get_bool("bool") == true
g_arr := params.get_array("array", 0)
assert g_arr[0] == 1
g_m := params.get_map("map", "")
assert g_m["hello"] == "world"
} }
fn on_test(sender voidptr, p eventbus.Params) {
mut eb := *(*eventbus.EventBus(sender))
eb.subscriber.subscribe("on_test_2", on_test_2)
eb.clear_all()
assert !eb.has_subscriber("on_test_2")
assert !eb.subscriber.is_subscribed("on_test_2")
assert p.get_string("eventbus") == "vevent"
}
fn on_test_2(sender voidptr, p eventbus.Params){}

View File

@ -1,99 +0,0 @@
module eventbus
/*
NOTE: All these non-generic methods are temporary until
V has a properly functioning generic system
*/
pub struct Params {
mut:
params []Param
}
struct Param{
typ string
name string
value voidptr
}
pub fn (p Params) get_string(name string) string {
param, is_type := p.get_param(name, "string")
return if is_type {string(byteptr(param.value))}else{""}
}
pub fn (p Params) get_int(name string) int {
param, is_type := p.get_param(name, "num")
return if is_type {int(param.value)}else{0}
}
pub fn (p Params) get_bool(name string) bool {
param, is_type := p.get_param(name, "bool")
return if is_type {int(param.value) == 1}else{false}
}
pub fn (p Params) get_array<T>(name string, def T) []T {
param, is_type := p.get_param(name, "array")
if is_type {
val := param.value
return unmarshall_array(def, val)
} else {
return []
}
}
pub fn (p Params) get_map<T>(name string, def T) map[string]T {
param, is_type := p.get_param(name, "map")
if is_type {
val := param.value
return unmarshall_map(def, val)
} else {
return map[string]T
}
}
pub fn (p Params) get_raw(name string) voidptr {
param, _ := p.get_param(name, "")
return param.value
}
pub fn (p mut Params) put_map(name string, value voidptr) {
p.put_custom(name, "map", value)
}
pub fn (p mut Params) put_array(name string, arr voidptr) {
p.put_custom(name, "array", arr)
}
pub fn (p mut Params) put_int(name string, num int) {
p.put_custom(name, "num", num)
}
pub fn (p mut Params) put_string(name string, s string) {
p.put_custom(name, "string", s.str)
}
pub fn (p mut Params) put_bool(name string, val bool) {
p.put_custom(name, "bool", if val { 1 } else { 0 })
}
pub fn (p mut Params) put_custom(name, typ string, data voidptr) {
p.params << Param {typ, name, data}
}
//HELPERS
fn (p Params) get_param(name string, typ string) (Param, bool) {
for param in p.params {
if param.name == name {
return param, param.typ == typ
}
}
return Param{value: voidptr(0)}, false
}
fn unmarshall_array<T> (s T, m voidptr) array_T {
return *(*array_T(m))
}
fn unmarshall_map<T> (s T, m voidptr) map_T {
return *(*map_T(m))
}