Skip to main content

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}