pub mod addressing;
pub mod color_orders;
pub mod definition;
pub mod fixture;
pub mod geometry;
pub mod layout;
pub mod topology;
use crate::asset::Asset;
use crate::collection::Collections;
use crate::naming::unique_name;
use crate::patching::definition::FixtureDefinition;
use crate::patching::fixture::{
ArtnetDMXAddress, CHANNELS_PER_UNIVERSE, Fixture, FixtureId, LayoutPosition, MAX_UNIVERSE,
};
use crate::patching::layout::{Layout, LayoutRef};
use crate::patching::topology::Topology;
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet};
use std::sync::Arc;
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
#[serde(default)]
pub struct Patch {
pub topology: Topology,
pub layouts: Vec<Layout>,
}
impl Patch {
pub fn layout(&self, layout: LayoutRef) -> Option<&Layout> {
match layout {
LayoutRef::Topology => Some(&self.topology.default_layout),
LayoutRef::Layout(index) => self.layouts.get(index),
}
}
pub fn layout_mut(&mut self, layout: LayoutRef) -> Option<&mut Layout> {
match layout {
LayoutRef::Topology => Some(&mut self.topology.default_layout),
LayoutRef::Layout(index) => self.layouts.get_mut(index),
}
}
pub fn fixture(&self, id: FixtureId) -> Option<&Fixture> {
self.topology.fixtures.get(&id)
}
pub fn fixture_name(&self, id: FixtureId) -> String {
self.fixture(id)
.map(|fixture| fixture.name.clone())
.unwrap_or_else(|| id.to_string())
}
pub fn layout_refs(&self) -> Vec<LayoutRef> {
std::iter::once(LayoutRef::Topology)
.chain((0..self.layouts.len()).map(LayoutRef::Layout))
.collect()
}
pub fn add_layout(&mut self) -> usize {
let name = unique_name("Layout", self.layouts.iter().map(|l| l.name.as_str()));
self.layouts.push(Layout::new(name));
self.layouts.len() - 1
}
pub fn duplicate_layout(&mut self, index: usize) -> Option<usize> {
let mut copy = self.layouts.get(index)?.clone();
copy.name = unique_name(
&format!("{} copy", copy.name),
self.layouts.iter().map(|l| l.name.as_str()),
);
self.layouts.insert(index + 1, copy);
Some(index + 1)
}
pub fn remove_layout(&mut self, index: usize) {
if index < self.layouts.len() {
self.layouts.remove(index);
}
}
pub fn add_fixture(&mut self, fixture: Fixture, position: LayoutPosition) -> FixtureId {
self.topology.add_fixture(fixture, position)
}
pub fn remove_fixture(&mut self, id: FixtureId) {
self.topology.remove_fixture(id, &mut self.layouts);
}
pub fn add_member(&mut self, layout: LayoutRef, id: FixtureId) {
let Some(position) = self.topology.default_layout.members.get(&id).copied() else {
return;
};
if let Some(layout) = self.layout_mut(layout) {
layout.members.entry(id).or_insert(position);
}
}
pub fn set_member(&mut self, layout: LayoutRef, id: FixtureId, member: bool) {
if layout == LayoutRef::Topology {
return;
}
if member {
self.add_member(layout, id);
} else if let Some(layout) = self.layout_mut(layout) {
layout.forget(id);
}
}
pub fn definition(
&self,
id: FixtureId,
collections: &Collections,
) -> Option<Arc<Asset<FixtureDefinition>>> {
let definition = self.fixture(id)?.definition?;
Asset::get(definition, collections)
}
pub fn addresses(&self, collections: &Collections) -> BTreeMap<FixtureId, ArtnetDMXAddress> {
let mut addresses = BTreeMap::new();
for head in self.topology.chain_heads() {
let Some(port) = self.topology.ports.get(&head) else {
continue;
};
let mut universe = port.universe;
let mut channel = port.channel.min(CHANNELS_PER_UNIVERSE);
for id in self.topology.chain_from(head) {
let Some(definition) = self.definition(id, collections) else {
break;
};
let width = definition.data.channel_count();
if width > CHANNELS_PER_UNIVERSE {
break;
}
if channel + width > CHANNELS_PER_UNIVERSE {
universe = universe.saturating_add(1);
channel = 0;
}
if universe > MAX_UNIVERSE {
break;
}
addresses.insert(id, ArtnetDMXAddress::from_pair((universe, channel)));
channel += width;
}
}
addresses
}
fn occupied_universes(
&self,
collections: &Collections,
placing: Option<FixtureId>,
) -> BTreeSet<u16> {
let mine = |id: FixtureId| placing == Some(self.topology.chain_head_of(id));
let mut occupied: BTreeSet<u16> = self
.topology
.ports
.iter()
.filter(|(id, _)| !mine(**id))
.map(|(_, port)| port.universe)
.collect();
occupied.extend(
self.addresses(collections)
.iter()
.filter(|(id, _)| !mine(**id))
.map(|(_, address)| address.universe()),
);
occupied
}
pub fn lowest_free_universe(
&self,
collections: &Collections,
placing: Option<FixtureId>,
) -> u16 {
let occupied = self.occupied_universes(collections, placing);
(0..)
.find(|universe| !occupied.contains(universe))
.unwrap_or(0)
}
pub fn fixture_mode(
&self,
layout: LayoutRef,
mode: usize,
id: FixtureId,
collections: &Collections,
) -> Option<usize> {
let definition = self.definition(id, collections)?;
let definition = &definition.data;
if definition.modes.is_empty() {
return None;
}
let assigned = self
.layout(layout)
.and_then(|layout| layout.mode(mode).fixture_modes.get(&id).copied());
Some(match assigned {
Some(index) if index < definition.modes.len() => index,
_ => definition.default_mode(),
})
}
pub fn set_fixture_mode(
&mut self,
layout: LayoutRef,
mode: usize,
id: FixtureId,
value: usize,
) {
if let Some(layout) = self.layout_mut(layout) {
layout.mode_mut(mode).fixture_modes.insert(id, value);
}
}
}
#[cfg(test)]
mod tests;