1use std::time::{Duration, Instant, SystemTime};
16
17use crate::message::{
18 BatchMsg, Message, PrintLineMsg, QuitMsg, RequestWindowSizeMsg, SequenceMsg, SetWindowTitleMsg,
19};
20
21#[cfg(feature = "async")]
22use std::future::Future;
23#[cfg(feature = "async")]
24use std::pin::Pin;
25
26pub struct Cmd(Box<dyn FnOnce() -> Option<Message> + Send + 'static>);
47
48impl Cmd {
49 pub fn new<F>(f: F) -> Self
51 where
52 F: FnOnce() -> Message + Send + 'static,
53 {
54 Self(Box::new(move || Some(f())))
55 }
56
57 pub fn new_optional<F>(f: F) -> Self
59 where
60 F: FnOnce() -> Option<Message> + Send + 'static,
61 {
62 Self(Box::new(f))
63 }
64
65 pub fn none() -> Option<Self> {
67 None
68 }
69
70 pub fn execute(self) -> Option<Message> {
72 (self.0)()
73 }
74
75 pub fn blocking<F>(f: F) -> Self
100 where
101 F: FnOnce() -> Message + Send + 'static,
102 {
103 Self::new(f)
107 }
108
109 pub fn blocking_result<F, T, E, S, Err>(f: F, on_success: S, on_error: Err) -> Self
132 where
133 F: FnOnce() -> Result<T, E> + Send + 'static,
134 S: FnOnce(T) -> Message + Send + 'static,
135 Err: FnOnce(E) -> Message + Send + 'static,
136 {
137 Self::new(move || match f() {
138 Ok(value) => on_success(value),
139 Err(err) => on_error(err),
140 })
141 }
142}
143
144#[cfg(feature = "async")]
171#[allow(clippy::type_complexity)]
172pub struct AsyncCmd(
173 Box<dyn FnOnce() -> Pin<Box<dyn Future<Output = Option<Message>> + Send>> + Send + 'static>,
174);
175
176#[cfg(feature = "async")]
177impl AsyncCmd {
178 pub fn new<F, Fut>(f: F) -> Self
180 where
181 F: FnOnce() -> Fut + Send + 'static,
182 Fut: Future<Output = Message> + Send + 'static,
183 {
184 Self(Box::new(move || Box::pin(async move { Some(f().await) })))
185 }
186
187 pub fn new_optional<F, Fut>(f: F) -> Self
189 where
190 F: FnOnce() -> Fut + Send + 'static,
191 Fut: Future<Output = Option<Message>> + Send + 'static,
192 {
193 Self(Box::new(move || Box::pin(f())))
194 }
195
196 pub fn none() -> Option<Self> {
198 None
199 }
200
201 pub async fn execute(self) -> Option<Message> {
203 (self.0)().await
204 }
205}
206
207#[cfg(feature = "async")]
209pub(crate) enum CommandKind {
210 Sync(Cmd),
212 Async(AsyncCmd),
214}
215
216#[cfg(feature = "async")]
217impl CommandKind {
218 pub async fn execute(self) -> Option<Message> {
220 match self {
221 CommandKind::Sync(cmd) => {
222 tokio::task::spawn_blocking(move || cmd.execute())
224 .await
225 .ok()
226 .flatten()
227 }
228 CommandKind::Async(cmd) => cmd.execute().await,
229 }
230 }
231}
232
233#[cfg(feature = "async")]
234impl From<Cmd> for CommandKind {
235 fn from(cmd: Cmd) -> Self {
236 CommandKind::Sync(cmd)
237 }
238}
239
240#[cfg(feature = "async")]
241impl From<AsyncCmd> for CommandKind {
242 fn from(cmd: AsyncCmd) -> Self {
243 CommandKind::Async(cmd)
244 }
245}
246
247pub fn batch(cmds: Vec<Option<Cmd>>) -> Option<Cmd> {
263 let valid_cmds: Vec<Cmd> = cmds.into_iter().flatten().collect();
264
265 match valid_cmds.len() {
266 0 => None,
267 1 => valid_cmds.into_iter().next(),
268 _ => Some(Cmd::new_optional(move || {
269 Some(Message::new(BatchMsg(valid_cmds)))
270 })),
271 }
272}
273
274pub fn sequence(cmds: Vec<Option<Cmd>>) -> Option<Cmd> {
290 let valid_cmds: Vec<Cmd> = cmds.into_iter().flatten().collect();
291
292 match valid_cmds.len() {
293 0 => None,
294 1 => valid_cmds.into_iter().next(),
295 _ => Some(Cmd::new_optional(move || {
296 Some(Message::new(SequenceMsg(valid_cmds)))
297 })),
298 }
299}
300
301pub fn quit() -> Cmd {
303 Cmd::new(|| Message::new(QuitMsg))
304}
305
306pub fn tick<F>(duration: Duration, f: F) -> Cmd
325where
326 F: FnOnce(Instant) -> Message + Send + 'static,
327{
328 Cmd::new(move || {
329 std::thread::sleep(duration);
330 f(Instant::now())
331 })
332}
333
334pub fn every<F>(duration: Duration, f: F) -> Cmd
353where
354 F: FnOnce(Instant) -> Message + Send + 'static,
355{
356 Cmd::new(move || {
357 let duration_nanos = duration.as_nanos() as u64;
358 if duration_nanos == 0 {
359 return f(Instant::now());
361 }
362
363 let now_nanos = SystemTime::now()
365 .duration_since(SystemTime::UNIX_EPOCH)
366 .map(|d| d.as_nanos() as u64)
367 .unwrap_or(0);
368 let next_tick_nanos = ((now_nanos / duration_nanos) + 1) * duration_nanos;
370 let sleep_nanos = next_tick_nanos - now_nanos;
371 std::thread::sleep(Duration::from_nanos(sleep_nanos));
372 f(Instant::now())
373 })
374}
375
376#[cfg(feature = "async")]
398pub fn tick_async<F>(duration: Duration, f: F) -> AsyncCmd
399where
400 F: FnOnce(Instant) -> Message + Send + 'static,
401{
402 AsyncCmd::new(move || async move {
403 tokio::time::sleep(duration).await;
404 f(Instant::now())
405 })
406}
407
408#[cfg(feature = "async")]
426pub fn every_async<F>(duration: Duration, f: F) -> AsyncCmd
427where
428 F: FnOnce(Instant) -> Message + Send + 'static,
429{
430 AsyncCmd::new(move || async move {
431 let duration_nanos = duration.as_nanos() as u64;
432 if duration_nanos == 0 {
433 return f(Instant::now());
435 }
436
437 let now_nanos = SystemTime::now()
439 .duration_since(SystemTime::UNIX_EPOCH)
440 .map(|d| d.as_nanos() as u64)
441 .unwrap_or(0);
442 let next_tick_nanos = ((now_nanos / duration_nanos) + 1) * duration_nanos;
444 let sleep_nanos = next_tick_nanos - now_nanos;
445 tokio::time::sleep(Duration::from_nanos(sleep_nanos)).await;
446 f(Instant::now())
447 })
448}
449
450pub fn set_window_title(title: impl Into<String>) -> Cmd {
452 let title = title.into();
453 Cmd::new(move || Message::new(SetWindowTitleMsg(title)))
454}
455
456pub fn window_size() -> Cmd {
460 Cmd::new(|| Message::new(RequestWindowSizeMsg))
461}
462
463pub fn println(msg: impl Into<String>) -> Cmd {
487 let msg = msg.into();
488 Cmd::new(move || Message::new(PrintLineMsg(msg)))
489}
490
491pub fn printf(msg: impl Into<String>) -> Cmd {
522 println(msg)
523}
524
525#[cfg(test)]
526mod tests {
527 use super::*;
528
529 #[test]
530 fn test_cmd_new() {
531 let cmd = Cmd::new(|| Message::new(42i32));
532 let msg = cmd.execute().unwrap();
533 assert_eq!(msg.downcast::<i32>().unwrap(), 42);
534 }
535
536 #[test]
537 fn test_cmd_none() {
538 assert!(Cmd::none().is_none());
539 }
540
541 #[test]
542 fn test_batch_empty() {
543 let cmd = batch(vec![]);
544 assert!(cmd.is_none());
545 }
546
547 #[test]
548 fn test_batch_single() {
549 let cmd = batch(vec![Some(Cmd::new(|| Message::new(42i32)))]);
550 assert!(cmd.is_some());
551 }
552
553 #[test]
554 fn test_sequence_empty() {
555 let cmd = sequence(vec![]);
556 assert!(cmd.is_none());
557 }
558
559 #[test]
564 fn test_batch_multiple_commands() {
565 let cmd = batch(vec![
567 Some(Cmd::new(|| Message::new(1i32))),
568 Some(Cmd::new(|| Message::new(2i32))),
569 Some(Cmd::new(|| Message::new(3i32))),
570 ]);
571 assert!(cmd.is_some());
572
573 let msg = cmd.unwrap().execute().unwrap();
575 assert!(msg.is::<BatchMsg>());
576 }
577
578 #[test]
579 fn test_batch_filters_none_values() {
580 let cmd = batch(vec![
582 Some(Cmd::new(|| Message::new(1i32))),
583 None, Some(Cmd::new(|| Message::new(2i32))),
585 None, ]);
587 assert!(cmd.is_some());
588
589 let msg = cmd.unwrap().execute().unwrap();
591 let batch_msg = msg.downcast::<BatchMsg>().unwrap();
592 assert_eq!(batch_msg.0.len(), 2);
594 }
595
596 #[test]
597 fn test_batch_all_none_returns_none() {
598 let cmd = batch(vec![None, None, None]);
600 assert!(cmd.is_none());
601 }
602
603 #[test]
604 fn test_batch_mixed_with_single_some() {
605 let cmd = batch(vec![None, Some(Cmd::new(|| Message::new(42i32))), None]);
607 assert!(cmd.is_some());
608 }
609
610 #[test]
611 fn test_sequence_single() {
612 let cmd = sequence(vec![Some(Cmd::new(|| Message::new(42i32)))]);
614 assert!(cmd.is_some());
615
616 let msg = cmd.unwrap().execute().unwrap();
618 assert!(msg.is::<i32>());
619 assert_eq!(msg.downcast::<i32>().unwrap(), 42);
620 }
621
622 #[test]
623 fn test_sequence_multiple_commands() {
624 let cmd = sequence(vec![
626 Some(Cmd::new(|| Message::new(1i32))),
627 Some(Cmd::new(|| Message::new(2i32))),
628 Some(Cmd::new(|| Message::new(3i32))),
629 ]);
630 assert!(cmd.is_some());
631
632 let msg = cmd.unwrap().execute().unwrap();
634 assert!(msg.is::<SequenceMsg>());
635 }
636
637 #[test]
638 fn test_sequence_filters_none_values() {
639 let cmd = sequence(vec![
641 Some(Cmd::new(|| Message::new(1i32))),
642 None,
643 Some(Cmd::new(|| Message::new(2i32))),
644 ]);
645 assert!(cmd.is_some());
646
647 let msg = cmd.unwrap().execute().unwrap();
649 let seq_msg = msg.downcast::<SequenceMsg>().unwrap();
650 assert_eq!(seq_msg.0.len(), 2);
651 }
652
653 #[test]
654 fn test_sequence_all_none_returns_none() {
655 let cmd = sequence(vec![None, None]);
657 assert!(cmd.is_none());
658 }
659
660 #[test]
661 fn test_cmd_new_with_closure() {
662 let cmd = Cmd::new(|| Message::new("hello"));
664 let msg = cmd.execute().unwrap();
665 assert!(msg.is::<&str>());
666 assert_eq!(msg.downcast::<&str>().unwrap(), "hello");
667 }
668
669 #[test]
670 fn test_cmd_new_with_captured_value() {
671 let value = 42i32;
673 let cmd = Cmd::new(move || Message::new(value));
674 let msg = cmd.execute().unwrap();
675 assert_eq!(msg.downcast::<i32>().unwrap(), 42);
676 }
677
678 #[test]
679 fn test_cmd_new_optional_some() {
680 let cmd = Cmd::new_optional(|| Some(Message::new(42i32)));
682 let msg = cmd.execute();
683 assert!(msg.is_some());
684 assert_eq!(msg.unwrap().downcast::<i32>().unwrap(), 42);
685 }
686
687 #[test]
688 fn test_cmd_new_optional_none() {
689 let cmd = Cmd::new_optional(|| None);
691 let msg = cmd.execute();
692 assert!(msg.is_none());
693 }
694
695 #[test]
696 fn test_blocking_executes() {
697 use std::sync::Arc;
699 use std::sync::atomic::{AtomicBool, Ordering};
700
701 let executed = Arc::new(AtomicBool::new(false));
702 let executed_clone = Arc::clone(&executed);
703
704 let cmd = Cmd::blocking(move || {
705 executed_clone.store(true, Ordering::SeqCst);
706 Message::new(())
707 });
708
709 let msg = cmd.execute();
710 assert!(msg.is_some());
711 assert!(executed.load(Ordering::SeqCst));
712 }
713
714 #[test]
715 fn test_quit() {
716 let cmd = quit();
717 let msg = cmd.execute().unwrap();
718 assert!(msg.is::<QuitMsg>());
719 }
720
721 #[test]
722 fn test_set_window_title() {
723 let cmd = set_window_title("My App");
724 let msg = cmd.execute().unwrap();
725 assert!(msg.is::<SetWindowTitleMsg>());
726 }
727
728 #[test]
729 fn test_println() {
730 let cmd = println("Hello, World!");
731 let msg = cmd.execute().unwrap();
732 assert!(msg.is::<PrintLineMsg>());
733 let print_msg = msg.downcast::<PrintLineMsg>().unwrap();
734 assert_eq!(print_msg.0, "Hello, World!");
735 }
736
737 #[test]
738 fn test_println_from_string() {
739 let cmd = println(String::from("From String"));
740 let msg = cmd.execute().unwrap();
741 let print_msg = msg.downcast::<PrintLineMsg>().unwrap();
742 assert_eq!(print_msg.0, "From String");
743 }
744
745 #[test]
746 fn test_printf() {
747 let cmd = printf(format!("Count: {}", 42));
748 let msg = cmd.execute().unwrap();
749 assert!(msg.is::<PrintLineMsg>());
750 let print_msg = msg.downcast::<PrintLineMsg>().unwrap();
751 assert_eq!(print_msg.0, "Count: 42");
752 }
753
754 #[test]
755 fn test_println_multiline() {
756 let cmd = println("Line 1\nLine 2\nLine 3");
757 let msg = cmd.execute().unwrap();
758 let print_msg = msg.downcast::<PrintLineMsg>().unwrap();
759 assert_eq!(print_msg.0, "Line 1\nLine 2\nLine 3");
760 }
761
762 #[test]
763 fn test_blocking() {
764 let cmd = Cmd::blocking(|| Message::new("blocked"));
765 let msg = cmd.execute().unwrap();
766 assert_eq!(msg.downcast::<&str>().unwrap(), "blocked");
767 }
768
769 #[test]
770 fn test_blocking_result_success() {
771 struct FileContent(String);
772
773 let cmd = Cmd::blocking_result(
774 || Ok::<_, std::io::Error>("file content".to_string()),
775 |content| Message::new(FileContent(content)),
776 |_err| Message::new("error"),
777 );
778 let msg = cmd.execute().unwrap();
779 assert!(msg.is::<FileContent>());
780 let content = msg.downcast::<FileContent>().unwrap();
781 assert_eq!(content.0, "file content");
782 }
783
784 #[test]
785 fn test_blocking_result_error() {
786 #[allow(dead_code)] struct FileError(std::io::Error);
788
789 let cmd = Cmd::blocking_result(
790 || {
791 Err::<String, _>(std::io::Error::new(
792 std::io::ErrorKind::NotFound,
793 "not found",
794 ))
795 },
796 |_content| Message::new("success"),
797 |err| Message::new(FileError(err)),
798 );
799 let msg = cmd.execute().unwrap();
800 assert!(msg.is::<FileError>());
801 }
802
803 #[cfg(feature = "async")]
808 mod async_tests {
809 use super::*;
810
811 #[tokio::test]
812 async fn test_async_cmd_new() {
813 let cmd = AsyncCmd::new(|| async { Message::new(42i32) });
814 let msg = cmd.execute().await.unwrap();
815 assert_eq!(msg.downcast::<i32>().unwrap(), 42);
816 }
817
818 #[tokio::test]
819 async fn test_async_cmd_new_optional_some() {
820 let cmd = AsyncCmd::new_optional(|| async { Some(Message::new("hello")) });
821 let msg = cmd.execute().await.unwrap();
822 assert_eq!(msg.downcast::<&str>().unwrap(), "hello");
823 }
824
825 #[tokio::test]
826 async fn test_async_cmd_new_optional_none() {
827 let cmd = AsyncCmd::new_optional(|| async { None });
828 assert!(cmd.execute().await.is_none());
829 }
830
831 #[tokio::test]
832 async fn test_async_cmd_none() {
833 assert!(AsyncCmd::none().is_none());
834 }
835
836 #[tokio::test]
837 async fn test_command_kind_sync() {
838 let cmd = Cmd::new(|| Message::new(100i32));
839 let kind: CommandKind = cmd.into();
840 let msg = kind.execute().await.unwrap();
841 assert_eq!(msg.downcast::<i32>().unwrap(), 100);
842 }
843
844 #[tokio::test]
845 async fn test_command_kind_async() {
846 let cmd = AsyncCmd::new(|| async { Message::new(200i32) });
847 let kind: CommandKind = cmd.into();
848 let msg = kind.execute().await.unwrap();
849 assert_eq!(msg.downcast::<i32>().unwrap(), 200);
850 }
851
852 #[tokio::test]
853 async fn test_tick_async_produces_message() {
854 struct TickMsg(#[allow(dead_code)] Instant);
855
856 let cmd = tick_async(Duration::from_millis(1), |t| Message::new(TickMsg(t)));
857 let msg = cmd.execute().await.unwrap();
858 assert!(msg.is::<TickMsg>());
859 }
860
861 #[tokio::test]
862 async fn test_blocking_via_spawn_blocking() {
863 let cmd = Cmd::blocking(|| {
865 std::thread::sleep(Duration::from_millis(1));
867 Message::new("blocked_async")
868 });
869 let kind: CommandKind = cmd.into();
870 let msg = kind.execute().await.unwrap();
871 assert_eq!(msg.downcast::<&str>().unwrap(), "blocked_async");
872 }
873
874 #[tokio::test]
875 async fn test_blocking_result_via_spawn_blocking() {
876 #[allow(dead_code)]
877 struct FileContent(String);
878
879 let cmd = Cmd::blocking_result(
880 || {
881 std::thread::sleep(Duration::from_millis(1));
883 Ok::<_, std::io::Error>("async file content".to_string())
884 },
885 |content| Message::new(FileContent(content)),
886 |_err| Message::new("error"),
887 );
888 let kind: CommandKind = cmd.into();
889 let msg = kind.execute().await.unwrap();
890 assert!(msg.is::<FileContent>());
891 }
892
893 #[tokio::test]
898 async fn test_blocking_result_error_in_async_context() {
899 #[allow(dead_code)]
900 struct ErrorResult(String);
901
902 let cmd = Cmd::blocking_result(
903 || {
904 Err::<String, _>(std::io::Error::new(
905 std::io::ErrorKind::NotFound,
906 "not found",
907 ))
908 },
909 |_content| Message::new("success"),
910 |err| Message::new(ErrorResult(err.to_string())),
911 );
912 let kind: CommandKind = cmd.into();
913 let msg = kind.execute().await.unwrap();
914 assert!(msg.is::<ErrorResult>());
915 }
916
917 #[tokio::test]
918 async fn test_async_cmd_with_io_error() {
919 #[allow(dead_code)]
920 struct IoError(String);
921
922 let cmd = AsyncCmd::new(|| async {
923 let result: Result<String, std::io::Error> = Err(std::io::Error::new(
925 std::io::ErrorKind::NotFound,
926 "file not found",
927 ));
928 match result {
929 Ok(data) => Message::new(data),
930 Err(e) => Message::new(IoError(e.to_string())),
931 }
932 });
933 let msg = cmd.execute().await.unwrap();
934 assert!(msg.is::<IoError>());
935 }
936
937 #[tokio::test]
938 async fn test_async_cmd_optional_returns_none_on_error() {
939 let cmd = AsyncCmd::new_optional(|| async {
940 let result: Result<i32, &str> = Err("failed");
942 result.ok().map(Message::new)
943 });
944 assert!(cmd.execute().await.is_none());
945 }
946
947 #[tokio::test]
952 async fn test_tick_async_respects_duration() {
953 struct TimerFired;
954
955 let start = std::time::Instant::now();
956 let cmd = tick_async(Duration::from_millis(50), |_| Message::new(TimerFired));
957 let msg = cmd.execute().await.unwrap();
958 let elapsed = start.elapsed();
959
960 assert!(msg.is::<TimerFired>());
961 assert!(elapsed >= Duration::from_millis(50));
962 assert!(elapsed < Duration::from_millis(150)); }
964
965 #[tokio::test]
966 async fn test_async_cmd_with_timeout() {
967 use tokio::time::timeout;
968
969 struct SlowResult;
970
971 let cmd = AsyncCmd::new(|| async {
972 tokio::time::sleep(Duration::from_millis(10)).await;
973 Message::new(SlowResult)
974 });
975
976 let result = timeout(Duration::from_millis(100), cmd.execute()).await;
978 assert!(result.is_ok());
979 assert!(result.unwrap().unwrap().is::<SlowResult>());
980 }
981
982 #[tokio::test]
983 async fn test_async_cmd_timeout_expires() {
984 use tokio::time::timeout;
985
986 let cmd = AsyncCmd::new(|| async {
987 tokio::time::sleep(Duration::from_secs(10)).await;
988 Message::new("never")
989 });
990
991 let result = timeout(Duration::from_millis(10), cmd.execute()).await;
993 assert!(result.is_err()); }
995
996 #[tokio::test]
1001 async fn test_concurrent_async_commands() {
1002 use std::sync::Arc;
1003 use std::sync::atomic::{AtomicUsize, Ordering};
1004
1005 #[allow(dead_code)]
1006 struct CounterResult(usize);
1007
1008 let counter = Arc::new(AtomicUsize::new(0));
1009 let mut handles = vec![];
1010
1011 for i in 0..10 {
1013 let counter = Arc::clone(&counter);
1014 let cmd = AsyncCmd::new(move || async move {
1015 counter.fetch_add(1, Ordering::SeqCst);
1016 tokio::time::sleep(Duration::from_millis(1)).await;
1017 Message::new(CounterResult(i))
1018 });
1019 handles.push(tokio::spawn(async move { cmd.execute().await }));
1020 }
1021
1022 for handle in handles {
1024 let msg = handle.await.unwrap().unwrap();
1025 assert!(msg.is::<CounterResult>());
1026 }
1027
1028 assert_eq!(counter.load(Ordering::SeqCst), 10);
1030 }
1031
1032 #[tokio::test]
1033 async fn test_concurrent_command_kind_mixed() {
1034 use std::sync::Arc;
1035 use std::sync::atomic::{AtomicUsize, Ordering};
1036
1037 let counter = Arc::new(AtomicUsize::new(0));
1038 let mut handles = vec![];
1039
1040 for i in 0..6 {
1042 let counter = Arc::clone(&counter);
1043 let kind: CommandKind = if i % 2 == 0 {
1044 let counter = Arc::clone(&counter);
1046 Cmd::new(move || {
1047 counter.fetch_add(1, Ordering::SeqCst);
1048 Message::new(i)
1049 })
1050 .into()
1051 } else {
1052 let counter = Arc::clone(&counter);
1054 AsyncCmd::new(move || async move {
1055 counter.fetch_add(1, Ordering::SeqCst);
1056 Message::new(i)
1057 })
1058 .into()
1059 };
1060 handles.push(tokio::spawn(async move { kind.execute().await }));
1061 }
1062
1063 for handle in handles {
1065 assert!(handle.await.unwrap().is_some());
1066 }
1067
1068 assert_eq!(counter.load(Ordering::SeqCst), 6);
1070 }
1071
1072 #[tokio::test]
1073 async fn test_command_kind_ordering_within_single_task() {
1074 use std::sync::Arc;
1075 use std::sync::atomic::{AtomicUsize, Ordering};
1076
1077 #[derive(Debug, PartialEq)]
1078 struct OrderedResult {
1079 index: usize,
1080 order: usize,
1081 }
1082
1083 let order = Arc::new(AtomicUsize::new(0));
1084 let mut results = vec![];
1085
1086 for i in 0..3usize {
1088 let order = Arc::clone(&order);
1089 let cmd = AsyncCmd::new(move || async move {
1090 let n = order.fetch_add(1, Ordering::SeqCst);
1091 Message::new(OrderedResult { index: i, order: n })
1092 });
1093 let msg = cmd.execute().await.unwrap();
1094 results.push(msg.downcast::<OrderedResult>().unwrap());
1095 }
1096
1097 assert_eq!(results[0], OrderedResult { index: 0, order: 0 });
1099 assert_eq!(results[1], OrderedResult { index: 1, order: 1 });
1100 assert_eq!(results[2], OrderedResult { index: 2, order: 2 });
1101 }
1102
1103 #[tokio::test]
1108 async fn test_async_cmd_with_large_message() {
1109 let large_data = vec![42u8; 1024 * 1024]; let cmd = AsyncCmd::new(move || async move { Message::new(large_data) });
1111 let msg = cmd.execute().await.unwrap();
1112 let data = msg.downcast::<Vec<u8>>().unwrap();
1113 assert_eq!(data.len(), 1024 * 1024);
1114 assert!(data.iter().all(|&b| b == 42));
1115 }
1116
1117 #[tokio::test]
1118 async fn test_every_async_produces_message() {
1119 struct EveryTick;
1120
1121 let cmd = every_async(Duration::from_millis(1), |_| Message::new(EveryTick));
1122 let msg = cmd.execute().await.unwrap();
1123 assert!(msg.is::<EveryTick>());
1124 }
1125
1126 #[tokio::test]
1127 async fn test_command_kind_from_conversions() {
1128 let sync_cmd = Cmd::new(|| Message::new(1i32));
1130 let kind: CommandKind = sync_cmd.into();
1131 assert!(matches!(kind, CommandKind::Sync(_)));
1132
1133 let async_cmd = AsyncCmd::new(|| async { Message::new(2i32) });
1135 let kind: CommandKind = async_cmd.into();
1136 assert!(matches!(kind, CommandKind::Async(_)));
1137 }
1138
1139 #[tokio::test]
1140 async fn test_spawn_blocking_does_not_block_runtime() {
1141 use std::time::Instant;
1142
1143 let start = Instant::now();
1144
1145 let cmd1: CommandKind = Cmd::blocking(|| {
1147 std::thread::sleep(Duration::from_millis(50));
1148 Message::new(1)
1149 })
1150 .into();
1151
1152 let cmd2: CommandKind = Cmd::blocking(|| {
1153 std::thread::sleep(Duration::from_millis(50));
1154 Message::new(2)
1155 })
1156 .into();
1157
1158 let (r1, r2) = tokio::join!(cmd1.execute(), cmd2.execute());
1159
1160 let elapsed = start.elapsed();
1161
1162 assert!(r1.is_some());
1163 assert!(r2.is_some());
1164
1165 assert!(elapsed < Duration::from_millis(100));
1167 }
1168 }
1169}