use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::collections::HashSet;
use std::hash::{Hash, Hasher};
use std::sync::atomic::{AtomicBool, Ordering};
use uuid::Uuid;
use super::keys::{A_IN, A_SPEED, A_SRC_LEN, A_TRIM_IN, A_TRIM_OUT};
pub type AttrFlags = u8;
pub const FLAG_DAG: AttrFlags = 1 << 0;
pub const FLAG_DISPLAY: AttrFlags = 1 << 1;
pub const FLAG_KEYABLE: AttrFlags = 1 << 2;
pub const FLAG_READONLY: AttrFlags = 1 << 3;
pub const FLAG_INTERNAL: AttrFlags = 1 << 4;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AttrType {
Bool,
Int,
Float,
String,
Uuid,
Vec3,
Vec4,
List,
Map,
Set,
Json,
}
#[derive(Debug, Clone)]
pub struct AttrDef {
pub name: &'static str,
pub attr_type: AttrType,
pub flags: AttrFlags,
pub ui_options: &'static [&'static str],
pub order: f32,
}
impl AttrDef {
pub const fn new(name: &'static str, attr_type: AttrType, flags: AttrFlags) -> Self {
Self { name, attr_type, flags, ui_options: &[], order: 99.0 }
}
pub const fn with_ui(
name: &'static str,
attr_type: AttrType,
flags: AttrFlags,
ui_options: &'static [&'static str],
) -> Self {
Self { name, attr_type, flags, ui_options, order: 99.0 }
}
pub const fn with_order(
name: &'static str,
attr_type: AttrType,
flags: AttrFlags,
order: f32,
) -> Self {
Self { name, attr_type, flags, ui_options: &[], order }
}
pub const fn with_ui_order(
name: &'static str,
attr_type: AttrType,
flags: AttrFlags,
ui_options: &'static [&'static str],
order: f32,
) -> Self {
Self { name, attr_type, flags, ui_options, order }
}
pub const fn is_dag(&self) -> bool {
self.flags & FLAG_DAG != 0
}
pub const fn is_display(&self) -> bool {
self.flags & FLAG_DISPLAY != 0
}
pub const fn is_keyable(&self) -> bool {
self.flags & FLAG_KEYABLE != 0
}
pub const fn is_readonly(&self) -> bool {
self.flags & FLAG_READONLY != 0
}
pub const fn is_internal(&self) -> bool {
self.flags & FLAG_INTERNAL != 0
}
}
#[derive(Debug, Clone)]
pub struct AttrSchema {
pub name: &'static str,
defs: Box<[AttrDef]>,
}
impl AttrSchema {
pub fn new(name: &'static str, defs: &[AttrDef]) -> Self {
Self { name, defs: defs.to_vec().into_boxed_slice() }
}
pub fn from_slices(name: &'static str, slices: &[&[AttrDef]]) -> Self {
let defs: Vec<AttrDef> = slices.iter()
.flat_map(|s| s.iter().cloned())
.collect();
Self { name, defs: defs.into_boxed_slice() }
}
pub fn get(&self, name: &str) -> Option<&AttrDef> {
self.defs.iter().find(|d| d.name == name)
}
pub fn is_dag(&self, name: &str) -> bool {
self.get(name).is_some_and(|d| d.is_dag())
}
pub fn is_display(&self, name: &str) -> bool {
self.get(name).is_some_and(|d| d.is_display())
}
pub fn dag_attrs(&self) -> impl Iterator<Item = &AttrDef> {
self.defs.iter().filter(|d| d.is_dag())
}
pub fn display_attrs(&self) -> impl Iterator<Item = &AttrDef> {
self.defs.iter().filter(|d| d.is_display())
}
pub fn iter(&self) -> impl Iterator<Item = &AttrDef> {
self.defs.iter()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AttrValue {
Bool(bool),
Str(String),
Int8(i8),
Int(i32),
UInt(u32),
Float(f32),
Vec3([f32; 3]),
Vec4([f32; 4]),
Mat3([[f32; 3]; 3]),
Mat4([[f32; 4]; 4]),
Uuid(Uuid),
List(Vec<AttrValue>),
Map(HashMap<String, AttrValue>),
Set(HashSet<AttrValue>),
Json(String),
}
impl std::hash::Hash for AttrValue {
fn hash<H: Hasher>(&self, state: &mut H) {
use AttrValue::*;
use std::collections::hash_map::DefaultHasher;
std::mem::discriminant(self).hash(state);
match self {
Bool(v) => v.hash(state),
Str(v) => v.hash(state),
Int8(v) => v.hash(state),
Int(v) => v.hash(state),
UInt(v) => v.hash(state),
Float(v) => v.to_bits().hash(state),
Vec3(arr) => arr.iter().for_each(|f| f.to_bits().hash(state)),
Vec4(arr) => arr.iter().for_each(|f| f.to_bits().hash(state)),
Mat3(m) => m.iter().flat_map(|r| r.iter()).for_each(|f| f.to_bits().hash(state)),
Mat4(m) => m.iter().flat_map(|r| r.iter()).for_each(|f| f.to_bits().hash(state)),
Uuid(v) => v.hash(state),
List(v) => v.hash(state),
Map(v) => {
let mut acc: u64 = 0;
for (k, val) in v {
let mut h = DefaultHasher::new();
k.hash(&mut h);
val.hash(&mut h);
acc ^= h.finish();
}
acc.hash(state);
}
Set(v) => {
let mut acc: u64 = 0;
for val in v {
let mut h = DefaultHasher::new();
val.hash(&mut h);
acc ^= h.finish();
}
acc.hash(state);
}
Json(v) => v.hash(state),
}
}
}
fn f32_bits_eq(a: f32, b: f32) -> bool {
a.to_bits() == b.to_bits()
}
fn f32_slice_bits_eq(a: &[f32], b: &[f32]) -> bool {
if a.len() != b.len() {
return false;
}
a.iter().zip(b.iter()).all(|(x, y)| f32_bits_eq(*x, *y))
}
impl PartialEq for AttrValue {
fn eq(&self, other: &Self) -> bool {
use AttrValue::*;
match (self, other) {
(Bool(a), Bool(b)) => a == b,
(Str(a), Str(b)) => a == b,
(Int8(a), Int8(b)) => a == b,
(Int(a), Int(b)) => a == b,
(UInt(a), UInt(b)) => a == b,
(Float(a), Float(b)) => f32_bits_eq(*a, *b),
(Vec3(a), Vec3(b)) => f32_slice_bits_eq(a, b),
(Vec4(a), Vec4(b)) => f32_slice_bits_eq(a, b),
(Mat3(a), Mat3(b)) => a.iter().zip(b.iter()).all(|(ra, rb)| f32_slice_bits_eq(ra, rb)),
(Mat4(a), Mat4(b)) => a.iter().zip(b.iter()).all(|(ra, rb)| f32_slice_bits_eq(ra, rb)),
(Uuid(a), Uuid(b)) => a == b,
(List(a), List(b)) => a == b,
(Map(a), Map(b)) => {
if a.len() != b.len() {
return false;
}
a.iter().all(|(k, v)| b.get(k).is_some_and(|ov| ov == v))
}
(Set(a), Set(b)) => a == b,
(Json(a), Json(b)) => a == b,
_ => false,
}
}
}
impl Eq for AttrValue {}
#[derive(Debug, Serialize, Deserialize)]
pub struct Attrs {
#[serde(default)]
map: HashMap<String, AttrValue>,
#[serde(skip)]
#[serde(default = "Attrs::default_dirty")]
dirty: AtomicBool,
#[serde(skip)]
schema: Option<&'static AttrSchema>,
}
impl Default for Attrs {
fn default() -> Self {
Self::new()
}
}
impl Attrs {
fn default_dirty() -> AtomicBool {
AtomicBool::new(false)
}
pub fn new() -> Self {
Self {
map: HashMap::new(),
dirty: AtomicBool::new(false),
schema: None,
}
}
pub fn with_schema(schema: &'static AttrSchema) -> Self {
Self {
map: HashMap::new(),
dirty: AtomicBool::new(false),
schema: Some(schema),
}
}
pub fn attach_schema(&mut self, schema: &'static AttrSchema) {
self.schema = Some(schema);
}
pub fn schema(&self) -> Option<&'static AttrSchema> {
self.schema
}
pub fn set(&mut self, key: impl Into<String>, value: AttrValue) {
let key = key.into();
let changed = match self.map.get(&key) {
Some(existing) => existing != &value,
None => true, };
self.map.insert(key.clone(), value);
if changed {
let is_dag = match &self.schema {
Some(schema) => schema.is_dag(&key),
None => true, };
if is_dag {
self.dirty.store(true, Ordering::Relaxed);
}
}
}
pub fn get(&self, key: &str) -> Option<&AttrValue> {
self.map.get(key)
}
pub fn get_str(&self, key: &str) -> Option<&str> {
match self.map.get(key) {
Some(AttrValue::Str(s)) => Some(s),
_ => None,
}
}
pub fn get_i32(&self, key: &str) -> Option<i32> {
match self.map.get(key) {
Some(AttrValue::Int(v)) => Some(*v),
_ => None,
}
}
pub fn get_u32(&self, key: &str) -> Option<u32> {
match self.map.get(key) {
Some(AttrValue::UInt(v)) => Some(*v),
_ => None,
}
}
pub fn get_float(&self, key: &str) -> Option<f32> {
match self.map.get(key) {
Some(AttrValue::Float(v)) => Some(*v),
_ => None,
}
}
pub fn get_bool(&self, key: &str) -> Option<bool> {
match self.map.get(key) {
Some(AttrValue::Bool(v)) => Some(*v),
_ => None,
}
}
pub fn get_i8(&self, key: &str) -> Option<i8> {
match self.map.get(key) {
Some(AttrValue::Int8(v)) => Some(*v),
_ => None,
}
}
pub fn set_i8(&mut self, key: impl Into<String>, value: i8) {
self.set(key, AttrValue::Int8(value));
}
pub fn get_uuid(&self, key: &str) -> Option<Uuid> {
match self.map.get(key) {
Some(AttrValue::Uuid(v)) => Some(*v),
_ => None,
}
}
pub fn set_uuid(&mut self, key: impl Into<String>, value: Uuid) {
self.set(key, AttrValue::Uuid(value));
}
pub fn get_list(&self, key: &str) -> Option<&Vec<AttrValue>> {
match self.map.get(key) {
Some(AttrValue::List(v)) => Some(v),
_ => None,
}
}
pub fn get_list_mut(&mut self, key: &str) -> Option<&mut Vec<AttrValue>> {
match self.map.get_mut(key) {
Some(AttrValue::List(v)) => Some(v),
_ => None,
}
}
pub fn get_map(&self, key: &str) -> Option<&HashMap<String, AttrValue>> {
match self.map.get(key) {
Some(AttrValue::Map(v)) => Some(v),
_ => None,
}
}
pub fn get_map_mut(&mut self, key: &str) -> Option<&mut HashMap<String, AttrValue>> {
match self.map.get_mut(key) {
Some(AttrValue::Map(v)) => Some(v),
_ => None,
}
}
pub fn set_map(&mut self, key: impl Into<String>, value: HashMap<String, AttrValue>) {
self.set(key, AttrValue::Map(value));
}
pub fn get_set(&self, key: &str) -> Option<&HashSet<AttrValue>> {
match self.map.get(key) {
Some(AttrValue::Set(v)) => Some(v),
_ => None,
}
}
pub fn get_set_mut(&mut self, key: &str) -> Option<&mut HashSet<AttrValue>> {
match self.map.get_mut(key) {
Some(AttrValue::Set(v)) => Some(v),
_ => None,
}
}
pub fn set_set(&mut self, key: impl Into<String>, value: HashSet<AttrValue>) {
self.set(key, AttrValue::Set(value));
}
pub fn get_uuid_list(&self, key: &str) -> Option<Vec<Uuid>> {
let list = self.get_list(key)?;
let mut out = Vec::with_capacity(list.len());
for v in list {
match v {
AttrValue::Uuid(id) => out.push(*id),
_ => return None,
}
}
Some(out)
}
pub fn set_uuid_list(&mut self, key: impl Into<String>, values: &[Uuid]) {
let list = values.iter().copied().map(AttrValue::Uuid).collect();
self.set(key, AttrValue::List(list));
}
pub fn set_list(&mut self, key: impl Into<String>, value: Vec<AttrValue>) {
self.set(key, AttrValue::List(value));
}
pub fn get_vec3(&self, key: &str) -> Option<[f32; 3]> {
match self.map.get(key) {
Some(AttrValue::Vec3(v)) => Some(*v),
_ => None,
}
}
pub fn set_vec3(&mut self, key: impl Into<String>, value: [f32; 3]) {
self.set(key, AttrValue::Vec3(value));
}
pub fn get_vec4(&self, key: &str) -> Option<[f32; 4]> {
match self.map.get(key) {
Some(AttrValue::Vec4(v)) => Some(*v),
_ => None,
}
}
pub fn set_vec4(&mut self, key: impl Into<String>, value: [f32; 4]) {
self.set(key, AttrValue::Vec4(value));
}
pub fn get_mat3(&self, key: &str) -> Option<[[f32; 3]; 3]> {
match self.map.get(key) {
Some(AttrValue::Mat3(v)) => Some(*v),
_ => None,
}
}
pub fn set_mat3(&mut self, key: impl Into<String>, value: [[f32; 3]; 3]) {
self.set(key, AttrValue::Mat3(value));
}
pub fn get_mat4(&self, key: &str) -> Option<[[f32; 4]; 4]> {
match self.map.get(key) {
Some(AttrValue::Mat4(v)) => Some(*v),
_ => None,
}
}
pub fn set_mat4(&mut self, key: impl Into<String>, value: [[f32; 4]; 4]) {
self.set(key, AttrValue::Mat4(value));
}
pub fn get_i32_or_zero(&self, key: &str) -> i32 {
self.get_i32(key).unwrap_or(0)
}
pub fn get_i32_or(&self, key: &str, default: i32) -> i32 {
self.get_i32(key).unwrap_or(default)
}
pub fn get_float_or(&self, key: &str, default: f32) -> f32 {
self.get_float(key).unwrap_or(default)
}
pub fn get_bool_or(&self, key: &str, default: bool) -> bool {
self.get_bool(key).unwrap_or(default)
}
pub fn layer_start(&self) -> i32 {
let in_val = self.get_i32_or_zero(A_IN);
let trim_in = self.get_i32_or_zero(A_TRIM_IN);
let speed = self.get_float_or(A_SPEED, 1.0).clamp(0.1, 4.0);
let offset = (trim_in as f64 / speed as f64).round().clamp(i32::MIN as f64, i32::MAX as f64) as i32;
in_val.saturating_add(offset)
}
pub fn layer_end(&self) -> i32 {
let layer_start = self.layer_start();
let src_len = self.get_i32_or_zero(A_SRC_LEN);
let trim_in = self.get_i32_or_zero(A_TRIM_IN);
let trim_out = self.get_i32_or_zero(A_TRIM_OUT);
let speed = self.get_float_or(A_SPEED, 1.0).clamp(0.1, 4.0);
let visible_src = (src_len - trim_in - trim_out).max(1);
let visible_timeline = (visible_src as f64 / speed as f64).round().clamp(1.0, i32::MAX as f64) as i32;
layer_start.saturating_add(visible_timeline).saturating_sub(1)
}
pub fn src_len(&self) -> i32 {
self.get_i32_or_zero(A_SRC_LEN)
}
pub fn full_bar_end(&self) -> i32 {
let in_val = self.get_i32_or_zero(A_IN);
let src_len = self.get_i32_or_zero(A_SRC_LEN);
let speed = self.get_float_or(A_SPEED, 1.0).clamp(0.1, 4.0);
let duration = (src_len as f64 / speed as f64).ceil().clamp(1.0, i32::MAX as f64) as i32;
in_val.saturating_add(duration).saturating_sub(1)
}
pub fn full_bar_start(&self) -> i32 {
self.get_i32_or_zero(A_IN)
}
pub fn get_mut(&mut self, key: &str) -> Option<&mut AttrValue> {
self.map.get_mut(key)
}
pub fn remove(&mut self, key: &str) -> Option<AttrValue> {
self.map.remove(key)
}
pub fn iter(&self) -> impl Iterator<Item = (&String, &AttrValue)> {
self.map.iter()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&String, &mut AttrValue)> {
self.map.iter_mut()
}
pub fn contains(&self, key: &str) -> bool {
self.map.contains_key(key)
}
pub fn len(&self) -> usize {
self.map.len()
}
pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
pub fn hash_filtered(&self, include: Option<&[&str]>, exclude: Option<&[&str]>) -> u64 {
let include_set: Option<HashSet<&str>> = include.map(|v| v.iter().copied().collect());
let exclude_set: Option<HashSet<&str>> = exclude.map(|v| v.iter().copied().collect());
let mut keys: Vec<&String> = self.map.keys().collect();
keys.sort_unstable();
let mut hasher = std::collections::hash_map::DefaultHasher::new();
for key in keys {
if let Some(ref inc) = include_set
&& !inc.contains(key.as_str()) {
continue;
}
if let Some(ref exc) = exclude_set
&& exc.contains(key.as_str()) {
continue;
}
key.hash(&mut hasher);
if let Some(val) = self.map.get(key) {
val.hash(&mut hasher);
}
}
hasher.finish()
}
pub fn hash_all(&self) -> u64 {
self.hash_filtered(None, None)
}
pub fn is_dirty(&self) -> bool {
self.dirty.load(Ordering::Relaxed)
}
pub fn clear_dirty(&self) {
self.dirty.store(false, Ordering::Relaxed);
}
pub fn mark_dirty(&self) {
self.dirty.store(true, Ordering::Relaxed);
}
pub fn get_json<T: serde::de::DeserializeOwned>(&self, key: &str) -> Option<T> {
match self.map.get(key) {
Some(AttrValue::Json(s)) => serde_json::from_str(s).ok(),
_ => None,
}
}
pub fn set_json<T: serde::Serialize>(&mut self, key: impl Into<String>, value: &T) {
if let Ok(json) = serde_json::to_string(value) {
self.set(key, AttrValue::Json(json));
}
}
pub fn get_json_str(&self, key: &str) -> Option<&str> {
match self.map.get(key) {
Some(AttrValue::Json(s)) => Some(s),
_ => None,
}
}
}
impl Clone for Attrs {
fn clone(&self) -> Self {
Self {
map: self.map.clone(),
dirty: AtomicBool::new(self.dirty.load(Ordering::Relaxed)),
schema: self.schema,
}
}
}