hyperion/effects/
instance.rs1use std::time::{Duration, Instant};
2
3use async_trait::async_trait;
4use thiserror::Error;
5use tokio::sync::{
6 mpsc::{Receiver, Sender},
7 Mutex,
8};
9
10use crate::{
11 image::{RawImage, RawImageError},
12 models::Color,
13};
14
15use super::EffectMessageKind;
16
17#[derive(Debug, Clone, Copy, PartialEq)]
18pub enum ControlMessage {
19 Abort,
20}
21
22struct InstanceMethodsData {
23 crx: Receiver<ControlMessage>,
24 aborted: bool,
25}
26
27pub struct InstanceMethods {
28 tx: Sender<EffectMessageKind>,
29 led_count: usize,
30 deadline: Option<Instant>,
31 data: Mutex<InstanceMethodsData>,
32}
33
34impl InstanceMethods {
35 pub fn new(
36 tx: Sender<EffectMessageKind>,
37 crx: Receiver<ControlMessage>,
38 led_count: usize,
39 duration: Option<Duration>,
40 ) -> Self {
41 Self {
42 tx,
43 led_count,
44 deadline: duration.map(|d| Instant::now() + d),
45 data: Mutex::new(InstanceMethodsData {
46 crx,
47 aborted: false,
48 }),
49 }
50 }
51
52 fn completed(&self, data: &InstanceMethodsData) -> bool {
53 data.aborted || self.deadline.map(|d| Instant::now() > d).unwrap_or(false)
54 }
55
56 async fn poll_control(&self) -> Result<(), RuntimeMethodError> {
58 let mut data = self.data.lock().await;
59 match data.crx.try_recv() {
60 Ok(m) => match m {
61 ControlMessage::Abort => {
62 data.aborted = true;
63 return Err(RuntimeMethodError::EffectAborted);
64 }
65 },
66 Err(err) => {
67 match err {
68 tokio::sync::mpsc::error::TryRecvError::Empty => {
69 }
71 tokio::sync::mpsc::error::TryRecvError::Disconnected => {
72 data.aborted = true;
74 return Err(RuntimeMethodError::EffectAborted);
75 }
76 }
77 }
78 }
79
80 if self.completed(&data) {
81 Err(RuntimeMethodError::EffectAborted)
82 } else {
83 Ok(())
84 }
85 }
86
87 async fn wrap_result<T, E: Into<RuntimeMethodError>>(
88 &self,
89 res: Result<T, E>,
90 ) -> Result<T, RuntimeMethodError> {
91 match res {
92 Ok(t) => Ok(t),
93 Err(err) => {
94 self.data.lock().await.aborted = true;
96 Err(err.into())
97 }
98 }
99 }
100}
101
102#[async_trait]
103impl RuntimeMethods for InstanceMethods {
104 fn get_led_count(&self) -> usize {
105 self.led_count
106 }
107
108 async fn abort(&self) -> bool {
109 self.poll_control().await.is_err()
110 }
111
112 async fn set_color(&self, color: crate::models::Color) -> Result<(), RuntimeMethodError> {
113 self.poll_control().await?;
114
115 self.wrap_result(self.tx.send(EffectMessageKind::SetColor { color }).await)
116 .await
117 }
118
119 async fn set_led_colors(
120 &self,
121 colors: Vec<crate::models::Color>,
122 ) -> Result<(), RuntimeMethodError> {
123 self.poll_control().await?;
124
125 self.wrap_result(
126 self.tx
127 .send(EffectMessageKind::SetLedColors {
128 colors: colors.into(),
129 })
130 .await,
131 )
132 .await
133 }
134
135 async fn set_image(&self, image: RawImage) -> Result<(), RuntimeMethodError> {
136 self.poll_control().await?;
137
138 self.wrap_result(
139 self.tx
140 .send(EffectMessageKind::SetImage {
141 image: image.into(),
142 })
143 .await,
144 )
145 .await
146 }
147}
148
149#[async_trait]
150pub trait RuntimeMethods: Send {
151 fn get_led_count(&self) -> usize;
152 async fn abort(&self) -> bool;
153
154 async fn set_color(&self, color: Color) -> Result<(), RuntimeMethodError>;
155 async fn set_led_colors(&self, colors: Vec<Color>) -> Result<(), RuntimeMethodError>;
156 async fn set_image(&self, image: RawImage) -> Result<(), RuntimeMethodError>;
157}
158
159#[derive(Debug, Error)]
160pub enum RuntimeMethodError {
161 #[cfg(feature = "python")]
162 #[error("Invalid arguments to hyperion.{name}")]
163 InvalidArguments { name: &'static str },
164 #[cfg(feature = "python")]
165 #[error("Length of bytearray argument should be 3*ledCount")]
166 InvalidByteArray,
167 #[error("Effect aborted")]
168 EffectAborted,
169 #[error(transparent)]
170 InvalidImageData(#[from] RawImageError),
171}
172
173impl<T> From<tokio::sync::mpsc::error::SendError<T>> for RuntimeMethodError {
174 fn from(_: tokio::sync::mpsc::error::SendError<T>) -> Self {
175 Self::EffectAborted
176 }
177}