ticked_async_executor 0.4.0

Local executor that runs woken async tasks when it is ticked
Documentation
```toml
# default = ["tick_event", "timer_registration"]

# default = ["channel-flume", "tick_event", "timer_registration"]

# channel-flume = []


dhat = { version = "0.3" }
```

```rust

    #[global_allocator]
    static ALLOC: dhat::Alloc = dhat::Alloc;

    pub struct DhatTester {
        name: &'static str,
        profiler: Option<dhat::Profiler>,
    }

    impl DhatTester {
        pub fn new(_name: &'static str) -> Self {
            let this = Self::new_dhat(_name);
            this
        }

        pub fn stats(_stats_cb: impl FnOnce(dhat::HeapStats)) {
            {
                let stats = dhat::HeapStats::get();
                _stats_cb(stats);
            }
        }

        fn new_dhat(name: &'static str) -> Self {
            const DHAT_TEST_DIR: &str = "target/dhat";
            if !std::fs::exists(DHAT_TEST_DIR).unwrap() {
                let _ignore = std::fs::create_dir_all(DHAT_TEST_DIR);
            }
            let profiler = dhat::Profiler::builder()
                .file_name(format!("{DHAT_TEST_DIR}/{name}.json"))
                .build();
            Self {
                name,
                profiler: Some(profiler),
            }
        }
    }

    impl Drop for DhatTester {
        fn drop(&mut self) {
            let stats = dhat::HeapStats::get();
            println!("-------------");
            println!("{}:\n{stats:?}", self.name);
            drop(self.profiler.take().unwrap());
            println!("-------------");
        }
    }

    #[tokio::test]
    async fn test_memory_for_executor() {
        let mut executor = {
            let _profiler = DhatTester::new("test_memory_for_executor_1");
            let executor = TickedAsyncExecutor::default();
            executor
        };

        // let rt = {
        //     let _profiler = DhatTester::new("test_tokio");
        //     let rt = tokio::runtime::Builder::new_multi_thread()
        //         .worker_threads(1)
        //         .enable_all()
        //         .build()
        //         .unwrap();
        //     rt
        // };

        {
            let _profiler = DhatTester::new("test_tokio_spawn");
            // let handle = rt.spawn(async move {});
            let _handle = tokio::spawn(async move {
                for i in 0..1000000 {
                    println!("I: {i}");
                }
            });
            // let _r = handle.await;
        }

        {
            let _profiler = DhatTester::new("test_memory_types");
            // let _a = std::rc::Rc::new(10);
            let _a = 10;
            // let _b = std::sync::Arc::new(10);
        }

        let (tx, mut rx) = {
            // oneshot: 72
            // watch: 344
            // mpsc: 800
            // broadcast: 176
            let _profiler = DhatTester::new("test_memory_for_executor_channel");
            // let (tx, rx) = tokio::sync::oneshot::channel::<usize>();
            // let (tx, rx) = tokio::sync::watch::channel::<usize>(1);
            // let (tx, rx) = tokio::sync::mpsc::channel::<usize>(1);
            let (tx, rx) = tokio::sync::broadcast::channel::<usize>(1);
            (tx, rx)
        };

        {
            let _profiler = DhatTester::new("test_memory_for_executor_2");
            executor
                .spawn_local("_", async move {
                    // let d = rx.await;
                    // println!("D: {d:?}");

                    // rx.changed().await.unwrap();
                    // let d = *rx.borrow_and_update();
                    // println!("D: {d:?}");

                    // rx.changed().await.unwrap();
                    // let d = *rx.borrow_and_update();
                    // println!("D: {d:?}");

                    let d = rx.recv().await.unwrap();
                    println!("D: {d:?}");

                    let d = rx.recv().await.unwrap();
                    println!("D: {d:?}");
                })
                .detach();
        }

        {
            let _profiler = DhatTester::new("test_memory_for_executor_3");
            executor.tick(1.0, None);
            executor.tick(1.0, None);
            executor.tick(1.0, None);
            executor.tick(1.0, None);
            assert_eq!(executor.num_tasks(), 1);
            // tx.send(100).unwrap();
            // tx.send_replace(100);
            // tx.try_send(100).unwrap();
            tx.send(100).unwrap();

            executor.tick(1.0, None);
            executor.tick(1.0, None);
            executor.tick(1.0, None);
            executor.tick(1.0, None);
            assert_eq!(executor.num_tasks(), 1);
            // tx.send_replace(200);
            // tx.try_send(200).unwrap();
            tx.send(200).unwrap();

            executor.wait_till_completed(1.0);
        }
    }

    #[test]
    fn test_future() {
        let (tx, rx) = tokio::sync::oneshot::channel::<usize>();

        let future = async move {
            let data = rx.await;
            println!("Data: {data:?}");
        };

        let waker = std::task::Waker::noop();
        let mut cx = std::task::Context::from_waker(waker);

        let mut future = std::pin::pin!(future);

        {
            let status = future.as_mut().poll(&mut cx);
            assert_eq!(status, std::task::Poll::Pending);
        }

        {
            let status = future.as_mut().poll(&mut cx);
            assert_eq!(status, std::task::Poll::Pending);
        }

        {
            let _r = tx.send(1000).unwrap();
            let status = future.as_mut().poll(&mut cx);
            assert_eq!(status, std::task::Poll::Ready(()));
        }
    }

    // #[test]
    // fn test_offload() {
    //     {
    //         let _profiler = DhatTester::new("test_thread_scope");
    //         std::thread::scope(|s| {
    //             s.spawn(|| {});
    //         });
    //     }

    //     {
    //         let _profiler = DhatTester::new("test_thread_scope2");
    //         std::thread::scope(|s| {});
    //     }

    //     let runtime = tokio::runtime::Builder::new_multi_thread()
    //         .worker_threads(1)
    //         .enable_all()
    //         .build()
    //         .unwrap();

    //     {
    //         let _profiler = DhatTester::new("test_thread_scope3");
    //         let h = runtime.spawn(async move {
    //             println!("OK");
    //         });
    //         while !h.is_finished() {}
    //         // runtime.spawn_blocking(|| async move { println!("OK") });
    //     }

    //     // let alloc = bumpalo::Bump::new();
    //     // let future = alloc.alloc(async move {});
    //     // let f = std::pin::pin!(future);
    //     // f.as_mut().poll();
    //     // std::pin::pin!(future).as_mut().poll();
    // }
```