1use std::collections::hash_map::Entry;
2use std::collections::HashMap;
3use std::hash::{DefaultHasher, Hash, Hasher};
4use std::sync::{mpsc, OnceLock};
5use std::sync::mpsc::{Receiver, Sender};
6use std::thread;
7use common::fixture::{ChannelIndex, ChannelValue, Fixture, FixtureError, PropertyType, MAX_CHANNEL};
8use common::fixture::ChannelError::{ChannelAlreadyInUse, UniverseOutOfRange};
9use crate::fixture::fixture_command::FixtureCommand;
10use common::fixture::FixtureError::{DmxStateDesync, InvalidFixture};
11use common::logging::LogLevel::{Error, Info};
12use common::networking::subscription_objects::{DMXConfigForClientState, DMXConfigurationForClient};
13use common::{r_debug_log, r_log};
14use crate::fixture::fixture_engine::ChannelReservation::{Empty, Pending, Reserved};
15use crate::networking::on_dmx_config_update;
16
17#[derive(Clone, Debug)]
23enum ChannelReservation {
24 Empty,
25 Pending(String),
26 Reserved(String, PropertyType, usize),
27}
28
29pub struct FixtureEngine {
31 fixtures: HashMap<String, Fixture>,
32 dmx_config: Vec<[ChannelReservation; MAX_CHANNEL as usize]>,
33 receiver: Receiver<FixtureCommand>,
34}
35
36static FIXTURE_ACTION_SENDER: OnceLock<Sender<FixtureCommand>> = OnceLock::new();
38
39impl FixtureEngine {
40
41 pub fn spawn() -> Result<Receiver<(usize,Vec<Fixture>)>, &'static str > {
47
48 if FIXTURE_ACTION_SENDER.get().is_some() {
49 return Err("Critical Error: The fixture engine has already been started!");
50 }
51
52 let (tx, rx) = mpsc::channel();
53
54 let mut engine = Self {
55 fixtures: HashMap::new(),
56 dmx_config: Vec::new(),
57 receiver: rx,
58 };
59
60 if FIXTURE_ACTION_SENDER.set(tx).is_err() {
61 return Err("Race condition: Engine was started in parallel!");
62 }
63
64 let (interface_sender, interface_receiver) = mpsc::channel();
65
66 thread::spawn(move || {
67 engine.run(interface_sender)
68 });
69
70 r_debug_log!(Info, "Fixture Engine Actor thread started successfully.");
71
72 Ok(interface_receiver)
73 }
74
75 fn run(&mut self, interface_sender: Sender<(usize,Vec<Fixture>)>) {
81 while let Ok(command) = self.receiver.recv() {
82 let mut dmx_config_changed = false;
83 let mut dmx_values_changed = false;
84 match command {
85 FixtureCommand::SpawnFixture {name, fixture_type_name, start_channel, start_universe, reply_to } => {
86 let result = self.new_fixture(name, fixture_type_name, start_channel, start_universe);
87 reply_to.send(result.clone()).unwrap();
88 if result.is_ok() {
89 dmx_config_changed = true;
90 }
91 }
92
93 FixtureCommand::MoveFixture {name, new_channel, new_universe, reply_to} => {
94 let result = self.move_fixture(name, new_channel, new_universe);
95 reply_to.send(result.clone()).unwrap();
96 if result.is_ok() {
97 dmx_config_changed = true;
98 }
99 }
100
101 FixtureCommand::RemoveFixture {name, reply_to} => {
102 let result = self.remove_fixture(name);
103 reply_to.send(result.clone()).unwrap();
104 if result.is_ok() {
105 dmx_config_changed = true;
106 }
107 }
108
109 FixtureCommand::SetProperty {fixture_name, property, value, reply_to} => {
110 let result = self.set_property(fixture_name, property, value);
111 reply_to.send(result.clone()).unwrap();
112 if result.is_ok() {
113 dmx_values_changed = true;
114 }
115 }
116
117 FixtureCommand::GetType {fixture_name, reply_to} => {
118 reply_to.send(self.get_fixture_type_from_string(fixture_name)).unwrap();
119 }
120 }
121
122 if dmx_config_changed {
123 dmx_values_changed = true;
124 on_dmx_config_update(self.get_dmx_config_for_client());
125 }
126
127 if dmx_values_changed {
128 let universe_count = self.dmx_config.len();
129 let fixtures: Vec<Fixture> = self.fixtures.values().cloned().collect();
130 interface_sender.send((universe_count, fixtures)).unwrap();
131 }
132
133 }
134 }
135
136 fn new_fixture(
155 &mut self, name: String, fixture_type_name: String, start_channel: ChannelIndex, start_universe: usize
156 ) -> Result<(), FixtureError> {
157 let fixture = Fixture::new(
158 fixture_type_name, start_channel,start_universe, name.clone()
159 )?;
160
161 let max_universe = fixture
162 .iter_over_properties()
163 .flat_map(|(_, channel)| channel.get_channel_indices())
164 .map(|(_, universe_index)| universe_index)
165 .max()
166 .unwrap_or(start_universe);
167
168 self.ensure_universe_size(max_universe + 1);
169
170 fixture.iter_over_properties().try_for_each(|(_, channel)| {
172 for (channel_index, universe_index) in channel.get_channel_indices() {
173 let universe = self.dmx_config.get_mut(universe_index)
174 .ok_or(UniverseOutOfRange)?;
175 if let Reserved(existing, property, _) = &universe[channel_index as usize] {
176 return Err(ChannelAlreadyInUse(format!("{}, {}", existing, property)));
177 }
178
179 universe[channel_index as usize] = Pending(name.clone())
180 }
181
182 Ok(())
183 }).map_err(FixtureError::from)?;
184
185 if let Entry::Vacant(entry) = self.fixtures.entry(name.clone()) {
187 entry.insert(fixture.clone());
188 } else {
189 return Err(FixtureError::FixtureNameAlreadyInUse(name.clone()))
190 }
191
192 fixture.iter_over_properties().try_for_each(|(property, channel)| {
194 let mut fine_degree = 0;
195
196 for (channel_index, universe_index) in channel.get_channel_indices() {
197 let universe = self.dmx_config.get_mut(universe_index)
198 .ok_or(UniverseOutOfRange)?;
199
200 let ch_index = channel_index as usize;
201
202 match &universe[ch_index] {
203 Pending(existing) if *existing == name => {
204 universe[ch_index] = Reserved(existing.clone(), property.clone(), fine_degree);
205 fine_degree += 1;
206 }
207
208 _ => {
209 return Err(DmxStateDesync);
210 }
211 }
212 }
213
214 Ok(())
215 }).map_err(FixtureError::from)?;
216
217 Ok(())
218 }
219
220 fn move_fixture(
236 &mut self, name: String, new_channel: ChannelIndex, new_universe: usize
237 ) -> Result<(), FixtureError> {
238 let mut fixture_original = self.fixtures.get_mut(&name).ok_or(InvalidFixture(name.clone()))?
239 .clone();
240
241
242 let mut fixture_clone = fixture_original.clone();
243 fixture_clone.move_to_channel(new_channel, new_universe)?;
244
245 let max_universe = fixture_clone
246 .iter_over_properties()
247 .flat_map(|(_, channel)| channel.get_channel_indices())
248 .map(|(_, uni_idx)| uni_idx)
249 .max()
250 .unwrap_or(new_universe);
251
252 self.ensure_universe_size(max_universe + 1);
253
254 fixture_clone.iter_over_properties().try_for_each(|(_, channel)| {
256
257 for (channel_index, universe_index) in channel.get_channel_indices() {
258 let universe = self.dmx_config.get_mut(universe_index)
259 .ok_or(UniverseOutOfRange)?;
260
261 match &universe[channel_index as usize] {
262 Reserved(existing, _, _) if existing == &name => {}
263 Reserved(existing, property, _) => {
264 return Err(ChannelAlreadyInUse(format!("{}, {}", existing, property)));
265 }
266 _ => {
267 universe[channel_index as usize] = Pending(name.clone())
268 }
269 }
270 }
271
272 Ok(())
273 }).map_err(FixtureError::from)?;
274
275 self.remove_reservations(&mut fixture_original)?;
277
278 fixture_clone.iter_over_properties().try_for_each(|(property, channel)| {
280 let mut fine_degree = 0;
281
282 for (channel_index, universe_index) in channel.get_channel_indices() {
283 let universe = self.dmx_config.get_mut(universe_index)
284 .ok_or(UniverseOutOfRange)?;
285
286 let ch_index = channel_index as usize;
287
288 match &universe[ch_index] {
289 Pending(existing) if *existing == name => {
290 universe[ch_index] = Reserved(name.clone(), property.clone(), fine_degree);
291 fine_degree += 1;
292 }
293
294 Empty => {
295 universe[ch_index] = Reserved(name.clone(), property.clone(), fine_degree);
296 fine_degree += 1;
297 }
298
299 _ => {
300 return Err(DmxStateDesync)
301 }
302 }
303 }
304
305 Ok(())
306 }).map_err(FixtureError::from)?;
307
308 self.fixtures.insert(name, fixture_clone);
309
310 Ok(())
311 }
312
313 fn remove_fixture(&mut self, name: String) -> Result<(), FixtureError> {
325 let mut fixture = self.fixtures.remove(&name).ok_or(InvalidFixture(name.clone()))?;
326
327 if let Err(e) = self.remove_reservations(&mut fixture) {
328
329 self.fixtures.insert(name, fixture);
331 return Err(e);
332 }
333
334 Ok(())
335 }
336
337 fn set_property(&mut self, name: String, property: PropertyType, value: ChannelValue) -> Result<(), FixtureError> {
350
351 let fixture = self.fixtures.get_mut(&name).ok_or(InvalidFixture(name.clone()))?;
352
353 fixture.set(property, value)?;
354
355 Ok(())
356 }
357
358 fn get_fixture_type_from_string(&self, name: String) -> Result<String, FixtureError> {
368 match self.fixtures.get(&name) {
369 None => Err(InvalidFixture(name)),
370 Some(fixture) => Ok(fixture.get_fixture_type()),
371 }
372 }
373
374 fn ensure_universe_size(&mut self, size: usize) {
380 if size > self.dmx_config.len() {
381 self.dmx_config.resize_with(size, || std::array::from_fn(|_| Empty));
382 }
383 }
384
385 fn remove_reservations(&mut self, fixture: &mut Fixture) -> Result<(), FixtureError> {
396 let name = fixture.get_name();
397
398 fixture.iter_over_properties().try_for_each(|(_, channel)| {
399 for (channel_index, universe_index) in channel.get_channel_indices() {
400 let universe = self.dmx_config.get_mut(universe_index)
401 .ok_or(UniverseOutOfRange)?;
402
403 match &universe[channel_index as usize] {
404 Reserved(existing, _, _) if *existing == *name => {}
405 _ => {
406 return Err(DmxStateDesync);
407 }
408 }
409 universe[channel_index as usize] = Empty;
410 }
411
412 Ok(())
413 }).map_err(FixtureError::from)?;
414 Ok(())
415 }
416
417 fn get_dmx_config_for_client(&self) -> DMXConfigForClientState{
419 self.dmx_config.iter().map(|universe| {
420 universe.iter().map(|channel| {
421 match channel {
422 Reserved(fixture, property, fine_degree) => {
423 let fixture_type = match self.fixtures.get(&fixture.clone()) {
424 Some(fixture_object) => fixture_object.get_fixture_type(),
425 None => {
426 r_log!(Error,"Fixture {} is saved in DMXConfiguration, but not in FixtureList.",
427 fixture
428 );
429 return DMXConfigurationForClient::Empty;
430 }
431 };
432
433 let mut hasher = DefaultHasher::new();
434 fixture_type.hash(&mut hasher);
435 let full_hash: u64 = hasher.finish();
436 let fixture_type_hash = (full_hash % 256) as u8;
437
438 DMXConfigurationForClient::Reserved {
439 fixture_name: fixture.clone(),
440 property_type: property.clone(),
441 fine_degree: *fine_degree,
442 fixture_type_hash
443 }
444 }
445 _ => DMXConfigurationForClient::Empty,
446 }
447 }).collect()
448 }).collect()
449 }
450}
451
452pub fn new_fixture(
472 name: String, fixture_type_name: String, start_channel: ChannelIndex, start_universe: usize
473) -> Result<(), FixtureError> {
474 let (reply_tx, reply_rx) = mpsc::channel();
475
476 let cmd = FixtureCommand::SpawnFixture {
477 name,
478 fixture_type_name,
479 start_channel,
480 start_universe,
481 reply_to: reply_tx,
482 };
483
484 let engine_tx = FIXTURE_ACTION_SENDER.get().expect("CRITICAL: Engine not running!");
485 engine_tx.send(cmd).unwrap();
486
487 reply_rx.recv().unwrap()
488}
489
490pub fn move_fixture(name: String, new_channel: ChannelIndex, new_universe: usize) -> Result<(), FixtureError> {
506 let (reply_tx, reply_rx) = mpsc::channel();
507
508 let cmd = FixtureCommand::MoveFixture {
509 name,
510 new_channel,
511 new_universe,
512 reply_to: reply_tx,
513 };
514
515 let engine_tx = FIXTURE_ACTION_SENDER.get().expect("CRITICAL: Engine not running!");
516 engine_tx.send(cmd).unwrap();
517
518 reply_rx.recv().unwrap()
519}
520
521pub fn remove_fixture(name: String) -> Result<(), FixtureError> {
533 let (reply_tx, reply_rx) = mpsc::channel();
534
535 let cmd = FixtureCommand::RemoveFixture {
536 name,
537 reply_to: reply_tx,
538 };
539
540 let engine_tx = FIXTURE_ACTION_SENDER.get().expect("CRITICAL: Engine not running!");
541 engine_tx.send(cmd).unwrap();
542
543 reply_rx.recv().unwrap()
544}
545
546pub fn set_property(fixture_name: String, property: PropertyType, value: ChannelValue) -> Result<(), FixtureError> {
559 let (reply_tx, reply_rx) = mpsc::channel();
560
561 let cmd = FixtureCommand::SetProperty {
562 fixture_name,
563 property,
564 value,
565 reply_to: reply_tx
566 };
567
568 let engine_tx = FIXTURE_ACTION_SENDER.get().expect("CRITICAL: Engine not running!");
569 engine_tx.send(cmd).unwrap();
570
571 reply_rx.recv().unwrap()
572}
573
574pub fn get_fixture_type(fixture_name: String) -> Result<String, FixtureError> {
584 let (reply_tx, reply_rx) = mpsc::channel();
585
586 let cmd = FixtureCommand::GetType {
587 fixture_name,
588 reply_to: reply_tx
589 };
590
591 let engine_tx = FIXTURE_ACTION_SENDER.get().expect("CRITICAL: Engine not running!");
592 engine_tx.send(cmd).unwrap();
593
594 reply_rx.recv().unwrap()
595}