#![allow(dead_code)]
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct CharacterProfileConfig {
pub max_params: usize,
}
#[derive(Debug, Clone)]
pub struct ProfileEntry {
pub name: String,
pub value: f32,
}
#[derive(Debug, Clone)]
pub struct CharacterProfile {
config: CharacterProfileConfig,
pub name: String,
params: HashMap<String, f32>,
}
#[allow(dead_code)]
pub fn default_character_profile_config() -> CharacterProfileConfig {
CharacterProfileConfig { max_params: 512 }
}
#[allow(dead_code)]
pub fn new_character_profile(name: &str, config: CharacterProfileConfig) -> CharacterProfile {
CharacterProfile {
config,
name: name.to_string(),
params: HashMap::new(),
}
}
#[allow(dead_code)]
pub fn profile_set_param(profile: &mut CharacterProfile, name: &str, value: f32) {
if profile.params.len() >= profile.config.max_params && !profile.params.contains_key(name) {
return;
}
profile.params.insert(name.to_string(), value);
}
#[allow(dead_code)]
pub fn profile_get_param(profile: &CharacterProfile, name: &str) -> Option<f32> {
profile.params.get(name).copied()
}
#[allow(dead_code)]
pub fn profile_param_count(profile: &CharacterProfile) -> usize {
profile.params.len()
}
#[allow(dead_code)]
pub fn profile_apply_to(source: &CharacterProfile, target: &mut CharacterProfile) {
for (k, &v) in &source.params {
if target.params.len() < target.config.max_params || target.params.contains_key(k) {
target.params.insert(k.clone(), v);
}
}
}
#[allow(dead_code)]
pub fn profile_diff(a: &CharacterProfile, b: &CharacterProfile) -> Vec<ProfileEntry> {
let mut diffs = Vec::new();
for (k, &va) in &a.params {
match b.params.get(k) {
Some(&vb) if (va - vb).abs() > f32::EPSILON => {
diffs.push(ProfileEntry { name: k.clone(), value: va - vb });
}
None => {
diffs.push(ProfileEntry { name: k.clone(), value: va });
}
_ => {}
}
}
for (k, &vb) in &b.params {
if !a.params.contains_key(k) {
diffs.push(ProfileEntry { name: k.clone(), value: -vb });
}
}
diffs
}
#[allow(dead_code)]
pub fn profile_to_json(profile: &CharacterProfile) -> String {
let mut entries: Vec<String> = profile
.params
.iter()
.map(|(k, v)| format!("\"{}\":{}", k, v))
.collect();
entries.sort();
format!(
"{{\"name\":\"{}\",\"param_count\":{},\"params\":{{{}}}}}",
profile.name,
profile.params.len(),
entries.join(",")
)
}
#[allow(dead_code)]
pub fn profile_clear(profile: &mut CharacterProfile) {
profile.params.clear();
}
#[allow(dead_code)]
pub fn profile_has_param(profile: &CharacterProfile, name: &str) -> bool {
profile.params.contains_key(name)
}
#[cfg(test)]
mod tests {
use super::*;
fn make_profile(name: &str) -> CharacterProfile {
new_character_profile(name, default_character_profile_config())
}
#[test]
fn test_set_and_get_param() {
let mut p = make_profile("char1");
profile_set_param(&mut p, "jaw_open", 0.5);
assert!((profile_get_param(&p, "jaw_open").expect("should succeed") - 0.5).abs() < 1e-6);
}
#[test]
fn test_get_missing_param() {
let p = make_profile("empty");
assert!(profile_get_param(&p, "nonexistent").is_none());
}
#[test]
fn test_param_count() {
let mut p = make_profile("c");
assert_eq!(profile_param_count(&p), 0);
profile_set_param(&mut p, "a", 1.0);
profile_set_param(&mut p, "b", 2.0);
assert_eq!(profile_param_count(&p), 2);
}
#[test]
fn test_has_param() {
let mut p = make_profile("c");
profile_set_param(&mut p, "smile", 0.8);
assert!(profile_has_param(&p, "smile"));
assert!(!profile_has_param(&p, "frown"));
}
#[test]
fn test_apply_to() {
let mut src = make_profile("src");
profile_set_param(&mut src, "smile", 1.0);
let mut dst = make_profile("dst");
profile_set_param(&mut dst, "smile", 0.0);
profile_apply_to(&src, &mut dst);
assert!((profile_get_param(&dst, "smile").expect("should succeed") - 1.0).abs() < 1e-6);
}
#[test]
fn test_diff_returns_differences() {
let mut a = make_profile("a");
let mut b = make_profile("b");
profile_set_param(&mut a, "x", 1.0);
profile_set_param(&mut b, "x", 0.5);
let diffs = profile_diff(&a, &b);
assert!(!diffs.is_empty());
}
#[test]
fn test_diff_identical_profiles_empty() {
let mut a = make_profile("a");
let mut b = make_profile("b");
profile_set_param(&mut a, "x", 1.0);
profile_set_param(&mut b, "x", 1.0);
let diffs = profile_diff(&a, &b);
assert!(diffs.is_empty());
}
#[test]
fn test_clear() {
let mut p = make_profile("c");
profile_set_param(&mut p, "a", 1.0);
profile_set_param(&mut p, "b", 2.0);
profile_clear(&mut p);
assert_eq!(profile_param_count(&p), 0);
}
#[test]
fn test_to_json_contains_name() {
let p = make_profile("hero");
let json = profile_to_json(&p);
assert!(json.contains("\"name\":\"hero\""));
}
#[test]
fn test_max_params_enforced() {
let cfg = CharacterProfileConfig { max_params: 2 };
let mut p = new_character_profile("limited", cfg);
profile_set_param(&mut p, "a", 1.0);
profile_set_param(&mut p, "b", 2.0);
profile_set_param(&mut p, "c", 3.0); assert_eq!(profile_param_count(&p), 2);
}
}