use std::sync::Arc;
use gled_model::{
asset::Asset,
collection::Collections,
patching::{Patch, definition::FixtureDefinition},
};
use crate::pipeline::extract_output::OutputConfig;
#[derive(Debug, Default)]
pub(crate) struct PatchLayout {
patch_generation: u64,
derived_from: Option<u64>,
definitions: Vec<Arc<Asset<FixtureDefinition>>>,
collections_generation: Option<u64>,
generation: u64,
}
impl PatchLayout {
pub(crate) fn update(
&mut self,
patch: Option<&Patch>,
collections: &Collections,
collections_generation: u64,
output_config: &mut OutputConfig,
) -> bool {
let definitions_changed = self.collections_generation != Some(collections_generation)
&& self.take_definitions(collections);
self.collections_generation = Some(collections_generation);
if !definitions_changed && self.derived_from == Some(self.patch_generation) {
return false;
}
self.derived_from = Some(self.patch_generation);
self.generation += 1;
output_config.universes = Arc::new(
patch
.map(|patch| {
patch
.addressing(collections)
.universes()
.iter()
.copied()
.collect()
})
.unwrap_or_default(),
);
output_config.color_orders = Arc::new(
patch
.map(|patch| patch.color_orders(collections))
.unwrap_or_default(),
);
true
}
fn take_definitions(&mut self, collections: &Collections) -> bool {
let definitions: Vec<_> = collections
.get::<FixtureDefinition>()
.map(|collection| collection.shared().cloned().collect())
.unwrap_or_default();
let changed = definitions.len() != self.definitions.len()
|| definitions
.iter()
.zip(&self.definitions)
.any(|(new, old)| !Arc::ptr_eq(new, old));
self.definitions = definitions;
changed
}
pub(crate) fn patch_replaced(&mut self) {
self.patch_generation += 1;
}
pub(crate) fn generation(&self) -> u64 {
self.generation
}
}
#[cfg(test)]
mod tests {
use super::*;
use gled_model::{
asset_id::AssetId,
collection::Collection,
patching::{
definition::{ColorOrder, EmittedColor, Pixel},
fixture::{Fixture, LayoutPosition},
},
};
fn collections_with_a_lamp() -> (Collections, AssetId<FixtureDefinition>) {
let definition = FixtureDefinition {
pixels: vec![Pixel {
name: "lamp".to_owned(),
model: 0,
position_m: [0.0; 3],
rotation: 0.0,
channels: vec![EmittedColor::Red, EmittedColor::Green, EmittedColor::Blue],
}],
..Default::default()
};
let asset = Asset {
id: AssetId::default(),
path: vec!["lamp".to_owned()],
data: definition,
};
let id = asset.id;
let mut collection = Collection::<FixtureDefinition>::default();
collection.set_asset(asset);
let mut collections = Collections::default();
collections.insert(collection);
(collections, id)
}
fn patch_with(definition: AssetId<FixtureDefinition>) -> Patch {
let mut patch = Patch::default();
let mut fixture = Fixture::new("lamp");
fixture.definition = Some(definition);
patch.add_fixture(fixture, LayoutPosition::default());
patch
}
#[test]
fn a_new_patch_sets_the_universes_once() {
let (collections, definition) = collections_with_a_lamp();
let patch = patch_with(definition);
let mut layout = PatchLayout::default();
let mut output = OutputConfig::default();
assert!(layout.update(Some(&patch), &collections, 1, &mut output));
assert_eq!(*output.universes, [0].into());
assert!(
!layout.update(Some(&patch), &collections, 1, &mut output),
"nothing changed"
);
}
#[test]
fn an_unrelated_asset_change_keeps_the_layout() {
let (collections, definition) = collections_with_a_lamp();
let patch = patch_with(definition);
let mut layout = PatchLayout::default();
let mut output = OutputConfig::default();
layout.update(Some(&patch), &collections, 1, &mut output);
assert!(!layout.update(Some(&patch), &collections, 2, &mut output));
}
#[test]
fn an_edited_definition_derives_the_layout_again() {
let (mut collections, definition) = collections_with_a_lamp();
let patch = patch_with(definition);
let mut layout = PatchLayout::default();
let mut output = OutputConfig::default();
layout.update(Some(&patch), &collections, 1, &mut output);
let mut edited = (**collections
.get::<FixtureDefinition>()
.and_then(|collection| collection.get(&definition))
.expect("the definition is there"))
.clone();
edited.data.size_m = [1.0; 3];
collections.get_mut::<FixtureDefinition>().set_asset(edited);
assert!(layout.update(Some(&patch), &collections, 2, &mut output));
}
#[test]
fn a_definition_edited_to_grb_reorders_its_lamp() {
let (mut collections, definition) = collections_with_a_lamp();
let patch = patch_with(definition);
let mut layout = PatchLayout::default();
let mut output = OutputConfig::default();
layout.update(Some(&patch), &collections, 1, &mut output);
assert!(output.color_orders.is_empty());
let mut edited = (**collections
.get::<FixtureDefinition>()
.and_then(|collection| collection.get(&definition))
.expect("the definition is there"))
.clone();
edited.data.pixels[0].set_color_order(ColorOrder::Grb);
collections.get_mut::<FixtureDefinition>().set_asset(edited);
layout.update(Some(&patch), &collections, 2, &mut output);
assert_eq!(output.color_orders.order(0, 0), ColorOrder::Grb);
}
#[test]
fn closing_the_project_clears_the_universes() {
let (collections, definition) = collections_with_a_lamp();
let patch = patch_with(definition);
let mut layout = PatchLayout::default();
let mut output = OutputConfig::default();
layout.update(Some(&patch), &collections, 1, &mut output);
layout.patch_replaced();
assert!(layout.update(None, &collections, 1, &mut output));
assert!(output.universes.is_empty());
}
#[test]
fn a_replaced_patch_is_derived_again() {
let (collections, definition) = collections_with_a_lamp();
let mut layout = PatchLayout::default();
let mut output = OutputConfig::default();
layout.update(Some(&Patch::default()), &collections, 1, &mut output);
assert!(output.universes.is_empty());
let generation = layout.generation();
layout.patch_replaced();
let patch = patch_with(definition);
assert!(layout.update(Some(&patch), &collections, 1, &mut output));
assert_eq!(*output.universes, [0].into());
assert_ne!(layout.generation(), generation, "interfaces notice it");
}
#[test]
fn only_a_replaced_patch_is_derived_again() {
let (collections, definition) = collections_with_a_lamp();
let mut layout = PatchLayout::default();
let mut output = OutputConfig::default();
layout.update(Some(&Patch::default()), &collections, 1, &mut output);
let patch = patch_with(definition);
assert!(!layout.update(Some(&patch), &collections, 1, &mut output));
assert!(output.universes.is_empty());
}
}