├── utils ├── AnyMap │ ├── .gitignore │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── AnyVec │ ├── .gitignore │ ├── Cargo.toml │ └── src │ │ └── lib.rs └── logger │ ├── .gitignore │ ├── Cargo.toml │ └── src │ └── lib.rs ├── examples ├── rainbow_signal │ ├── .gitignore │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── with_logging │ ├── .gitignore │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── with_args.rs ├── simple.rs └── args_macro.rs ├── assets ├── signals-rs_banner_dark.png └── signals-rs_banner_light.png ├── .gitignore ├── .vscode ├── settings.json └── tasks.json ├── Cargo.toml ├── Makefile ├── README_cargo.md ├── README.md └── src └── lib.rs /utils/AnyMap/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock -------------------------------------------------------------------------------- /utils/AnyVec/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock -------------------------------------------------------------------------------- /utils/logger/.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target/ -------------------------------------------------------------------------------- /examples/rainbow_signal/.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target/ -------------------------------------------------------------------------------- /examples/with_logging/.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target/ -------------------------------------------------------------------------------- /assets/signals-rs_banner_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompeyDev/signals-rs/HEAD/assets/signals-rs_banner_dark.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | *.exe 4 | 5 | # Generated Example Binaries 6 | rainbow_signal* 7 | with_logging* 8 | -------------------------------------------------------------------------------- /assets/signals-rs_banner_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CompeyDev/signals-rs/HEAD/assets/signals-rs_banner_light.png -------------------------------------------------------------------------------- /utils/AnyVec/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "anyvec" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /utils/AnyMap/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "anymap" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | hashbrown = "0.13.2" -------------------------------------------------------------------------------- /utils/logger/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "logger" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | chrono = "0.4.24" 10 | colored = "2.0.0" 11 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.linkedProjects": [ 3 | "./examples/rainbow_signal/Cargo.toml", 4 | "./examples/with_logging/Cargo.toml", 5 | "./utils/AnyMap/Cargo.toml", 6 | "./utils/AnyVec/Cargo.toml", 7 | "./utils/logger/Cargo.toml", 8 | "./Cargo.toml" 9 | ] 10 | } -------------------------------------------------------------------------------- /examples/rainbow_signal/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rainbow_signal" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | colored = "2.0.0" 10 | rand = "0.8.5" 11 | signals_rs = { git = "https://github.com/CompeyDev/signals-rs.git", rev = "8a650a1" } 12 | -------------------------------------------------------------------------------- /examples/with_logging/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "with_logging" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | colored = "2.0.0" 10 | rand = "0.8.5" 11 | signals_rs = { git = "https://github.com/CompeyDev/signals-rs.git", rev = "8a650a1", features = ["log"] } 12 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "signals_rs" 3 | version = "0.1.8" 4 | authors = ["DevComp "] 5 | edition = "2021" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | anymap = { path = "./utils/AnyMap" } 11 | anyvec = { path = "./utils/AnyVec" } 12 | logger = { path = "./utils/logger" } 13 | rand = "0.8.5" 14 | 15 | 16 | [features] 17 | default = [] 18 | log = [] 19 | -------------------------------------------------------------------------------- /examples/with_args.rs: -------------------------------------------------------------------------------- 1 | use signals_rs::{Arguments, Signal}; 2 | 3 | fn main() { 4 | let mut signal = Signal::new(); 5 | 6 | signal.connect(&|args| { 7 | let secret_msg = args.get::<&str>(0).unwrap().to_owned(); 8 | 9 | println!( 10 | "this signal was invoked with the secret message: '{}'", 11 | secret_msg 12 | ); 13 | }); 14 | 15 | let mut signal_args = Arguments::new(); 16 | 17 | signal_args.push("Hello, signals!"); 18 | 19 | signal.fire(None, Some(signal_args)); 20 | 21 | signal.disconnect(None); 22 | signal.destroy(); 23 | } 24 | -------------------------------------------------------------------------------- /examples/simple.rs: -------------------------------------------------------------------------------- 1 | use signals_rs::Signal; 2 | 3 | fn main() { 4 | let mut some_signal = Signal::new(); 5 | 6 | let (connection, connection_id) = 7 | some_signal.connect(&|_| println!("This signal has been fired, continuing...")); 8 | 9 | // We can now fire this connection to execute its registered callback. 10 | // Do note that in a more complicated scenario with multiple connections 11 | // to a signal, a connection_id parameter must be passed. 12 | connection.fire(None, None); 13 | 14 | // This "disconnects" from a signal and removes the registered callback, as it is no longer required. 15 | connection.disconnect(Some(connection_id)); 16 | 17 | // Signals can be destroyed or dropped too. 18 | some_signal.destroy(); 19 | } 20 | -------------------------------------------------------------------------------- /examples/with_logging/src/main.rs: -------------------------------------------------------------------------------- 1 | use signals_rs::Signal; 2 | 3 | fn main() { 4 | let mut some_signal = Signal::new(); 5 | 6 | let (connection, connection_id) = some_signal.connect(&|_| println!("This signal has been fired, continuing...")); 7 | 8 | // We can now fire this connection to execute its registered callback. 9 | // Do note that in a more complicated scenario with multiple connections 10 | // to a signal, a connection_id parameter must be passed. 11 | connection.fire(None, None); 12 | 13 | // This "disconnects" from a signal and removes the registered callback, as it is no longer required. 14 | connection.disconnect(Some(connection_id)); 15 | 16 | // Signals can be destroyed or dropped too. 17 | some_signal.destroy(); 18 | } -------------------------------------------------------------------------------- /utils/logger/src/lib.rs: -------------------------------------------------------------------------------- 1 | use chrono; 2 | use colored::Colorize; 3 | 4 | #[derive(Debug, Clone, Copy)] 5 | pub enum Scope { 6 | Info, 7 | Warning, 8 | Error 9 | } 10 | 11 | pub fn log(scope: Scope, msg: &str) { 12 | let now = chrono::Local::now().format("%d/%m/%Y %H:%M:%S"); 13 | 14 | match scope { 15 | Scope::Info => println!("[{} :: {}] -> {}", now, "info".green().bold(), msg), 16 | Scope::Warning => println!("[{} :: {}] -> {}", now, "warn".yellow().bold(), msg), 17 | Scope::Error => println!("[{} :: {}] -> {}", now, "error".red().bold(), msg), 18 | } 19 | } 20 | 21 | #[cfg(test)] 22 | mod tests { 23 | use super::*; 24 | 25 | #[test] 26 | fn it_works() { 27 | log(Scope::Info, "this is an info log"); 28 | log(Scope::Warning, "this is an warn log"); 29 | log(Scope::Error, "this is an error log"); 30 | } 31 | } -------------------------------------------------------------------------------- /examples/args_macro.rs: -------------------------------------------------------------------------------- 1 | use signals_rs::{args, Signal}; 2 | 3 | fn main() { 4 | let mut signal = Signal::new(); 5 | 6 | signal.connect(&|args| { 7 | let secret_msg = args.get::<&str>(0).unwrap().to_owned(); 8 | let secret_num = args.get::(1).unwrap().to_owned(); 9 | let info_msg = args.get::<&str>(2).unwrap().to_owned(); 10 | 11 | println!( 12 | "this signal was invoked with the secret message: '{}'", 13 | secret_msg 14 | ); 15 | 16 | println!( 17 | "this signal was also invoked with as secret number: '{}'", 18 | secret_num 19 | ); 20 | 21 | println!( 22 | "{}", 23 | info_msg 24 | ); 25 | }); 26 | 27 | signal.fire(None, Some(args! { 28 | "Hello, signals!", 29 | 69u32, 30 | "this is an example of a signal using the args! macro" 31 | })); 32 | 33 | signal.disconnect(None); 34 | signal.destroy(); 35 | } 36 | -------------------------------------------------------------------------------- /utils/AnyVec/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(box_into_inner)] 2 | 3 | use std::any::Any; 4 | use std::vec::Vec; 5 | type Anything = Box; 6 | 7 | #[derive(Debug)] 8 | pub struct AnyVec(Vec); 9 | 10 | impl AnyVec { 11 | pub fn new() -> Self { 12 | Self(Vec::new()) 13 | } 14 | 15 | pub fn new_with_capacity(capacity: usize) -> Self { 16 | Self(Vec::with_capacity(capacity)) 17 | } 18 | 19 | pub fn insert(&mut self, key: usize, val: V) { 20 | self 21 | .0 22 | .insert(key, Box::new(val)); 23 | } 24 | 25 | pub fn push(&mut self, val: V) { 26 | self 27 | .0 28 | .push(Box::new(val)); 29 | } 30 | 31 | pub fn get(&self, idx: usize) -> Option<&V> { 32 | self.0.get(idx)?.downcast_ref::() 33 | } 34 | 35 | pub fn get_mut(&mut self, idx: usize) -> Option<&mut V> { 36 | self.0 37 | .get_mut(idx)? 38 | .downcast_mut::() 39 | } 40 | 41 | pub fn remove(&mut self, idx: usize) -> Option { 42 | let boxed = self 43 | .0 44 | .remove(idx) 45 | .downcast::() 46 | .ok()?; 47 | 48 | Some(Box::into_inner(boxed)) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | check_defined = \ 2 | $(strip $(foreach 1,$1, \ 3 | $(call __check_defined,$1,$(strip $(value 2))))) 4 | __check_defined = \ 5 | $(if $(value $1),, \ 6 | $(error Mandatory argument $1$(if $2, ($2))$(if $(value @), \ 7 | not provided. Required by target `$@`))) 8 | 9 | log_prefix := \x1b[34m[\u001b[0m\x1b[31m*\x1b[34m\x1b[34m]\u001b[0m 10 | command_prefix := \x1b[34m[\u001b[0m\x1b[31m\#\x1b[34m\x1b[34m]\u001b[0m 11 | examples_dir := examples/$(EXAMPLE) 12 | 13 | build: src/lib.rs 14 | @:$(call check_defined, TYPE, type of build: debug/release) 15 | 16 | @echo -e "${log_prefix} Building library signals-rs..." 17 | @echo -e "${command_prefix} cargo build --$(TYPE)" 18 | @cargo build --$(TYPE) 19 | 20 | example: examples/ 21 | @:$(call check_defined, EXAMPLE, example to build) 22 | 23 | @echo -e "${log_prefix} Building example $(EXAMPLE)" 24 | @echo -e "${command_prefix} cd examples/$(EXAMPLE) && cargo build --release" 25 | @cd ${examples_dir} && cargo build --release 26 | @echo -e "${command_prefix} mv ./target/release/$(EXAMPLE) .." 27 | @mv ./${examples_dir}/target/release/$(EXAMPLE) . 28 | 29 | docsgen: 30 | @echo -e "${log_prefix} Generating docs for signals-rs..." 31 | @echo -e "${command_prefix} cargo rustdoc --verbose" 32 | @cargo rustdoc --verbose 33 | @echo -e "${command_prefix} mv target/doc ." 34 | @mv target/doc . 35 | -------------------------------------------------------------------------------- /utils/AnyMap/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(box_into_inner)] 2 | 3 | use std::any::{Any, TypeId}; 4 | use std::cmp::Eq; 5 | use hashbrown::HashMap; 6 | use std::hash::Hash; 7 | 8 | type HashKey = (K, TypeId); 9 | type Anything = Box; 10 | 11 | pub struct AnyMap(HashMap, Anything>); 12 | 13 | impl AnyMap { 14 | pub fn new() -> Self { 15 | Self(HashMap::new()) 16 | } 17 | 18 | pub fn new_with_capacity(capacity: usize) -> Self { 19 | Self(HashMap::with_capacity(capacity)) 20 | } 21 | 22 | pub fn insert(&mut self, key: K, val: V) -> Option { 23 | let boxed = self 24 | .0 25 | .insert((key, val.type_id()), Box::new(val))? 26 | .downcast::() 27 | .ok()?; 28 | 29 | Some(Box::into_inner(boxed)) 30 | } 31 | 32 | pub fn get(&self, key: K) -> Option<&V> { 33 | self.0.get(&(key, TypeId::of::()))?.downcast_ref::() 34 | } 35 | 36 | pub fn get_mut(&mut self, key: K) -> Option<&mut V> { 37 | self.0 38 | .get_mut(&(key, TypeId::of::()))? 39 | .downcast_mut::() 40 | } 41 | 42 | pub fn remove(&mut self, key: K) -> Option { 43 | let boxed = self 44 | .0 45 | .remove(&(key, TypeId::of::()))? 46 | .downcast::() 47 | .ok()?; 48 | 49 | Some(Box::into_inner(boxed)) 50 | } 51 | } -------------------------------------------------------------------------------- /examples/rainbow_signal/src/main.rs: -------------------------------------------------------------------------------- 1 | use colored::Colorize; 2 | use rand::prelude::*; 3 | use signals_rs::Signal; 4 | use std::fmt::Write; 5 | 6 | fn main() { 7 | println!("Hello, signals!"); 8 | 9 | // First, we instantiate a new signal. 10 | let rainbow_time = &mut Signal::new(); 11 | 12 | // Then we provide a connection callback to our signal. 13 | let (conn, id) = rainbow_time.connect(&|_| { 14 | let orig_msg = "ooo, rainbows!"; 15 | let mut secret_msg: String = String::new(); 16 | let choices = ["red", "blue", "purple", "yellow", "green", "magenta"]; 17 | let mut rng = rand::thread_rng(); 18 | 19 | for (_, char) in orig_msg.chars().enumerate() { 20 | let char_string = char.to_string(); 21 | 22 | let choice_idx = rng.gen_range(0..choices.len()); 23 | let chosen_color = choices[choice_idx]; 24 | 25 | let chosen_color_string = match chosen_color { 26 | "red" => char_string.red(), 27 | "blue" => char_string.blue(), 28 | "purple" => char_string.purple(), 29 | "yellow" => char_string.yellow(), 30 | "green" => char_string.green(), 31 | "magenta" => char_string.magenta(), 32 | &_ => panic!("well, this wasn't supposed to happen..."), // out of bounds str 33 | }; 34 | 35 | write!(secret_msg, "{}", chosen_color_string).unwrap(); 36 | } 37 | 38 | println!("psst... {}", secret_msg.bold()); 39 | }); 40 | 41 | // Now, we can fire this when we're ready and have our callback execute! 42 | conn.fire(None, None); 43 | 44 | // Post firing, we must clean up the signal that we no longer use. 45 | conn.disconnect(Some(id)); 46 | rainbow_time.destroy(); 47 | } 48 | -------------------------------------------------------------------------------- /README_cargo.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | A lua(u)-inspired implementation of signals/events in rust. 4 | 5 | A signal is a global state switch which can be used as a gateway for conditional code execution. Signals can be activated by first "connecting" to them with a callback, then "firing" them to toggle their state. 6 | ``` 7 | let mut some_signal = Signal::new(); 8 | 9 | let (connection, connection_id) = some_signal.connect(&|_| println!("This signal has been fired, continuing...")); 10 | 11 | // We can now fire this connection to execute its registered callback. 12 | // Do note that in a more complicated scenario with multiple connections 13 | // to a signal, a connection_id parameter must be passed. 14 | connection.fire(None, None); 15 | 16 | // This "disconnects" from a signal and removes the registered callback, as it is no longer required. 17 | connection.disconnect(Some(connection_id)); 18 | 19 | // Signals can be destroyed or dropped too. 20 | some_signal.destroy(); 21 | ``` 22 | 23 | Signals are especially useful as lightweight events for global state sync-ups. 24 | 25 | # Installation 26 | In order to install signals-rs, first switch to a nightly channel (`rustup toolchain install nightly`) for your rust compiler and then you can add it as such to your dependencies section in `Cargo.toml`: 27 | 28 | ```toml 29 | [dependencies] 30 | signals_rs = { git = "https://github.com/CompeyDev/signals-rs.git", rev = "8a650a1" } 31 | ``` 32 | 33 | # Features 34 | As of now, this crate consists 1 feature, namely `log`. This feature is opt-in and enables progress logging for various activities, classified into "errors", "warns", and "info" logs. Useful for debugging purposes. 35 | 36 | It can be enabled as such: 37 | ```toml 38 | [dependencies] 39 | signals_rs = { git = "https://github.com/CompeyDev/signals-rs.git", rev = "8a650a1", features = ["log"] } 40 | ``` 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | A lua(u)-inspired implementation of signals/events in rust. 5 | 6 | A signal is a global state switch which can be used as a gateway for conditional code execution. Signals can be activated by first "connecting" to them with a callback, then "firing" them to toggle their state. 7 | ```rs 8 | let mut some_signal = Signal::new(); 9 | 10 | let (connection, connection_id) = some_signal.connect(&|_| println!("This signal has been fired, continuing...")); 11 | 12 | // We can now fire this connection to execute its registered callback. 13 | // Do note that in a more complicated scenario with multiple connections 14 | // to a signal, a connection_id parameter must be passed. 15 | connection.fire(None, None); 16 | 17 | // This "disconnects" from a signal and removes the registered callback, as it is no longer required. 18 | connection.disconnect(Some(connection_id)); 19 | 20 | // Signals can be destroyed or dropped too. 21 | some_signal.destroy(); 22 | ``` 23 | 24 | Signals are especially useful as lightweight events for global state sync-ups. 25 | 26 | # Installation 27 | In order to install signals-rs, first switch to a nightly channel (`rustup toolchain install nightly`) for your rust compiler and then you can add it as such to your dependencies section in `Cargo.toml`: 28 | 29 | ```toml 30 | [dependencies] 31 | signals_rs = { git = "https://github.com/CompeyDev/signals-rs.git", rev = "8a650a1" } 32 | ``` 33 | 34 | # Features 35 | As of now, this crate consists 1 feature, namely `log`. This feature is opt-in and enables progress logging for various activities, classified into "errors", "warns", and "info" logs. Useful for debugging purposes. 36 | 37 | It can be enabled as such: 38 | ```toml 39 | [dependencies] 40 | signals_rs = { git = "https://github.com/CompeyDev/signals-rs.git", rev = "8a650a1", features = ["log"] } 41 | ``` 42 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Cargo: Build project", 6 | "detail": "Compile the library's source", 7 | "type": "shell", 8 | "command": "cargo build ${input:BUILD_TYPE}", 9 | "group": { 10 | "kind": "build" 11 | }, 12 | "options": { 13 | "cwd": "${workspaceFolder}" 14 | }, 15 | "presentation": { 16 | "reveal": "always", 17 | "panel": "new", 18 | "focus": true, 19 | "close": true 20 | } 21 | }, 22 | { 23 | "label": "Cargo: Build example", 24 | "detail": "Build a particular example to a binary", 25 | "type": "process", 26 | "windows": { 27 | "command": "${env:ComSpec}", 28 | "args": [ 29 | "'cd examples/${input:EXAMPLE} && cargo build --release && mv ./examples/${input:EXAMPLE}/target/release/${input:EXAMPLE}.exe .'" 30 | ] 31 | }, 32 | "linux": { 33 | "command": "sh", 34 | "args": [ 35 | "'cd examples/${input:EXAMPLE} && cargo build --release && mv ./examples/${input:EXAMPLE}/target/release/${input:EXAMPLE}.exe .'" 36 | ] 37 | }, 38 | "problemMatcher": [ 39 | "$rustc" 40 | ] 41 | }, 42 | { 43 | "label": "Cargo: Generate docs", 44 | "detail": "Generate documentation from annotations", 45 | "type": "process", 46 | "command": "cargo rustdoc --verbose ; mv target/doc ." 47 | }, 48 | ], 49 | "inputs": [ 50 | { 51 | "id": "BUILD_TYPE", 52 | "description": "type of build: debug/release", 53 | "default": "debug", 54 | "type": "promptString" 55 | }, 56 | { 57 | "id": "EXAMPLE", 58 | "description": "example to build, located in examples directory", 59 | "type": "promptString" 60 | } 61 | ] 62 | } -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README_cargo.md")] 2 | 3 | use anymap::AnyMap as HashMap; 4 | pub use anyvec::AnyVec as Arguments; 5 | #[cfg(feature = "log")] 6 | use logger::{log, Scope}; 7 | use rand::random; 8 | 9 | #[macro_export] 10 | macro_rules! args { 11 | ( $( $e:expr ),* ) => { 12 | { 13 | let mut alloc_args_anyvec = anyvec::AnyVec::new(); 14 | $( 15 | alloc_args_anyvec.push::<_>($e); 16 | 17 | )* 18 | 19 | 20 | alloc_args_anyvec 21 | } 22 | }; 23 | } 24 | 25 | /// # signals-rs 26 | /// `signals-rs` is a lua(u)-inspired implementation of signals/events. 27 | /// 28 | /// A signal is a global state switch which can be used as a gateway for conditional code execution. 29 | /// Signals can be activated by first "connecting" to them with a callback, then "firing" them to toggle their state. 30 | /// 31 | /// ``` 32 | /// use signals_rs::Signal; 33 | /// 34 | /// fn main() { 35 | /// let mut some_signal = Signal::new(); 36 | /// 37 | /// let (connection, connection_id) = some_signal.connect(&|_| println!("This signal has been fired, continuing...")); 38 | /// some_signal.disconnect(Some(connection_id)); // This "disconnects" from a signal and removes the registered callback, as it is no longer required. 39 | /// 40 | /// some_signal.destroy(); // Signals can be destroyed or dropped too. 41 | /// } 42 | /// ``` 43 | /// 44 | /// Signals are especially useful as lightweight events for global state sync-ups. 45 | pub struct Signal { 46 | pub connections: HashMap, 47 | pub destroyed: bool, 48 | } 49 | 50 | impl Signal { 51 | /// `Signal::new` instantiates and returns a new Signal, which can then be made use of. 52 | /// 53 | /// ``` 54 | /// use signals_rs::Signal; 55 | /// 56 | /// let mut signal = Signal::new(); 57 | /// ``` 58 | pub fn new() -> Signal { 59 | let mut connections = HashMap::new(); 60 | 61 | connections.insert("last_accessed_connection".to_string(), "Unknown"); 62 | 63 | #[cfg(feature = "log")] 64 | log(Scope::Info, "constructed and instantianted signal!"); 65 | 66 | return Signal { 67 | connections, 68 | destroyed: false, 69 | }; 70 | } 71 | 72 | /// `Signal.connect` registers a supplied callback closure/function to be executed on a signal mutation activated 73 | /// by `Signal.fire`. A signal can be connected to with multiple callbacks. 74 | /// 75 | /// Every callback returns a tuple with a `Signal` and Callback ID, which can be stored for future use with 76 | /// `Signal.fire` & `Signal.disconnect`. 77 | /// 78 | /// ``` 79 | /// use signals_rs::{Signal, Arguments}; 80 | /// 81 | /// let mut signal = Signal::new(); 82 | /// 83 | /// let (_, first_callback_id) = signal.connect(&|_| println!("received signal fire from callback #1!")); 84 | /// 85 | /// fn signal_callback(_args: Arguments) { 86 | /// println!("received signal fire from callback #2!"); 87 | /// } 88 | /// 89 | /// let (_, second_callback_id) = signal.connect(&signal_callback); 90 | /// 91 | /// println!("#1 -> {}", first_callback_id); 92 | /// println!("#2 -> {}", second_callback_id); 93 | /// ``` 94 | /// 95 | /// The callback provided to a signal connection must be a function which accepts 96 | /// a single parameter of the type `Argument`. This parameter is a vector which 97 | /// can be supplied with arbritrary values as parameters to execute the 98 | /// connection with **on fire**. 99 | /// 100 | /// ``` 101 | /// signals_rs::Signal::new().connect(&|args| { 102 | /// let first_arg = args.get::<&str>(0); 103 | /// let second_arg = args.get::(1); 104 | /// 105 | /// println!("arg #1: {}; arg #2: {}", first_arg.unwrap(), second_arg.unwrap()); 106 | /// }); 107 | /// ``` 108 | pub fn connect(&mut self, callback: &'static dyn Fn(Arguments)) -> (&mut Signal, String) { 109 | if !self.destroyed { 110 | let connection_id: String = format!("{:x}", random::()); 111 | 112 | #[cfg(feature = "log")] 113 | log( 114 | Scope::Info, 115 | format!("generating connection {}", connection_id.clone()).as_str(), 116 | ); 117 | 118 | let mut connection_meta: HashMap<&str> = HashMap::new(); 119 | 120 | connection_meta.insert("callback", callback); 121 | connection_meta.insert("is_primary", false); 122 | 123 | #[cfg(feature = "log")] 124 | log( 125 | Scope::Info, 126 | format!("generating connection meta for {}", connection_id.clone()).as_str(), 127 | ); 128 | 129 | self.connections 130 | .insert(connection_id.to_owned(), connection_meta); 131 | self.connections.insert( 132 | "last_accessed_connection".to_string(), 133 | connection_id.to_owned(), 134 | ); 135 | 136 | #[cfg(feature = "log")] 137 | log( 138 | Scope::Info, 139 | format!("successfully created connection {}", connection_id.clone()).as_str(), 140 | ); 141 | 142 | return (self, connection_id); 143 | } else { 144 | panic!("fatal: signal has been destroyed!") 145 | } 146 | } 147 | 148 | /// `Signal.disconnect` disconnects a registered callback from a signal. This prevents execution of a certain callback 149 | /// once it's been disconnected. An optional `connection_id` parameter may be provided which can be used to disconnect 150 | /// a specific connection instead of the last accessed connection. Providing a connection_id is highly recommended and 151 | /// minimizes the risk of an unregistered connection from being disconnected. 152 | /// 153 | /// ``` 154 | /// use signals_rs::Signal; 155 | /// 156 | /// let mut signal = Signal::new(); 157 | /// 158 | /// let (_, callback_id) = signal.connect(&|_| println!("received signal fire from callback")); 159 | /// 160 | /// signal.disconnect(Some(callback_id)); 161 | /// ``` 162 | pub fn disconnect(&mut self, connection_id: Option) { 163 | if !self.destroyed { 164 | let target_id = match connection_id { 165 | Some(conn_id) => conn_id, 166 | None => { 167 | #[cfg(feature = "log")] 168 | log( 169 | Scope::Warning, 170 | "no connection id provided, defaulting to last access connection!", 171 | ); 172 | let conn_id = self 173 | .connections 174 | .get::("last_accessed_connection".to_string()) 175 | .unwrap() 176 | .to_owned(); 177 | 178 | if conn_id == "Unknown".to_string() { 179 | #[cfg(feature = "log")] 180 | log( 181 | Scope::Error, 182 | "no last known connection, please manually supply one", 183 | ); 184 | 185 | panic!("no last known connection, please manually supply one") 186 | } else { 187 | conn_id 188 | } 189 | } 190 | }; 191 | 192 | #[cfg(feature = "log")] 193 | let logger_id = target_id.clone(); 194 | 195 | #[cfg(feature = "log")] 196 | log( 197 | Scope::Info, 198 | format!("calculated target {}", logger_id).as_str(), 199 | ); 200 | 201 | *self 202 | .connections 203 | .get_mut::("last_accessed_connection".to_string()) 204 | .unwrap() = "Unknown".to_string(); 205 | self.connections 206 | .remove::>(target_id) 207 | .expect("non existent connection id supplied"); 208 | 209 | #[cfg(feature = "log")] 210 | log( 211 | Scope::Info, 212 | format!("successfully disconnected from {}", logger_id).as_str(), 213 | ); 214 | } else { 215 | panic!("fatal: signal has been destroyed!") 216 | } 217 | } 218 | 219 | /// `Signal.fire` fires a callback from a connection registered beforehand. An optional `connection_id` may be provided. 220 | /// In such a case where no `connection_id` is provided, it will default to the previously accessed connection. It is 221 | /// recommended practice to provide a `connection_id` as this minimizes the risk of an unregistered connection from being 222 | /// fired. 223 | /// 224 | /// ``` 225 | /// use signals_rs::Signal; 226 | /// 227 | /// let mut signal = Signal::new(); 228 | /// 229 | /// let (conn, callback_id) = signal.connect(&|_| println!("received signal fire from callback")); 230 | /// 231 | /// conn.fire(Some(callback_id.clone()), None); 232 | /// conn.disconnect(Some(callback_id)); 233 | /// ``` 234 | ///
235 | /// 236 | /// On fire, an optional args parameter can be provided, which contains `Arguments` to execute the connection 237 | /// with. This crate also provides a wrapper wround `std::vec::Vec`, which allows the vector to contain 238 | /// values with irregular types. 239 | /// 240 | /// ``` 241 | /// use signals_rs::{Signal, Arguments}; 242 | /// 243 | /// let mut signal = Signal::new(); 244 | /// let mut params = Arguments::new(); 245 | /// 246 | /// signal.connect(&|args| { 247 | /// println!("received signal fire from callback"); 248 | /// println!("args received: {:#?}", args); 249 | /// }); 250 | /// 251 | /// params.push("Hello!"); 252 | /// params.push(392); 253 | /// 254 | /// signal.fire(None, Some(params)); 255 | /// ``` 256 | /// Arguments can also be declared using the `args!` macro, this is usually useful for 257 | /// declaring arguments in a short one-liner manner. When using this macro, the types are 258 | /// inferred, so declaring inferrable types are recommended. 259 | /// ``` 260 | /// use signals_rs::args; 261 | /// 262 | /// args { 123u32, 78f32, "Hello!" }; // Declaring numbers using suffixes makes them inferrable 263 | /// ``` 264 | pub fn fire(&mut self, connection_id: Option, args: Option) { 265 | let conn_id = match connection_id { 266 | Some(target_id) => target_id, 267 | None => { 268 | #[cfg(feature = "log")] 269 | log( 270 | Scope::Warning, 271 | "no connection id provided, defaulting to last access connection!", 272 | ); 273 | self.connections 274 | .get::("last_accessed_connection".to_string()) 275 | .unwrap() 276 | .to_owned() 277 | } 278 | }; 279 | 280 | let exec_args = match args { 281 | Some(args) => args, 282 | None => Arguments::new(), 283 | }; 284 | 285 | #[cfg(feature = "log")] 286 | let logger_id = conn_id.clone(); 287 | 288 | #[cfg(feature = "log")] 289 | log( 290 | Scope::Info, 291 | format!("calculated connection target {}", logger_id).as_str(), 292 | ); 293 | 294 | let conn_meta = self 295 | .connections 296 | .get::>(conn_id) 297 | .expect("non existent connection id supplied"); 298 | 299 | #[cfg(feature = "log")] 300 | log( 301 | Scope::Info, 302 | format!("generating connection {:#?}", logger_id).as_str(), 303 | ); 304 | 305 | conn_meta 306 | .get::<&'static dyn Fn(Arguments)>("callback") 307 | .unwrap()(exec_args); 308 | } 309 | 310 | /// `Signal.destroy` destroys the signal and all registered callbacks are rendered dysfunctional. It is good practice 311 | /// to destroy a signal once it no longer serves its purpose. Destroying a signal is equivalent to dropping it as the 312 | /// `Drop` trait has been implemented for `Signal`. 313 | /// 314 | /// ``` 315 | /// use signals_rs::Signal; 316 | /// 317 | /// let mut signal = Signal::new(); 318 | /// 319 | /// signal.destroy() 320 | /// ``` 321 | pub fn destroy(&mut self) { 322 | drop(self); 323 | } 324 | } 325 | 326 | impl Drop for Signal { 327 | fn drop(&mut self) { 328 | #[cfg(feature = "log")] 329 | log(Scope::Info, "dropping self: Signal"); 330 | 331 | self.destroyed = true; 332 | 333 | self.connections = HashMap::new(); 334 | 335 | #[cfg(feature = "log")] 336 | log(Scope::Info, "sucessfully ran drop cleanup"); 337 | } 338 | } 339 | 340 | #[cfg(test)] 341 | mod tests { 342 | use super::*; 343 | 344 | #[test] 345 | fn connection_works() { 346 | let mut signal = Signal::new(); 347 | 348 | signal.connect(&|_| println!("received signal fire!")); 349 | 350 | assert_eq!( 351 | signal 352 | .connections 353 | .get::("last_accessed_connection".to_string()) 354 | .unwrap() 355 | .to_owned() 356 | != "Unknown".to_string(), 357 | true 358 | ); 359 | } 360 | 361 | #[test] 362 | fn disconnection_works() { 363 | let mut signal = Signal::new(); 364 | 365 | signal.connect(&|_| println!("received signal fire!")); 366 | 367 | signal.disconnect(None); 368 | } 369 | 370 | #[test] 371 | fn destruction_works() { 372 | let mut signal = Signal::new(); 373 | 374 | signal.connect(&|_| println!("received signal fire!")); 375 | 376 | signal.destroy(); 377 | } 378 | 379 | #[test] 380 | fn args_works() { 381 | let mut signal = Signal::new(); 382 | 383 | signal.connect(&|args| { 384 | let secret_msg = args.get::<&str>(0).unwrap().to_owned(); 385 | 386 | println!( 387 | "this signal was invoked with the secret message {}", 388 | secret_msg 389 | ); 390 | }); 391 | 392 | signal.disconnect(None); 393 | signal.destroy(); 394 | } 395 | 396 | #[test] 397 | fn fire_works() { 398 | let mut signal = Signal::new(); 399 | 400 | let (_, conn_id) = signal.connect(&|_| println!("received signal fire!")); 401 | 402 | signal.fire(Some(conn_id), None); 403 | } 404 | } 405 | --------------------------------------------------------------------------------