common/fixture/fixture_type.rs
1use std::collections::{HashMap, HashSet};
2use serde::{Deserialize, Serialize};
3use crate::fixture::color::ColorType;
4use crate::fixture::{ChannelError, FixtureError, FIXTURE_TYPE_LIST};
5use crate::fixture::channel::{ChannelParameter, PropertyType, SimplePropertyType};
6
7/// A template defining the DMX-Channel layout for a type of lighting fixture.
8///
9/// Fixture types are registered globally and used to create [`crate::fixture::Fixture`] instances.
10/// See [`FixtureType::new`] for how properties are parsed and validated.
11#[derive(Debug,Serialize,Deserialize)]
12pub struct FixtureType {
13 pub(super) color: Option<ColorType>,
14 pub(super) properties: HashMap<SimplePropertyType, ChannelParameter>,
15 pub(super) name: String,
16}
17
18impl FixtureType {
19 /// Creates a new fixture type and registers it globally.
20 ///
21 /// Parses the given properties into color channels ([`ColorType`]) and
22 /// simple properties ([`SimplePropertyType`]). Channel numbers are validated
23 /// for duplicates and range before registration.
24 ///
25 /// # Usage
26 ///
27 /// Register a fixture type first with [`FixtureType::new`], then create
28 /// instances of it with [`crate::fixture::Fixture::new`].
29 ///
30 /// # Arguments
31 ///
32 /// * `name` - Unique name for this fixture type.
33 /// * `properties` - Map of property types to their corresponding [`ChannelParameter`] layout.
34 ///
35 /// # Errors
36 ///
37 /// * [`InvalidPropertyType`](FixtureError::InvalidPropertyType) – if a property name is not recognized.
38 /// * [`FixtureTypeNameAlreadyInUse`](FixtureError::FixtureTypeNameAlreadyInUse) – if the name is already
39 /// registered.
40 /// * [`ChannelAlreadyInUse`](ChannelError::ChannelAlreadyInUse) – if two properties share a channel.
41 /// * [`ChannelOutOfRange`](ChannelError::ChannelOutOfRange) – if a channel exceeds [`MAX_CHANNEL`].
42
43 pub fn new(
44 name: String,
45 properties: HashMap<PropertyType, ChannelParameter>,
46 ) -> Result<(), FixtureError> {
47 let mut color = ColorType::new();
48 let mut new_properties = HashMap::new();
49 let mut seen_channels = HashSet::new();
50
51 for (key, channels) in &properties {
52 for channel in channels.get_channel_indices() {
53 if !seen_channels.insert(channel) {
54 return Err(FixtureError::ChannelError(
55 ChannelError::ChannelAlreadyInUse(key.to_string()),
56 ));
57 }
58 }
59
60 match key {
61 PropertyType::Color(color_type) => {
62 color.checked_add_channel(color_type.clone(), channels.clone())?;
63 }
64 PropertyType::Simple(simple_property) => {
65 new_properties.insert(simple_property.clone(), channels.clone());
66 }
67 }
68 }
69
70 let color = if color.exists() { Some(color) } else { None };
71
72 let output = Self {
73 color,
74 properties: new_properties,
75 name: name.clone(),
76 };
77
78 let mut list = FIXTURE_TYPE_LIST.write().unwrap();
79 match list.entry(name.clone()) {
80 std::collections::hash_map::Entry::Occupied(_) => {
81 Err(FixtureError::FixtureTypeNameAlreadyInUse(name))
82 }
83 std::collections::hash_map::Entry::Vacant(entry) => {
84 entry.insert(output);
85 Ok(())
86 }
87 }
88 }
89}