use glam::Vec2;
use crate::scatter::runner::{Placement, RunConfig, RunResult};
use crate::scatter::KindId;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ScatterEventKind {
RunStarted,
RunFinished,
LayerStarted,
LayerFinished,
PositionEvaluated,
PlacementMade,
OverlayGenerated,
Warning,
}
#[non_exhaustive]
#[derive(Debug, Clone)]
pub enum ScatterEvent {
RunStarted {
config: RunConfig,
layer_count: usize,
},
RunFinished {
result: RunResult,
},
LayerStarted {
index: usize,
id: String,
kinds: Vec<KindId>,
overlay_mask_size_px: Option<(u32, u32)>,
overlay_brush_radius_px: Option<i32>,
},
LayerFinished {
index: usize,
id: String,
result: RunResult,
overlay: Option<OverlaySummary>,
},
PositionEvaluated {
layer_index: usize,
layer_id: String,
position: Vec2,
evaluations: Vec<KindEvaluationLite>,
max_weight: f32,
},
PlacementMade {
layer_index: usize,
layer_id: String,
placement: Placement,
},
OverlayGenerated {
layer_index: usize,
layer_id: String,
summary: OverlaySummary,
},
Warning {
context: String,
message: String,
},
}
impl ScatterEvent {
pub fn kind(&self) -> ScatterEventKind {
match self {
ScatterEvent::RunStarted { .. } => ScatterEventKind::RunStarted,
ScatterEvent::RunFinished { .. } => ScatterEventKind::RunFinished,
ScatterEvent::LayerStarted { .. } => ScatterEventKind::LayerStarted,
ScatterEvent::LayerFinished { .. } => ScatterEventKind::LayerFinished,
ScatterEvent::PositionEvaluated { .. } => ScatterEventKind::PositionEvaluated,
ScatterEvent::PlacementMade { .. } => ScatterEventKind::PlacementMade,
ScatterEvent::OverlayGenerated { .. } => ScatterEventKind::OverlayGenerated,
ScatterEvent::Warning { .. } => ScatterEventKind::Warning,
}
}
}
#[derive(Debug, Clone)]
pub struct KindEvaluationLite {
pub kind_id: KindId,
pub allowed: bool,
pub weight: f32,
}
impl KindEvaluationLite {
pub fn new(kind_id: impl Into<KindId>, allowed: bool, weight: f32) -> Self {
Self {
kind_id: kind_id.into(),
allowed,
weight,
}
}
}
#[derive(Debug, Clone)]
pub struct OverlaySummary {
pub name: String,
pub size_px: (u32, u32),
}
impl OverlaySummary {
pub fn new(name: impl Into<String>, size_px: (u32, u32)) -> Self {
Self {
name: name.into(),
size_px,
}
}
}
pub trait EventSink {
fn send(&mut self, event: ScatterEvent);
fn wants(&self, _kind: ScatterEventKind) -> bool {
true
}
fn send_many<I>(&mut self, events: I)
where
Self: Sized,
I: IntoIterator<Item = ScatterEvent>,
{
for e in events {
self.send(e);
}
}
}
impl EventSink for () {
#[inline]
fn send(&mut self, _event: ScatterEvent) {}
fn wants(&self, _kind: ScatterEventKind) -> bool {
false
}
}
pub struct FnSink<F>
where
F: FnMut(ScatterEvent),
{
f: F,
}
impl<F> FnSink<F>
where
F: FnMut(ScatterEvent),
{
pub fn new(f: F) -> Self {
Self { f }
}
}
impl<F> EventSink for FnSink<F>
where
F: FnMut(ScatterEvent),
{
#[inline]
fn send(&mut self, event: ScatterEvent) {
(self.f)(event);
}
}
#[derive(Default)]
pub struct VecSink {
events: Vec<ScatterEvent>,
}
impl VecSink {
pub fn new() -> Self {
Self { events: Vec::new() }
}
pub fn with_capacity(cap: usize) -> Self {
Self {
events: Vec::with_capacity(cap),
}
}
pub fn into_inner(self) -> Vec<ScatterEvent> {
self.events
}
pub fn as_slice(&self) -> &[ScatterEvent] {
&self.events
}
pub fn clear(&mut self) {
self.events.clear();
}
pub fn len(&self) -> usize {
self.events.len()
}
pub fn is_empty(&self) -> bool {
self.events.is_empty()
}
}
impl EventSink for VecSink {
#[inline]
fn send(&mut self, event: ScatterEvent) {
self.events.push(event);
}
}
pub struct MultiSink<S: EventSink> {
pub(crate) sinks: Vec<S>,
}
impl<S: EventSink> MultiSink<S> {
pub fn new() -> Self {
Self { sinks: Vec::new() }
}
pub fn with_sinks(sinks: Vec<S>) -> Self {
Self { sinks }
}
pub fn push(&mut self, sink: S) {
self.sinks.push(sink);
}
pub fn is_empty(&self) -> bool {
self.sinks.is_empty()
}
pub fn len(&self) -> usize {
self.sinks.len()
}
}
impl<S: EventSink> Default for MultiSink<S> {
fn default() -> Self {
Self::new()
}
}
impl<S: EventSink> EventSink for MultiSink<S> {
fn send(&mut self, event: ScatterEvent) {
if self.sinks.is_empty() {
return;
}
let kind = event.kind();
let mut indices: Vec<usize> = self
.sinks
.iter()
.enumerate()
.filter(|(_, sink)| sink.wants(kind))
.map(|(idx, _)| idx)
.collect();
if indices.is_empty() {
return;
}
let last_idx = indices.pop().expect("indices not empty");
for idx in indices {
self.sinks[idx].send(event.clone());
}
self.sinks[last_idx].send(event);
}
fn wants(&self, kind: ScatterEventKind) -> bool {
self.sinks.iter().any(|sink| sink.wants(kind))
}
}
pub trait AsEventSink {
fn as_event_sink(&mut self) -> &mut dyn EventSink;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn kind_evaluation_lite_constructor_sets_fields() {
let eval = KindEvaluationLite::new("tree", true, 0.8);
assert_eq!(eval.kind_id, "tree");
assert!(eval.allowed);
assert_eq!(eval.weight, 0.8);
}
#[test]
fn overlay_summary_constructor_sets_fields() {
let summary = OverlaySummary::new("mask", (4, 4));
assert_eq!(summary.name, "mask");
assert_eq!(summary.size_px, (4, 4));
}
#[test]
fn vec_sink_collects_events() {
let mut sink = VecSink::with_capacity(2);
assert!(sink.is_empty());
sink.send(ScatterEvent::Warning {
context: "a".into(),
message: "m".into(),
});
sink.send(ScatterEvent::Warning {
context: "b".into(),
message: "n".into(),
});
assert_eq!(sink.len(), 2);
sink.clear();
assert!(sink.is_empty());
}
#[test]
fn multi_sink_fans_out_events() {
let sink_a = VecSink::new();
let sink_b = VecSink::new();
let mut multi = MultiSink::with_sinks(vec![sink_a, sink_b]);
let event = ScatterEvent::Warning {
context: "ctx".into(),
message: "msg".into(),
};
multi.send(event.clone());
assert_eq!(multi.sinks.len(), 2);
assert_eq!(multi.sinks[0].len(), 1);
assert_eq!(multi.sinks[1].len(), 1);
matches!(multi.sinks[0].as_slice()[0], ScatterEvent::Warning { .. })
.then_some(())
.expect("event captured");
}
#[test]
fn fn_sink_invokes_callback() {
let mut count = 0;
let mut sink = FnSink::new(|_event| {
count += 1;
});
sink.send(ScatterEvent::Warning {
context: "ctx".into(),
message: "msg".into(),
});
assert_eq!(count, 1);
}
}