mechutil 0.8.1

Utility structures and functions for mechatronics applications.
Documentation
#![allow(dead_code)]
#![allow(unused_imports)]
#![allow(rustdoc::invalid_rust_codeblocks)]
//
// (C) Copyright 2022 Automated Design Corp. All Rights Reserved.
// File Created: Thursday, 31st March 2022 1:26:45 pm

//!
//! # Notifier
//! One-to-many event notification class.
//!
//! A great reference to the concepts used in implementing notifier is
//! found at
//! https://stackoverflow.com/questions/41081240/idiomatic-callbacks-in-rust
//!
//! # Lifetime
//! the 'a lifetime parameter avoids needing to use static functions and
//! global variables for all the callbacks. A more complete description
//! from the StackOverflow article:
//!
//! # Lifetime of references inside boxed closures
//! The 'static lifetime bound on the type of the c argument accepted by
//! set_callback is a simple way to convince the compiler that references
//! contained in c, which might be a closure that refers to its environment,
//! only refer to global values and will therefore remain valid throughout
//! the use of the callback. But the static bound is also very heavy-handed:
//! while it accepts closures that own objects just fine (which we've ensured
//! above by making the closure move), it rejects closures that refer to
//! local environment, even when they only refer to values that outlive the
//! processor and would in fact be safe.
//!
//! As we only need the callbacks alive as long as the processor is alive,
//! we should try to tie their lifetime to that of the processor, which is
//! a less strict bound than 'static. But if we just remove the 'static lifetime
//! bound from set_callback, it no longer compiles. This is because
//! set_callback creates a new box and assigns it to the callback field
//! defined as Box<dyn FnMut()>. Since the definition doesn't specify a lifetime for
//! the boxed trait object, 'static is implied, and the assignment would effectively
//! widen the lifetime (from an unnamed arbitrary lifetime of the callback to 'static),
//! which is disallowed. The fix is to provide an explicit lifetime for the processor
//! and tie that lifetime to both the references in the box and the references in the
//! callback received by set_callback:
//!
//! ```
//! struct Processor<'a> {
//!     callback: Box<dyn FnMut() + 'a>,
//! }
//!
//!
//! impl<'a> Processor<'a> {
//!     fn set_callback(&mut self, c: impl FnMut() + 'a) {
//!         self.callback = Box::new(c);
//!     }
//!
//!     // ...
//! }
//! ```
//!  
//! With these lifetimes being made explicit, it is no longer necessary to use 'static.
//! The closure can now refer to the local s object, i.e. no longer has to be move,
//! provided that the definition of s is placed before the definition of p to ensure
//! that the string outlives the processor.
//!
//!
//!

use std::sync::Mutex;

/// One-to-many event notification class.
pub struct Notifier<'a, E> {
    pub subscribers: Vec<Box<dyn Fn(&E) + Send + Sync + 'a>>,
}

impl<'a, E> Notifier<'a, E> {
    /// Constructor
    pub fn new() -> Notifier<'a, E> {
        Notifier {
            subscribers: Vec::new(),
        }
    }

    /// Create a notifier with a callback right off the bat.
    /// This is just a shorthand that saves typing later.
    pub fn new_with_cb<F>(callback: F) -> Notifier<'a, E>
    where
        F: Fn(&E) + Send + Sync + 'a,
    {
        let mut ret = Notifier {
            subscribers: Vec::new(),
        };

        ret.register(callback);

        return ret;
    }

    /// Register a callback with this instance. A Notifier instance
    /// can have an unlimited number of callbacks registered.
    pub fn register<F>(&mut self, callback: F)
    where
        F: Fn(&E) + Send + Sync + 'a,
    {
        self.subscribers.push(Box::new(callback));
    }

    /// Signal all the registered callbacks.
    pub fn notify(&self, event: E) {
        for callback in &self.subscribers {
            callback(&event);
        }
    }
}

struct TestStruct {}

impl TestStruct {
    pub fn new() -> Self {
        return TestStruct {};
    }

    pub fn static_data_changed(val: &i32) {
        println!("Static Test struct received data change val {}!", val);
    }
}

struct TestGenerator<'a> {
    pub data_changed: Notifier<'a, u32>,
}

impl<'a> TestGenerator<'a> {
    pub fn fire(&self, val: u32) {
        self.data_changed.notify(val);
    }
}

struct TestListener {
    count: u32,
}

impl TestListener {
    pub fn new() -> Self {
        return TestListener { count: 0 };
    }

    pub fn member_data_changed(&mut self, val: u32) {
        // Here, there would be data in collections or connections
        // to an external device that we would need to access.
        self.count = val;
        println!("Count is now {}!", self.count);
    }
}

#[test]
fn test_notifier() {
    let mut n = Notifier::<i32>::new();
    n.register(|i| println!("i32 event received: {i}"));

    let mut sz = Notifier::<String>::new();
    sz.register(|s| println!("string event received: {s}"));

    let _tst = TestStruct::new();
    n.register(TestStruct::static_data_changed);

    println!("sending i32 event...");
    n.notify(4);

    println!("sending string event...");
    sz.notify("There is no spoon.".to_string());

    let l = Mutex::new(TestListener::new());

    let mut generator = TestGenerator {
        data_changed: Notifier::<u32>::new(),
    };

    let callback = |val: &u32| l.lock().unwrap().member_data_changed(*val);
    generator.data_changed.register(Box::new(callback));

    generator.fire(1234);
}