use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use super::paint_info::PaintInformation;
use crate::gpu::params::{ParamDef, ParamValue};
pub struct StabilizeResult {
pub divergence_index: Option<usize>,
}
pub trait StabilizerAlgorithm: Send {
fn push(&mut self, point: PaintInformation) -> StabilizeResult;
fn stabilized(&self) -> &[PaintInformation];
fn len(&self) -> usize {
self.stabilized().len()
}
fn is_empty(&self) -> bool {
self.stabilized().is_empty()
}
fn max_divergence_window(&self) -> usize {
0
}
fn clear(&mut self);
}
pub struct PassThrough {
points: Vec<PaintInformation>,
}
impl Default for PassThrough {
fn default() -> Self {
Self::new()
}
}
impl PassThrough {
pub fn new() -> Self {
Self {
points: Vec::with_capacity(256),
}
}
}
impl StabilizerAlgorithm for PassThrough {
fn push(&mut self, point: PaintInformation) -> StabilizeResult {
self.points.push(point);
StabilizeResult {
divergence_index: None,
}
}
fn stabilized(&self) -> &[PaintInformation] {
&self.points
}
fn clear(&mut self) {
self.points.clear();
}
}
pub struct StabilizerRegistration {
pub type_id: &'static str,
pub display_name: &'static str,
pub params: &'static [ParamDef],
pub from_params: fn(&[ParamValue]) -> Box<dyn StabilizerAlgorithm>,
}
pub struct StabilizerRegistry {
entries: HashMap<&'static str, StabilizerRegistration>,
}
impl Default for StabilizerRegistry {
fn default() -> Self {
Self::new()
}
}
impl StabilizerRegistry {
pub fn new() -> Self {
let mut entries = HashMap::new();
for reg in super::stabilizers::registrations() {
entries.insert(reg.type_id, reg);
}
StabilizerRegistry { entries }
}
pub fn types(&self) -> Vec<(&'static str, &'static str, &'static [ParamDef])> {
let mut types: Vec<_> = self
.entries
.iter()
.map(|(&id, reg)| (id, reg.display_name, reg.params))
.collect();
types.sort_by_key(|(id, _, _)| *id);
types
}
pub fn param_defs(&self, type_id: &str) -> &'static [ParamDef] {
self.entries.get(type_id).map(|e| e.params).unwrap_or(&[])
}
pub fn create(
&self,
type_id: &str,
params: &[ParamValue],
) -> Option<Box<dyn StabilizerAlgorithm>> {
self.entries
.get(type_id)
.map(|reg| (reg.from_params)(params))
}
pub fn create_from_config(&self, config: &StabilizerConfig) -> Box<dyn StabilizerAlgorithm> {
if config.algorithm.is_empty() || config.algorithm == "none" {
return Box::new(PassThrough::new());
}
self.create(&config.algorithm, &config.params)
.unwrap_or_else(|| {
log::warn!(
"unknown stabilizer algorithm '{}', using pass-through",
config.algorithm
);
Box::new(PassThrough::new())
})
}
}
#[derive(Clone, Debug, Serialize, Deserialize, Default, PartialEq)]
pub struct StabilizerConfig {
#[serde(default)]
pub algorithm: String,
#[serde(default)]
pub params: Vec<ParamValue>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pass_through_identity() {
let mut stab = PassThrough::new();
for i in 0..5 {
let pt = PaintInformation {
pos: [i as f32 * 10.0, 0.0],
pressure: 0.5,
..Default::default()
};
let result = stab.push(pt);
assert!(result.divergence_index.is_none());
}
assert_eq!(stab.len(), 5);
assert!((stab.stabilized()[2].pos[0] - 20.0).abs() < 1e-6);
}
#[test]
fn pass_through_clear() {
let mut stab = PassThrough::new();
stab.push(PaintInformation::default());
assert_eq!(stab.len(), 1);
stab.clear();
assert_eq!(stab.len(), 0);
}
#[test]
fn stabilizer_config_default_is_pass_through() {
let config = StabilizerConfig::default();
assert!(config.algorithm.is_empty());
assert!(config.params.is_empty());
}
#[test]
fn stabilizer_config_serde_round_trip() {
let config = StabilizerConfig {
algorithm: "laplacian".into(),
params: vec![ParamValue::Float(0.6)],
};
let json = serde_json::to_string(&config).unwrap();
let loaded: StabilizerConfig = serde_json::from_str(&json).unwrap();
assert_eq!(loaded.algorithm, "laplacian");
assert_eq!(loaded.params.len(), 1);
}
#[test]
fn stabilizer_config_missing_fields_default() {
let json = "{}";
let config: StabilizerConfig = serde_json::from_str(json).unwrap();
assert!(config.algorithm.is_empty());
assert!(config.params.is_empty());
}
#[test]
fn registry_creates_from_config() {
let registry = StabilizerRegistry::new();
let config = StabilizerConfig::default();
let stab = registry.create_from_config(&config);
assert_eq!(stab.len(), 0);
let config = StabilizerConfig {
algorithm: "none".into(),
params: vec![],
};
let stab = registry.create_from_config(&config);
assert_eq!(stab.len(), 0);
let config = StabilizerConfig {
algorithm: "laplacian".into(),
params: vec![ParamValue::Float(0.5)],
};
let mut stab = registry.create_from_config(&config);
stab.push(PaintInformation::default());
assert_eq!(stab.len(), 1);
}
#[test]
fn registry_discovers_algorithms() {
let registry = StabilizerRegistry::new();
let types = registry.types();
assert!(
!types.is_empty(),
"registry should discover at least one algorithm"
);
assert!(types.iter().any(|(id, _, _)| *id == "laplacian"));
}
}