1pub mod addressing;
6pub mod color_orders;
7pub mod definition;
8pub mod fixture;
9pub mod geometry;
10pub mod layout;
11pub mod topology;
12
13use crate::asset::Asset;
14use crate::collection::Collections;
15use crate::naming::unique_name;
16use crate::patching::definition::FixtureDefinition;
17use crate::patching::fixture::{
18 ArtnetDMXAddress, CHANNELS_PER_UNIVERSE, Fixture, FixtureId, LayoutPosition, MAX_UNIVERSE,
19};
20use crate::patching::layout::{Layout, LayoutRef};
21use crate::patching::topology::Topology;
22use serde::{Deserialize, Serialize};
23use std::collections::{BTreeMap, BTreeSet};
24use std::sync::Arc;
25
26#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
28#[serde(default)]
29pub struct Patch {
30 pub topology: Topology,
31 pub layouts: Vec<Layout>,
32}
33
34impl Patch {
35 pub fn layout(&self, layout: LayoutRef) -> Option<&Layout> {
36 match layout {
37 LayoutRef::Topology => Some(&self.topology.default_layout),
38 LayoutRef::Layout(index) => self.layouts.get(index),
39 }
40 }
41
42 pub fn layout_mut(&mut self, layout: LayoutRef) -> Option<&mut Layout> {
43 match layout {
44 LayoutRef::Topology => Some(&mut self.topology.default_layout),
45 LayoutRef::Layout(index) => self.layouts.get_mut(index),
46 }
47 }
48
49 pub fn fixture(&self, id: FixtureId) -> Option<&Fixture> {
50 self.topology.fixtures.get(&id)
51 }
52
53 pub fn fixture_name(&self, id: FixtureId) -> String {
55 self.fixture(id)
56 .map(|fixture| fixture.name.clone())
57 .unwrap_or_else(|| id.to_string())
58 }
59
60 pub fn layout_refs(&self) -> Vec<LayoutRef> {
62 std::iter::once(LayoutRef::Topology)
63 .chain((0..self.layouts.len()).map(LayoutRef::Layout))
64 .collect()
65 }
66
67 pub fn add_layout(&mut self) -> usize {
68 let name = unique_name("Layout", self.layouts.iter().map(|l| l.name.as_str()));
69 self.layouts.push(Layout::new(name));
70 self.layouts.len() - 1
71 }
72
73 pub fn duplicate_layout(&mut self, index: usize) -> Option<usize> {
74 let mut copy = self.layouts.get(index)?.clone();
75 copy.name = unique_name(
76 &format!("{} copy", copy.name),
77 self.layouts.iter().map(|l| l.name.as_str()),
78 );
79 self.layouts.insert(index + 1, copy);
80 Some(index + 1)
81 }
82
83 pub fn remove_layout(&mut self, index: usize) {
84 if index < self.layouts.len() {
85 self.layouts.remove(index);
86 }
87 }
88
89 pub fn add_fixture(&mut self, fixture: Fixture, position: LayoutPosition) -> FixtureId {
91 self.topology.add_fixture(fixture, position)
92 }
93
94 pub fn remove_fixture(&mut self, id: FixtureId) {
95 self.topology.remove_fixture(id, &mut self.layouts);
96 }
97
98 pub fn add_member(&mut self, layout: LayoutRef, id: FixtureId) {
101 let Some(position) = self.topology.default_layout.members.get(&id).copied() else {
102 return;
103 };
104 if let Some(layout) = self.layout_mut(layout) {
105 layout.members.entry(id).or_insert(position);
106 }
107 }
108
109 pub fn set_member(&mut self, layout: LayoutRef, id: FixtureId, member: bool) {
112 if layout == LayoutRef::Topology {
113 return;
114 }
115 if member {
116 self.add_member(layout, id);
117 } else if let Some(layout) = self.layout_mut(layout) {
118 layout.forget(id);
119 }
120 }
121
122 pub fn definition(
125 &self,
126 id: FixtureId,
127 collections: &Collections,
128 ) -> Option<Arc<Asset<FixtureDefinition>>> {
129 let definition = self.fixture(id)?.definition?;
130 Asset::get(definition, collections)
131 }
132
133 pub fn addresses(&self, collections: &Collections) -> BTreeMap<FixtureId, ArtnetDMXAddress> {
144 let mut addresses = BTreeMap::new();
145 for head in self.topology.chain_heads() {
146 let Some(port) = self.topology.ports.get(&head) else {
147 continue;
148 };
149 let mut universe = port.universe;
150 let mut channel = port.channel.min(CHANNELS_PER_UNIVERSE);
151 for id in self.topology.chain_from(head) {
152 let Some(definition) = self.definition(id, collections) else {
153 break;
154 };
155 let width = definition.data.channel_count();
156 if width > CHANNELS_PER_UNIVERSE {
159 break;
160 }
161 if channel + width > CHANNELS_PER_UNIVERSE {
162 universe = universe.saturating_add(1);
163 channel = 0;
164 }
165 if universe > MAX_UNIVERSE {
166 break;
167 }
168 addresses.insert(id, ArtnetDMXAddress::from_pair((universe, channel)));
169 channel += width;
170 }
171 }
172 addresses
173 }
174
175 fn occupied_universes(
183 &self,
184 collections: &Collections,
185 placing: Option<FixtureId>,
186 ) -> BTreeSet<u16> {
187 let mine = |id: FixtureId| placing == Some(self.topology.chain_head_of(id));
188
189 let mut occupied: BTreeSet<u16> = self
190 .topology
191 .ports
192 .iter()
193 .filter(|(id, _)| !mine(**id))
194 .map(|(_, port)| port.universe)
195 .collect();
196 occupied.extend(
197 self.addresses(collections)
198 .iter()
199 .filter(|(id, _)| !mine(**id))
200 .map(|(_, address)| address.universe()),
201 );
202 occupied
203 }
204
205 pub fn lowest_free_universe(
209 &self,
210 collections: &Collections,
211 placing: Option<FixtureId>,
212 ) -> u16 {
213 let occupied = self.occupied_universes(collections, placing);
214 (0..)
215 .find(|universe| !occupied.contains(universe))
216 .unwrap_or(0)
217 }
218
219 pub fn fixture_mode(
223 &self,
224 layout: LayoutRef,
225 mode: usize,
226 id: FixtureId,
227 collections: &Collections,
228 ) -> Option<usize> {
229 let definition = self.definition(id, collections)?;
230 let definition = &definition.data;
231 if definition.modes.is_empty() {
232 return None;
233 }
234 let assigned = self
235 .layout(layout)
236 .and_then(|layout| layout.mode(mode).fixture_modes.get(&id).copied());
237 Some(match assigned {
238 Some(index) if index < definition.modes.len() => index,
239 _ => definition.default_mode(),
240 })
241 }
242
243 pub fn set_fixture_mode(
244 &mut self,
245 layout: LayoutRef,
246 mode: usize,
247 id: FixtureId,
248 value: usize,
249 ) {
250 if let Some(layout) = self.layout_mut(layout) {
251 layout.mode_mut(mode).fixture_modes.insert(id, value);
252 }
253 }
254}
255
256#[cfg(test)]
257mod tests;