diff --git a/examples/eventbus/.gitignore b/examples/eventbus/.gitignore new file mode 100644 index 0000000000..30319e2761 --- /dev/null +++ b/examples/eventbus/.gitignore @@ -0,0 +1 @@ +eventbus \ No newline at end of file diff --git a/examples/eventbus/eventbus.v b/examples/eventbus/eventbus.v new file mode 100644 index 0000000000..cedc2004b4 --- /dev/null +++ b/examples/eventbus/eventbus.v @@ -0,0 +1,16 @@ +module main + +import ( + some_module + eventbus +) + +fn main(){ + mut sub := some_module.get_subscriber() + sub.subscribe("error", on_error) + some_module.do_work() +} + +fn on_error(p eventbus.Params) { + println(p.get_string("error")) +} \ No newline at end of file diff --git a/examples/eventbus/some_module/some_module.v b/examples/eventbus/some_module/some_module.v new file mode 100644 index 0000000000..f7db6b9555 --- /dev/null +++ b/examples/eventbus/some_module/some_module.v @@ -0,0 +1,27 @@ +module some_module + +import ( + eventbus +) + +const ( + eb = eventbus.new() +) + +pub fn do_work(){ + mut params := eventbus.Params{} + for i in 0..20 { + println("working...") + if i == 15 { + params.put_string("error", "CRASH!!") + eb.publish("error", params) + eb.publish("error", params) + return + } + } + +} + +pub fn get_subscriber() eventbus.Subscriber { + return eb.subscriber +} \ No newline at end of file diff --git a/tools/vtest.v b/tools/vtest.v index 6f4c55bf00..6721bbbf7b 100644 --- a/tools/vtest.v +++ b/tools/vtest.v @@ -165,7 +165,7 @@ pub fn v_test_v(args_before_test string){ println('\nBuilding examples...') mut es := new_test_sesion( args_before_test ) files := os.walk_ext(parent_dir+'/examples','.v') - stable := files.filter(!it.contains('automaton.v')) + stable := files.filter(!it.contains('automaton.v') && !it.contains('some_module.v')) es.files << stable es.test() println( es.benchmark.total_message('building examples') ) diff --git a/vlib/eventbus/README.md b/vlib/eventbus/README.md new file mode 100644 index 0000000000..57b6086808 --- /dev/null +++ b/vlib/eventbus/README.md @@ -0,0 +1,126 @@ +# Event Bus + +A module to provide eventing capabilities using pub/sub. + +## API + +1. `new()` - create a new `EventBus` + +### Structs: + +**EventBus:** + +1. `publish(string, Params)` - publish an event with provided Params & name +2. `clear_all()` - clear all subscribers +3. `has_subscriber(string)` - check if a subscriber to an event exists + +**Subscriber:** + +1. `subscribe(string, fn(Params))` - subscribe to an event +2. `subscribe_once(string, fn(Params))` - subscribe only once to an event +3. `is_subscribed(string)` - check if we are subscribed to an event +4. `unsubscribe(string)` - unsubscribe from an event + +**Event Handler Signature:** + +The function given to `subscribe` and `subscribe_once` must match this: + +```v +fn(Params){ + +} +// Example +fn onPress(p Params){ + //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 emitting**._ + +**main.v** + +```v +module main +import eventbus + +// initialize it globally +const ( + eb = eventbus.new() +) + +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(p eventbus.Params) { + println(p.get_string("error")) +} +``` + +**work.v** + +```v +module main + +import ( + eventbus +) + +fn do_work(){ + // get a mutable Params instance & put some data into it + mut params := eventbus.Params{} + params.put_string("error", "Error: no internet connection.") + // publish the event + eb.publish("error", params) +} +``` + +### How to use `Params`: + +```v +mut params := eventbus.Params{} +params.put_string("string", "vevent") +params.put_int("int", 20) +params.put_bool("bo", true) +//the array & map currently needs to set like this +eventbus.put_array(mut params, "array", [1,2,3]) +eventbus.put_map(mut params, "map", "", {"hello": "world"}) + +params.get_string("string") == "vevent" +params.get_int("int") == 20 +params.get_bool("bo") == true +m := params.get_string_map("map") +//the array currently needs to gotten like this +arr := eventbus.get_array(params, "array", 0) + +//you can also pass around custom type arrays & maps (it's a little crude but works): +struct Example{} +custom_map := {"example": Example{}} +eventbus.put_map(mut params, "custom_map", Example{}, custom_map) +//and get it like this +eventbus.get_map(params, "custom_map", {"":Example{}} + +//For arrays: +eventbus.put_array(mut params, "array", [Example{}]) +eventbus.get_array(params, "custom_array", Example{}) +``` + +### 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. +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 & Emitter:** + +This is mainly for security because the if emitter & subscriber are both passed around, a client can easily emit events acting as the server. So a client should only be able to use the Subscriber methods. diff --git a/vlib/eventbus/eventbus.v b/vlib/eventbus/eventbus.v index 95e82ac6ea..8a3986447e 100644 --- a/vlib/eventbus/eventbus.v +++ b/vlib/eventbus/eventbus.v @@ -1,9 +1,16 @@ module eventbus + +pub struct Publisher { + mut: + registry &Registry +} + pub struct Subscriber { mut: registry &Registry } + struct Registry{ mut: names []string @@ -18,12 +25,11 @@ struct EventHandler { pub struct EventBus{ mut: registry &Registry + publisher &Publisher pub: - subscriber Subscriber + subscriber &Subscriber } -// EventBus Methods - pub fn new() &EventBus{ registry := &Registry{ names: [] @@ -32,35 +38,50 @@ pub fn new() &EventBus{ } return &EventBus{ registry, - Subscriber{registry} + &Publisher{registry}, + &Subscriber{registry} } } -pub fn (eb mut EventBus) publish(name string, p Params){ - for i, n in eb.registry.names { +// EventBus Methods + +pub fn (eb &EventBus) publish(name string, p Params) { + mut publisher := eb.publisher + publisher.publish(name, p) +} + +pub fn (eb &EventBus) clear_all(){ + mut publisher := eb.publisher + publisher.clear_all() +} + +pub fn (eb &EventBus) has_subscriber(name string) bool { + return eb.registry.check_subscriber(name) +} + +// Publisher Methods + +fn (pb mut Publisher) publish(name string, p Params){ + for i, n in pb.registry.names { if name == n { - eh := eb.registry.events[i] + eh := pb.registry.events[i] invoke(eh, p) - once_index := eb.registry.once.index(eb.registry.names[i]) + once_index := pb.registry.once.index(pb.registry.names[i]) if once_index > -1 { - eb.registry.events.delete(i) - eb.registry.names.delete(i) - eb.registry.once.delete(once_index) + pb.registry.events.delete(i) + pb.registry.names.delete(i) + pb.registry.once.delete(once_index) } } } } -pub fn (eb mut EventBus) clear_all(){ - for i, n in eb.registry.names { - eb.registry.delete_entry(i) +fn (p mut Publisher) clear_all(){ + for i, n in p.registry.names { + p.registry.delete_entry(i) } } -pub fn (eb &EventBus) has_subscriber(name string) bool { - return eb.registry.check_subscriber(name) -} - // Subscriber Methods pub fn (s mut Subscriber) subscribe(name string, handler fn(Params)){