From bfcd58b191c0375577073596bd14c1bc37dd0123 Mon Sep 17 00:00:00 2001 From: SamuelOsborne Date: Mon, 23 Sep 2024 06:23:53 +0200 Subject: [PATCH] refctor: added state machine parsing and creation (#232) --- dotlottie-rs/src/dotlottie_player.rs | 44 +- dotlottie-rs/src/state_machine/actions/mod.rs | 227 +++ dotlottie-rs/src/state_machine/lib.rs | 2 + .../src/state_machine/listeners/mod.rs | 612 +----- dotlottie-rs/src/state_machine/mod.rs | 1797 +++++++++-------- dotlottie-rs/src/state_machine/parser/mod.rs | 23 +- dotlottie-rs/src/state_machine/states/mod.rs | 116 +- .../src/state_machine/transitions/guard.rs | 20 +- .../src/state_machine/transitions/mod.rs | 42 +- 9 files changed, 1266 insertions(+), 1617 deletions(-) create mode 100644 dotlottie-rs/src/state_machine/actions/mod.rs diff --git a/dotlottie-rs/src/dotlottie_player.rs b/dotlottie-rs/src/dotlottie_player.rs index 0e22a1a2..ec4b4216 100644 --- a/dotlottie-rs/src/dotlottie_player.rs +++ b/dotlottie-rs/src/dotlottie_player.rs @@ -1357,17 +1357,25 @@ impl DotLottiePlayer { let listeners = sm.get_listeners(); for listener in listeners { - match listener.try_read() { - Ok(listener) => { - let unwrapped_listener = &*listener; - - if !listener_types - .contains(&unwrapped_listener.get_type().to_string()) - { - listener_types.push(unwrapped_listener.get_type().to_string()); - } + match listener { + crate::listeners::Listener::PointerUp { .. } => { + listener_types.push("PointerUp".to_string()) + } + crate::listeners::Listener::PointerDown { .. } => { + listener_types.push("PointerDown".to_string()) + } + crate::listeners::Listener::PointerEnter { .. } => { + listener_types.push("PointerEnter".to_string()) + } + crate::listeners::Listener::PointerMove { .. } => { + listener_types.push("PointerMove".to_string()) + } + crate::listeners::Listener::PointerExit { .. } => { + listener_types.push("PointerExit".to_string()) + } + crate::listeners::Listener::OnComplete { .. } => { + listener_types.push("OnComplete".to_string()) } - Err(_) => return vec![], } } listener_types @@ -1379,7 +1387,7 @@ impl DotLottiePlayer { } } - pub fn set_state_machine_numeric_context(&self, key: &str, value: f32) -> bool { + pub fn set_numeric_trigger(&self, key: &str, value: f32) -> bool { match self.state_machine.try_read() { Ok(state_machine) => { if state_machine.is_none() { @@ -1394,7 +1402,7 @@ impl DotLottiePlayer { match sm_write { Ok(mut state_machine) => { if let Some(sm) = state_machine.as_mut() { - sm.set_numeric_context(key, value); + sm.set_numeric_trigger(key, value); } } Err(_) => return false, @@ -1403,7 +1411,7 @@ impl DotLottiePlayer { true } - pub fn set_state_machine_string_context(&self, key: &str, value: &str) -> bool { + pub fn set_string_trigger(&self, key: &str, value: &str) -> bool { match self.state_machine.try_read() { Ok(state_machine) => { if state_machine.is_none() { @@ -1418,7 +1426,7 @@ impl DotLottiePlayer { match sm_write { Ok(mut state_machine) => { if let Some(sm) = state_machine.as_mut() { - sm.set_string_context(key, value); + sm.set_string_trigger(key, value); } } Err(_) => return false, @@ -1427,7 +1435,7 @@ impl DotLottiePlayer { true } - pub fn set_state_machine_boolean_context(&self, key: &str, value: bool) -> bool { + pub fn set_bool_trigger(&self, key: &str, value: bool) -> bool { match self.state_machine.try_read() { Ok(state_machine) => { if state_machine.is_none() { @@ -1442,7 +1450,7 @@ impl DotLottiePlayer { match sm_write { Ok(mut state_machine) => { if let Some(sm) = state_machine.as_mut() { - sm.set_bool_context(key, value); + sm.set_bool_trigger(key, value); } } Err(_) => return false, @@ -1655,7 +1663,7 @@ impl DotLottiePlayer { } #[cfg(not(target_arch = "wasm32"))] - pub fn state_machine_subscribe(&self, observer: Arc) -> bool { + pub fn state_machine_subscribe(&self, observer: Rc) -> bool { let mut sm = self.state_machine.write().unwrap(); if sm.is_none() { @@ -1667,7 +1675,7 @@ impl DotLottiePlayer { } #[cfg(not(target_arch = "wasm32"))] - pub fn state_machine_unsubscribe(&self, observer: Arc) -> bool { + pub fn state_machine_unsubscribe(&self, observer: Rc) -> bool { let mut sm = self.state_machine.write().unwrap(); if sm.is_none() { diff --git a/dotlottie-rs/src/state_machine/actions/mod.rs b/dotlottie-rs/src/state_machine/actions/mod.rs new file mode 100644 index 00000000..140ff8e1 --- /dev/null +++ b/dotlottie-rs/src/state_machine/actions/mod.rs @@ -0,0 +1,227 @@ +use thiserror::Error; + +use std::{collections::HashMap, rc::Rc, sync::RwLock}; + +use crate::DotLottiePlayerContainer; + +#[derive(Error, Debug)] +pub enum StateMachineActionError { + #[error("Error executing action: {0}")] + ExecuteError(String), +} + +pub trait ActionTrait { + fn execute( + &self, + player: &Rc>, + string_trigger: &mut HashMap, + bool_trigger: &mut HashMap, + numeric_trigger: &mut HashMap, + event_trigger: &HashMap, + ) -> Result<(), StateMachineActionError>; +} + +#[derive(Clone, Debug)] +pub enum Action { + Increment { + trigger_name: String, + value: Option, + }, + Decrement { + trigger_name: String, + value: Option, + }, + Toggle { + trigger_name: String, + }, + SetBoolean { + trigger_name: String, + value: bool, + }, + SetNumeric { + trigger_name: String, + value: f32, + }, + SetString { + trigger_name: String, + value: String, + }, + Fire { + trigger_name: String, + }, + Reset { + trigger_name: String, + }, + SetExpression { + layer_name: String, + property_index: u32, + var_name: String, + value: f32, + }, + SetTheme { + theme_id: String, + }, + SetFrame { + value: f32, + }, + SetSlot { + value: String, + }, + OpenUrl { + url: String, + }, + FireCustomEvent { + value: String, + }, +} + +impl ActionTrait for Action { + fn execute( + &self, + player: &Rc>, + string_trigger: &mut HashMap, + bool_trigger: &mut HashMap, + numeric_trigger: &mut HashMap, + event_trigger: &HashMap, + ) -> Result<(), StateMachineActionError> { + match self { + Action::Increment { + trigger_name, + value, + } => { + println!("Incrementing trigger {} by {:?}", trigger_name, value); + + if let Some(value) = value { + numeric_trigger.insert( + trigger_name.to_string(), + numeric_trigger.get(trigger_name).unwrap_or(&0.0) + value, + ); + } else { + numeric_trigger.insert( + trigger_name.to_string(), + numeric_trigger.get(trigger_name).unwrap_or(&0.0) + 1.0, + ); + } + Ok(()) + } + Action::Decrement { + trigger_name, + value, + } => { + println!("Decrementing trigger {} by {:?}", trigger_name, value); + + if let Some(value) = value { + numeric_trigger.insert( + trigger_name.to_string(), + numeric_trigger.get(trigger_name).unwrap_or(&0.0) - value, + ); + } else { + numeric_trigger.insert( + trigger_name.to_string(), + numeric_trigger.get(trigger_name).unwrap_or(&0.0) - 1.0, + ); + } + Ok(()) + } + Action::Toggle { trigger_name } => { + println!("Toggling trigger {}", trigger_name); + bool_trigger.insert( + trigger_name.to_string(), + !bool_trigger.get(trigger_name).unwrap_or(&false), + ); + + Ok(()) + } + Action::SetBoolean { + trigger_name, + value, + } => { + println!("Setting trigger {} to {}", trigger_name, value); + bool_trigger.insert(trigger_name.to_string(), *value); + Ok(()) + } + Action::SetNumeric { + trigger_name, + value, + } => { + println!("Setting trigger {} to {}", trigger_name, value); + numeric_trigger.insert(trigger_name.to_string(), *value); + Ok(()) + } + Action::SetString { + trigger_name, + value, + } => { + println!("Setting trigger {} to {}", trigger_name, value); + string_trigger.insert(trigger_name.to_string(), value.to_string()); + + Ok(()) + } + Action::Fire { trigger_name } => { + println!("Firing trigger {}", trigger_name); + Ok(()) + } + Action::Reset { trigger_name } => { + println!("Resetting trigger {}", trigger_name); + Ok(()) + } + Action::SetExpression { + layer_name, + property_index, + var_name, + value, + } => { + println!( + "Setting expression {} on layer {} property {} to {}", + var_name, layer_name, property_index, value + ); + Ok(()) + } + Action::SetTheme { theme_id } => { + println!("Setting theme to {}", theme_id); + Ok(()) + } + Action::SetSlot { value } => { + println!("Setting slot to {}", value); + let read_lock = player.read(); + + match read_lock { + Ok(player) => { + player.load_theme_data(&value); + } + Err(_) => { + return Err(StateMachineActionError::ExecuteError( + "Error getting read lock on player".to_string(), + )); + } + } + + Ok(()) + } + Action::OpenUrl { url } => { + println!("Opening URL {}", url); + Ok(()) + } + Action::FireCustomEvent { value } => { + println!("Firing custom event {}", value); + + Ok(()) + } + Action::SetFrame { value } => { + let read_lock = player.read(); + + match read_lock { + Ok(player) => { + player.set_frame(*value); + Ok(()) + } + Err(_) => { + return Err(StateMachineActionError::ExecuteError( + "Error getting read lock on player".to_string(), + )); + } + } + } + } + } +} diff --git a/dotlottie-rs/src/state_machine/lib.rs b/dotlottie-rs/src/state_machine/lib.rs index fc6a81c0..67767a10 100644 --- a/dotlottie-rs/src/state_machine/lib.rs +++ b/dotlottie-rs/src/state_machine/lib.rs @@ -1,3 +1,4 @@ +pub mod actions; pub mod errors; pub mod events; pub mod listeners; @@ -6,6 +7,7 @@ pub mod state_machine; pub mod states; pub mod transitions; +pub use crate::actions::*; pub use crate::errors::*; pub use crate::events::*; pub use crate::listeners::*; diff --git a/dotlottie-rs/src/state_machine/listeners/mod.rs b/dotlottie-rs/src/state_machine/listeners/mod.rs index b26ff4ea..5b9234ec 100644 --- a/dotlottie-rs/src/state_machine/listeners/mod.rs +++ b/dotlottie-rs/src/state_machine/listeners/mod.rs @@ -1,40 +1,10 @@ -use std::fmt::{Debug, Display}; +use std::fmt::Display; -use crate::parser::StringNumberBool; +use super::actions::Action; pub trait ListenerTrait { - fn set_type(&mut self, r#type: ListenerType); - fn set_target(&mut self, target: &str); - fn set_action(&mut self, action: &str); - fn set_value(&mut self, value: StringNumberBool); - fn set_context_key(&mut self, context_key: &str); - - fn get_type(&self) -> &ListenerType; - fn get_target(&self) -> Option; + fn get_layer_name(&self) -> Option; fn get_action(&self) -> Option; - fn get_value(&self) -> Option<&StringNumberBool>; - fn get_context_key(&self) -> Option; -} - -#[derive(Debug, PartialEq)] -pub enum ListenerType { - PointerUp, - PointerDown, - PointerEnter, - PointerExit, - PointerMove, -} - -impl Display for ListenerType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ListenerType::PointerUp => write!(f, "PointerUp"), - ListenerType::PointerDown => write!(f, "PointerDown"), - ListenerType::PointerEnter => write!(f, "PointerEnter"), - ListenerType::PointerExit => write!(f, "PointerExit"), - ListenerType::PointerMove => write!(f, "PointerMove"), - } - } } pub enum ListenerAction { @@ -44,569 +14,95 @@ pub enum ListenerAction { None, } +#[derive(Debug)] pub enum Listener { PointerUp { - r#type: ListenerType, - target: Option, - action: Option, - value: Option, - context_key: Option, + layer_name: Option, + actions: Vec, }, PointerDown { - r#type: ListenerType, - target: Option, - action: Option, - value: Option, - context_key: Option, + layer_name: Option, + actions: Vec, }, PointerEnter { - r#type: ListenerType, - target: Option, - action: Option, - value: Option, - context_key: Option, + layer_name: Option, + actions: Vec, }, PointerMove { - r#type: ListenerType, - target: Option, - action: Option, - value: Option, - context_key: Option, + layer_name: Option, + actions: Vec, }, PointerExit { - r#type: ListenerType, - target: Option, - action: Option, - value: Option, - context_key: Option, + layer_name: Option, + actions: Vec, + }, + OnComplete { + state_name: Option, + actions: Vec, }, } -impl Debug for Listener { +impl Display for Listener { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::PointerUp { - r#type, - target, - action, - value, - context_key, + layer_name, + actions, } => f .debug_struct("PointerUp") - .field("r#type", r#type) - .field("target", target) - .field("action", action) - .field("value", value) - .field("context_key", context_key) + .field("layer_name", layer_name) + .field("action", actions) .finish(), Self::PointerDown { - r#type, - target, - action, - value, - context_key, + layer_name, + actions, } => f - .debug_struct("PointerDown") - .field("r#type", r#type) - .field("target", target) - .field("action", action) - .field("value", value) - .field("context_key", context_key) + .debug_struct("PointerUp") + .field("layer_name", layer_name) + .field("action", actions) .finish(), Self::PointerEnter { - r#type, - target, - action, - value, - context_key, + layer_name, + actions, } => f - .debug_struct("PointerEnter") - .field("r#type", r#type) - .field("target", target) - .field("action", action) - .field("value", value) - .field("context_key", context_key) + .debug_struct("PointerUp") + .field("layer_name", layer_name) + .field("action", actions) .finish(), Self::PointerMove { - r#type, - target, - action, - value, - context_key, + layer_name, + actions, } => f - .debug_struct("PointerMove") - .field("r#type", r#type) - .field("target", target) - .field("action", action) - .field("value", value) - .field("context_key", context_key) + .debug_struct("PointerUp") + .field("layer_name", layer_name) + .field("action", actions) .finish(), Self::PointerExit { - r#type, - target, - action, - value, - context_key, + layer_name, + actions, } => f - .debug_struct("PointerExit") - .field("r#type", r#type) - .field("target", target) - .field("action", action) - .field("value", value) - .field("context_key", context_key) + .debug_struct("PointerUp") + .field("layer_name", layer_name) + .field("action", actions) + .finish(), + Self::OnComplete { + state_name, + actions, + } => f + .debug_struct("PointerUp") + .field("state_name", state_name) + .field("action", actions) .finish(), } } } impl ListenerTrait for Listener { - fn set_type(&mut self, r#listener_type: ListenerType) { - match self { - Listener::PointerUp { - r#type, - target: _, - action: _, - value: _, - context_key: _, - } => { - *r#type = listener_type; - } - Listener::PointerDown { - r#type, - target: _, - action: _, - value: _, - context_key: _, - } => { - *r#type = listener_type; - } - Listener::PointerEnter { - r#type, - target: _, - action: _, - value: _, - context_key: _, - } => { - *r#type = listener_type; - } - Listener::PointerExit { - r#type, - target: _, - action: _, - value: _, - context_key: _, - } => { - *r#type = listener_type; - } - Listener::PointerMove { - r#type, - target: _, - action: _, - value: _, - context_key: _, - } => { - *r#type = listener_type; - } - } - } - - fn set_target(&mut self, new_target: &str) { - match self { - Listener::PointerUp { - r#type: _, - target, - action: _, - value: _, - context_key: _, - } => { - *target = Some(new_target.to_string()); - } - Listener::PointerDown { - r#type: _, - target, - action: _, - value: _, - context_key: _, - } => { - *target = Some(new_target.to_string()); - } - Listener::PointerEnter { - r#type: _, - target, - action: _, - value: _, - context_key: _, - } => { - *target = Some(new_target.to_string()); - } - Listener::PointerExit { - r#type: _, - target, - action: _, - value: _, - context_key: _, - } => { - *target = Some(new_target.to_string()); - } - Listener::PointerMove { - r#type: _, - target, - action: _, - value: _, - context_key: _, - } => { - *target = Some(new_target.to_string()); - } - } - } - - fn set_action(&mut self, new_action: &str) { - match self { - Listener::PointerUp { - r#type: _, - target: _, - action, - value: _, - context_key: _, - } => { - *action = Some(new_action.to_string()); - } - Listener::PointerDown { - r#type: _, - target: _, - action, - value: _, - context_key: _, - } => { - *action = Some(new_action.to_string()); - } - Listener::PointerEnter { - r#type: _, - target: _, - action, - value: _, - context_key: _, - } => { - *action = Some(new_action.to_string()); - } - Listener::PointerExit { - r#type: _, - target: _, - action, - value: _, - context_key: _, - } => { - *action = Some(new_action.to_string()); - } - Listener::PointerMove { - r#type: _, - target: _, - action, - value: _, - context_key: _, - } => { - *action = Some(new_action.to_string()); - } - } - } - - fn set_value(&mut self, new_value: StringNumberBool) { - match self { - Listener::PointerUp { - r#type: _, - target: _, - action: _, - value, - context_key: _, - } => { - *value = Some(new_value); - } - Listener::PointerDown { - r#type: _, - target: _, - action: _, - value, - context_key: _, - } => { - *value = Some(new_value); - } - Listener::PointerEnter { - r#type: _, - target: _, - action: _, - value, - context_key: _, - } => { - *value = Some(new_value); - } - Listener::PointerExit { - r#type: _, - target: _, - action: _, - value, - context_key: _, - } => { - *value = Some(new_value); - } - Listener::PointerMove { - r#type: _, - target: _, - action: _, - value, - context_key: _, - } => { - *value = Some(new_value); - } - } - } - - fn set_context_key(&mut self, new_context_key: &str) { - match self { - Listener::PointerUp { - r#type: _, - target: _, - action: _, - value: _, - context_key, - } => { - *context_key = Some(new_context_key.to_string()); - } - Listener::PointerDown { - r#type: _, - target: _, - action: _, - value: _, - context_key, - } => { - *context_key = Some(new_context_key.to_string()); - } - Listener::PointerEnter { - r#type: _, - target: _, - action: _, - value: _, - context_key, - } => { - *context_key = Some(new_context_key.to_string()); - } - Listener::PointerExit { - r#type: _, - target: _, - action: _, - value: _, - context_key, - } => { - *context_key = Some(new_context_key.to_string()); - } - Listener::PointerMove { - r#type: _, - target: _, - action: _, - value: _, - context_key, - } => { - *context_key = Some(new_context_key.to_string()); - } - } - } - - fn get_type(&self) -> &ListenerType { - match self { - Listener::PointerUp { - r#type, - target: _, - action: _, - value: _, - context_key: _, - } => r#type, - Listener::PointerDown { - r#type, - target: _, - action: _, - value: _, - context_key: _, - } => r#type, - Listener::PointerEnter { - r#type, - target: _, - action: _, - value: _, - context_key: _, - } => r#type, - Listener::PointerExit { - r#type, - target: _, - action: _, - value: _, - context_key: _, - } => r#type, - Listener::PointerMove { - r#type, - target: _, - action: _, - value: _, - context_key: _, - } => r#type, - } - } - - fn get_target(&self) -> Option { - match self { - Listener::PointerUp { - r#type: _, - target, - action: _, - value: _, - context_key: _, - } => target.clone(), - Listener::PointerDown { - r#type: _, - target, - action: _, - value: _, - context_key: _, - } => target.clone(), - Listener::PointerEnter { - r#type: _, - target, - action: _, - value: _, - context_key: _, - } => target.clone(), - Listener::PointerExit { - r#type: _, - target, - action: _, - value: _, - context_key: _, - } => target.clone(), - Listener::PointerMove { - r#type: _, - target, - action: _, - value: _, - context_key: _, - } => target.clone(), - } + fn get_layer_name(&self) -> Option { + todo!() } fn get_action(&self) -> Option { - match self { - Listener::PointerUp { - r#type: _, - target: _, - action, - value: _, - context_key: _, - } => action.clone(), - Listener::PointerDown { - r#type: _, - target: _, - action, - value: _, - context_key: _, - } => action.clone(), - Listener::PointerEnter { - r#type: _, - target: _, - action, - value: _, - context_key: _, - } => action.clone(), - Listener::PointerExit { - r#type: _, - target: _, - action, - value: _, - context_key: _, - } => action.clone(), - Listener::PointerMove { - r#type: _, - target: _, - action, - value: _, - context_key: _, - } => action.clone(), - } - } - - fn get_value(&self) -> Option<&StringNumberBool> { - match self { - Listener::PointerUp { - r#type: _, - target: _, - action: _, - value, - context_key: _, - } => value.as_ref(), - Listener::PointerDown { - r#type: _, - target: _, - action: _, - value, - context_key: _, - } => value.as_ref(), - Listener::PointerEnter { - r#type: _, - target: _, - action: _, - value, - context_key: _, - } => value.as_ref(), - Listener::PointerExit { - r#type: _, - target: _, - action: _, - value, - context_key: _, - } => value.as_ref(), - Listener::PointerMove { - r#type: _, - target: _, - action: _, - value, - context_key: _, - } => value.as_ref(), - } - } - - fn get_context_key(&self) -> Option { - match self { - Listener::PointerUp { - r#type: _, - target: _, - action: _, - value: _, - context_key, - } => context_key.clone(), - Listener::PointerDown { - r#type: _, - target: _, - action: _, - value: _, - context_key, - } => context_key.clone(), - Listener::PointerEnter { - r#type: _, - target: _, - action: _, - value: _, - context_key, - } => context_key.clone(), - Listener::PointerExit { - r#type: _, - target: _, - action: _, - value: _, - context_key, - } => context_key.clone(), - Listener::PointerMove { - r#type: _, - target: _, - action: _, - value: _, - context_key, - } => context_key.clone(), - } + todo!() } } diff --git a/dotlottie-rs/src/state_machine/mod.rs b/dotlottie-rs/src/state_machine/mod.rs index a0b81646..ffa88abe 100644 --- a/dotlottie-rs/src/state_machine/mod.rs +++ b/dotlottie-rs/src/state_machine/mod.rs @@ -1,7 +1,9 @@ use std::collections::HashMap; +use std::fmt::{Debug, Display}; use std::rc::Rc; -use std::sync::{Arc, RwLock}; +use std::sync::RwLock; +mod actions; pub mod errors; pub mod events; pub mod listeners; @@ -9,12 +11,14 @@ pub mod parser; pub mod states; pub mod transitions; +use actions::Action; +use parser::{ActionJson, ListenerJson, StateJson, TransitionJson}; +use states::StateTrait; + use crate::parser::StringNumberBool; use crate::state_machine::listeners::Listener; -use crate::state_machine::states::StateTrait; use crate::state_machine::transitions::guard::Guard; -use crate::state_machine::transitions::TransitionTrait; -use crate::{Config, DotLottiePlayerContainer, InternalEvent, Layout, Mode, PointerEvent}; +use crate::{Config, DotLottiePlayerContainer, Layout, Mode}; use self::parser::state_machine_parse; use self::{errors::StateMachineError, events::Event, states::State, transitions::Transition}; @@ -25,45 +29,66 @@ pub trait StateMachineObserver: Send + Sync { fn on_state_exit(&self, leaving_state: String); } -#[derive(PartialEq)] +#[derive(PartialEq, Debug)] pub enum StateMachineStatus { Running, Paused, Stopped, } +// todo: Remove Rcs, or replace with Rcs pub struct StateMachine { - pub global_state: Option>>, - pub states: Vec>>, - pub listeners: Vec>>, - pub current_state: Option>>, + pub global_state: Option, + pub states: HashMap, + + pub listeners: Vec, + pub current_state: Option>, pub player: Option>>, pub status: StateMachineStatus, - numeric_context: HashMap, - string_context: HashMap, - bool_context: HashMap, + numeric_trigger: HashMap, + string_trigger: HashMap, + bool_trigger: HashMap, + event_trigger: HashMap, - observers: RwLock>>, + observers: RwLock>>, } impl Default for StateMachine { fn default() -> StateMachine { StateMachine { global_state: None, - states: Vec::new(), + states: HashMap::new(), listeners: Vec::new(), current_state: None, player: None, - numeric_context: HashMap::new(), - string_context: HashMap::new(), - bool_context: HashMap::new(), + numeric_trigger: HashMap::new(), + string_trigger: HashMap::new(), + bool_trigger: HashMap::new(), + event_trigger: HashMap::new(), status: StateMachineStatus::Stopped, observers: RwLock::new(Vec::new()), } } } +impl Display for StateMachine { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("StateMachine") + .field("global_state", &self.global_state) + .field("states", &self.states) + .field("listeners", &self.listeners) + .field("current_state", &self.current_state) + .field("numeric_trigger", &self.numeric_trigger) + .field("string_trigger", &self.string_trigger) + .field("bool_trigger", &self.bool_trigger) + .field("event_trigger", &self.event_trigger) + .field("status", &self.status) + .finish() + } +} + +// todo: Use a lifetime for the player impl StateMachine { pub fn new( state_machine_definition: &str, @@ -71,13 +96,14 @@ impl StateMachine { ) -> Result { let mut state_machine = StateMachine { global_state: None, - states: Vec::new(), + states: HashMap::new(), listeners: Vec::new(), current_state: None, player: Some(player.clone()), - numeric_context: HashMap::new(), - string_context: HashMap::new(), - bool_context: HashMap::new(), + numeric_trigger: HashMap::new(), + string_trigger: HashMap::new(), + bool_trigger: HashMap::new(), + event_trigger: HashMap::new(), status: StateMachineStatus::Stopped, observers: RwLock::new(Vec::new()), }; @@ -85,837 +111,836 @@ impl StateMachine { state_machine.create_state_machine(state_machine_definition, &player) } - pub fn subscribe(&self, observer: Arc) { + pub fn subscribe(&self, observer: Rc) { let mut observers = self.observers.write().unwrap(); observers.push(observer); } - pub fn unsubscribe(&self, observer: &Arc) { + pub fn unsubscribe(&self, observer: &Rc) { self.observers .write() .unwrap() - .retain(|o| !Arc::ptr_eq(o, observer)); + .retain(|o| !Rc::ptr_eq(o, observer)); } - pub fn get_numeric_context(&self, key: &str) -> Option { - self.numeric_context.get(key).cloned() + pub fn get_numeric_trigger(&self, key: &str) -> Option { + self.numeric_trigger.get(key).cloned() } - pub fn get_string_context(&self, key: &str) -> Option { - self.string_context.get(key).cloned() + pub fn get_string_trigger(&self, key: &str) -> Option { + self.string_trigger.get(key).cloned() } - pub fn get_bool_context(&self, key: &str) -> Option { - self.bool_context.get(key).cloned() + pub fn get_bool_trigger(&self, key: &str) -> Option { + self.bool_trigger.get(key).cloned() } - pub fn set_numeric_context(&mut self, key: &str, value: f32) { - self.numeric_context.insert(key.to_string(), value); + pub fn set_numeric_trigger(&mut self, key: &str, value: f32) { + self.numeric_trigger.insert(key.to_string(), value); } - pub fn set_string_context(&mut self, key: &str, value: &str) { - self.string_context + pub fn set_string_trigger(&mut self, key: &str, value: &str) { + self.string_trigger .insert(key.to_string(), value.to_string()); } - pub fn set_bool_context(&mut self, key: &str, value: bool) { - self.bool_context.insert(key.to_string(), value); + pub fn set_bool_trigger(&mut self, key: &str, value: bool) { + self.bool_trigger.insert(key.to_string(), value); } - // Parses the JSON of the state machine definition and creates the states and transitions - pub fn create_state_machine( - &mut self, - sm_definition: &str, - player: &Rc>, - ) -> Result { - let parsed_state_machine = state_machine_parse(sm_definition); + fn build_listener(&self, listener_to_build: ListenerJson) -> Listener { + match listener_to_build { + ListenerJson::PointerUp { + layer_name, + actions, + } => { + let mut new_actions: Vec = Vec::new(); - if parsed_state_machine.is_err() { - println!( - "Error parsing state machine definition: {:?}", - parsed_state_machine.err() - ); - return Err(StateMachineError::ParsingError { - reason: "Failed to parse state machine definition".to_string(), - }); - } + for action in actions { + let new_action = self.build_action(action); - // TODO - // - Report PROPER errors if there are any - // - Create states and transitions based on the parsed state machine - // - Run it through a check pipeline to ensure everything is valid. Types based on their actions, inf. loops etc. + new_actions.push(new_action); + } - Ok(StateMachine::default()) + let new_listener = Listener::PointerUp { + layer_name, + actions: new_actions, + }; - // todo somehow get the context json without having to parse it again - // self.json_context = Some( - // state_machine_parse(sm_definition) - // .unwrap() - // .context_variables, - // ); + new_listener + } + ListenerJson::PointerDown { + layer_name, + actions, + } => { + let mut new_actions: Vec = Vec::new(); - // let mut states: Vec>> = Vec::new(); - // let mut global_state: Option>> = None; - // let mut listeners: Vec>> = Vec::new(); - // let mut new_state_machine = StateMachine::default(); - - // match parsed_state_machine { - // Ok(parsed_state_machine) => { - // // Loop through result json states and create objects for each - // for state in parsed_state_machine.states { - // match state { - // parser::StateJson::PlaybackState { - // name, - // animation_id, - // r#loop, - // autoplay, - // mode, - // speed, - // segment, - // background_color, - // use_frame_interpolation, - // reset_context, - // marker, - // .. - // } => { - // let unwrapped_mode = mode.unwrap_or("Forward".to_string()); - // let mode = { - // match unwrapped_mode.as_str() { - // "Forward" => Mode::Forward, - // "Reverse" => Mode::Reverse, - // "Bounce" => Mode::Bounce, - // "ReverseBounce" => Mode::ReverseBounce, - // _ => Mode::Forward, - // } - // }; - - // let default_config = Config::default(); - - // // Fill out a config with the state's values, if absent use default config values - // let playback_config = Config { - // mode, - // loop_animation: r#loop.unwrap_or(default_config.loop_animation), - // speed: speed.unwrap_or(default_config.speed), - // use_frame_interpolation: use_frame_interpolation - // .unwrap_or(default_config.use_frame_interpolation), - // autoplay: autoplay.unwrap_or(default_config.autoplay), - // segment: segment.unwrap_or(default_config.segment), - // background_color: background_color - // .unwrap_or(default_config.background_color), - // layout: Layout::default(), - // marker: marker.unwrap_or(default_config.marker), - // }; - - // // Construct a State with the values we've gathered - // let new_playback_state = State::Playback { - // name, - // config: playback_config, - // reset_context: reset_context.unwrap_or("".to_string()), - // animation_id: animation_id.unwrap_or("".to_string()), - // transitions: Vec::new(), - // }; - - // states.push(Arc::new(RwLock::new(new_playback_state))); - // } - // parser::StateJson::SyncState { - // name, - // animation_id, - // background_color, - // reset_context, - // frame_context_key, - // segment, - // .. - // } => { - // let mut config = Config::default(); - - // config.background_color = - // background_color.unwrap_or(config.background_color); - // config.segment = segment.unwrap_or(config.segment); - - // let new_sync_state = State::Sync { - // name, - // frame_context_key, - // reset_context: reset_context.unwrap_or("".to_string()), - // animation_id: animation_id.unwrap_or("".to_string()), - // transitions: Vec::new(), - // config, - // }; - - // states.push(Arc::new(RwLock::new(new_sync_state))); - // } - // parser::StateJson::GlobalState { - // name, - // reset_context, - // entry_actions: _, - // exit_actions: _, - // } => { - // let new_global_state = State::Global { - // name, - // reset_context: reset_context.unwrap_or("".to_string()), - // transitions: Vec::new(), - // }; - - // let locked_global_state = Arc::new(RwLock::new(new_global_state)); - - // global_state = Some(locked_global_state.clone()); - - // states.push(locked_global_state); - // } - // } - // } + for action in actions { + let new_action = self.build_action(action); - // // Loop through result transitions and create objects for each - // for transition in parsed_state_machine.transitions { - // match transition { - // parser::TransitionJson::Transition { - // from_state, - // to_state, - // guards, - // numeric_event, - // string_event, - // boolean_event, - // on_complete_event, - // on_pointer_down_event, - // on_pointer_up_event, - // on_pointer_enter_event, - // on_pointer_exit_event, - // on_pointer_move_event, - // } => { - // let target_state_index = to_state; - // let mut guards_for_transition: Vec = Vec::new(); - - // // Use the provided index to get the state in the vec we've built - // if target_state_index >= states.len() as u32 { - // return Err(StateMachineError::ParsingError { - // reason: "Transition has an invalid target state index value!" - // .to_string(), - // }); - // } - - // // Loop through transition guards and create equivalent Guard objects - // if guards.is_some() { - // let guards = guards.unwrap(); - - // for guard in guards { - // let new_guard = Guard { - // context_key: guard.context_key, - // condition_type: guard.condition_type, - // compare_to: guard.compare_to, - // }; - - // guards_for_transition.push(new_guard); - // } - // } - - // // let mut new_transition: Option = None; - // let mut state_to_attach_to: i32 = -1; - // let mut new_event: Option = None; - - // // Capture which event this transition has - // if numeric_event.is_some() { - // let numeric_event = numeric_event.unwrap(); - // new_event = Some(InternalEvent::Numeric { - // value: numeric_event.value, - // }); - // state_to_attach_to = from_state as i32; - // } else if string_event.is_some() { - // let string_event = string_event.unwrap(); - // new_event = Some(InternalEvent::String { - // value: string_event.value, - // }); - // state_to_attach_to = from_state as i32; - // } else if boolean_event.is_some() { - // let boolean_event = boolean_event.unwrap(); - // new_event = Some(InternalEvent::Bool { - // value: boolean_event.value, - // }); - // state_to_attach_to = from_state as i32; - // } else if on_complete_event.is_some() { - // new_event = Some(InternalEvent::OnComplete); - // state_to_attach_to = from_state as i32; - // } else if on_pointer_down_event.is_some() { - // // Default to 0.0 0.0 coordinates - // let pointer_down_event = on_pointer_down_event.unwrap(); - - // if pointer_down_event.target.is_some() { - // new_event = Some(InternalEvent::OnPointerDown { - // target: pointer_down_event.target, - // }); - // } else { - // new_event = Some(InternalEvent::OnPointerDown { target: None }); - // } - - // state_to_attach_to = from_state as i32; - // } else if on_pointer_up_event.is_some() { - // // Default to 0.0 0.0 coordinates - - // let pointer_up_event = on_pointer_up_event.unwrap(); - - // if pointer_up_event.target.is_some() { - // new_event = Some(InternalEvent::OnPointerUp { - // target: pointer_up_event.target, - // }); - // } else { - // new_event = Some(InternalEvent::OnPointerUp { target: None }); - // } - - // state_to_attach_to = from_state as i32; - // } else if on_pointer_enter_event.is_some() { - // // Default to 0.0 0.0 coordinates - // let pointer_enter_event = on_pointer_enter_event.unwrap(); - - // if pointer_enter_event.target.is_some() { - // new_event = Some(InternalEvent::OnPointerEnter { - // target: pointer_enter_event.target, - // }); - // } else { - // new_event = - // Some(InternalEvent::OnPointerEnter { target: None }); - // } - - // state_to_attach_to = from_state as i32; - // } else if on_pointer_exit_event.is_some() { - // // Default to 0.0 0.0 coordinates - // let pointer_exit_event = on_pointer_exit_event.unwrap(); - - // if pointer_exit_event.target.is_some() { - // new_event = Some(InternalEvent::OnPointerExit { - // target: pointer_exit_event.target, - // }); - // } else { - // new_event = Some(InternalEvent::OnPointerExit { target: None }); - // } - - // state_to_attach_to = from_state as i32; - // } else if on_pointer_move_event.is_some() { - // // Default to 0.0 0.0 coordinates - // let pointer_move_event = on_pointer_move_event.unwrap(); - - // if pointer_move_event.target.is_some() { - // new_event = Some(InternalEvent::OnPointerMove { - // target: pointer_move_event.target, - // }); - // } else { - // new_event = Some(InternalEvent::OnPointerMove { target: None }); - // } - - // state_to_attach_to = from_state as i32; - // } - // if let Some(event) = new_event { - // let new_transition = Transition::Transition { - // target_state: target_state_index, - // event: Arc::new(RwLock::new(event)), - // guards: guards_for_transition, - // }; - - // // Since the target is valid and transition created, we attach it to the state - // if state_to_attach_to < states.len() as i32 { - // let try_write_state = - // states[state_to_attach_to as usize].try_write(); - - // try_write_state - // .map_err(|_| StateMachineError::ParsingError { - // reason: "Failed to write to state".to_string(), - // })? - // .add_transition(new_transition); - // } - // } - // } - // } - // } + new_actions.push(new_action); + } - // for listener in parsed_state_machine.listeners { - // match listener.r#type { - // parser::ListenerJsonType::PointerUp => { - // let new_listener = Listener::PointerUp { - // r#type: listeners::ListenerType::PointerUp, - // target: listener.target, - // action: listener.action, - // value: listener.value, - // context_key: listener.context_key, - // }; - - // listeners.push(Arc::new(RwLock::new(new_listener))); - // } - // parser::ListenerJsonType::PointerDown => { - // let new_listener = Listener::PointerDown { - // r#type: listeners::ListenerType::PointerDown, - // target: listener.target, - // action: listener.action, - // value: listener.value, - // context_key: listener.context_key, - // }; - - // listeners.push(Arc::new(RwLock::new(new_listener))); - // } - // parser::ListenerJsonType::PointerEnter => { - // let new_listener = Listener::PointerEnter { - // r#type: listeners::ListenerType::PointerEnter, - // target: listener.target, - // action: listener.action, - // value: listener.value, - // context_key: listener.context_key, - // }; - - // listeners.push(Arc::new(RwLock::new(new_listener))); - // } - // parser::ListenerJsonType::PointerExit => { - // let new_listener = Listener::PointerExit { - // r#type: listeners::ListenerType::PointerExit, - // target: listener.target, - // action: listener.action, - // value: listener.value, - // context_key: listener.context_key, - // }; - - // listeners.push(Arc::new(RwLock::new(new_listener))); - // } - // parser::ListenerJsonType::PointerMove => { - // let new_listener = Listener::PointerMove { - // r#type: listeners::ListenerType::PointerMove, - // target: listener.target, - // action: listener.action, - // value: listener.value, - // context_key: listener.context_key, - // }; - - // listeners.push(Arc::new(RwLock::new(new_listener))); - // } - // } - // } + let new_listener = Listener::PointerDown { + layer_name, + actions: new_actions, + }; - // // Since value can either be a string, int or bool, we need to check the type and set the context accordingly - // for variable in parsed_state_machine.context_variables { - // match variable.r#type { - // ContextJsonType::Numeric => { - // if let StringNumberBool::F32(value) = variable.value { - // new_state_machine.set_numeric_context(&variable.key, value); - // } - // } - // ContextJsonType::String => { - // if let StringNumberBool::String(value) = variable.value { - // new_state_machine.set_string_context(&variable.key, value.as_str()); - // } - // } - // ContextJsonType::Boolean => { - // if let StringNumberBool::Bool(value) = variable.value { - // new_state_machine.set_bool_context(&variable.key, value); - // } - // } - // } - // } + new_listener + } + ListenerJson::PointerEnter { + layer_name, + actions, + } => { + let mut new_actions: Vec = Vec::new(); - // let mut initial_state = None; + for action in actions { + let new_action = self.build_action(action); - // // All states and transitions have been created, we can set the state machine's initial state - // let initial_state_index = parsed_state_machine.descriptor.initial; + new_actions.push(new_action); + } - // if initial_state_index < states.len() as u32 { - // initial_state = Some(states[initial_state_index as usize].clone()); - // } + let new_listener = Listener::PointerEnter { + layer_name, + actions: new_actions, + }; - // new_state_machine = StateMachine { - // global_state, - // states, - // listeners, - // current_state: initial_state, - // player: Some(player.clone()), - // numeric_context: new_state_machine.numeric_context, - // string_context: new_state_machine.string_context, - // bool_context: new_state_machine.bool_context, - // status: StateMachineStatus::Stopped, - // observers: RwLock::new(Vec::new()), - // }; - - // Ok(new_state_machine) - // } - // Err(error) => Err(error), - // } - } + new_listener + } + ListenerJson::PointerExit { + layer_name, + actions, + } => { + let mut new_actions: Vec = Vec::new(); - pub fn start(&mut self) { - self.status = StateMachineStatus::Running; - self.execute_current_state(); - } + for action in actions { + let new_action = self.build_action(action); - pub fn pause(&mut self) { - self.status = StateMachineStatus::Paused; - } + new_actions.push(new_action); + } - pub fn end(&mut self) { - self.status = StateMachineStatus::Stopped; - } + let new_listener = Listener::PointerExit { + layer_name, + actions: new_actions, + }; - pub fn set_initial_state(&mut self, state: Arc>) { - self.current_state = Some(state); - } + new_listener + } + ListenerJson::PointerMove { + layer_name, + actions, + } => { + let mut new_actions: Vec = Vec::new(); - pub fn get_current_state(&self) -> Option>> { - self.current_state.clone() - } + for action in actions { + let new_action = self.build_action(action); - pub fn add_state(&mut self, state: Arc>) { - self.states.push(state); - } + new_actions.push(new_action); + } + let new_listener = Listener::PointerMove { + layer_name, + actions: new_actions, + }; - pub fn get_listeners(&self) -> &Vec>> { - &self.listeners + new_listener + } + ListenerJson::OnComplete { + state_name, + actions, + } => { + let mut new_actions: Vec = Vec::new(); + + for action in actions { + let new_action = self.build_action(action); + + new_actions.push(new_action); + } + + let new_listener = Listener::OnComplete { + state_name, + actions: new_actions, + }; + + new_listener + } + } } - // Return codes - // 0: Success - // 1: Failure - // 2: Play animation - // 3: Pause animation - // 4: Request and draw a new single frame of the animation (needed for sync state) - pub fn execute_current_state(&mut self) -> i32 { - if self.current_state.is_none() { - return 1; + fn build_action(&self, action_to_build: ActionJson) -> Action { + match action_to_build { + parser::ActionJson::OpenUrl { url } => Action::OpenUrl { url }, + parser::ActionJson::ThemeAction { theme_id, target } => Action::SetTheme { theme_id }, + parser::ActionJson::Increment { + trigger_name, + value, + } => Action::Increment { + trigger_name, + value, + }, + parser::ActionJson::Decrement { + trigger_name, + value, + } => Action::Decrement { + trigger_name, + value, + }, + parser::ActionJson::Toggle { trigger_name } => Action::Toggle { trigger_name }, + parser::ActionJson::SetBoolean { + trigger_name, + value, + } => Action::SetBoolean { + trigger_name, + value, + }, + parser::ActionJson::SetString { + trigger_name, + value, + } => Action::SetString { + trigger_name, + value, + }, + parser::ActionJson::SetNumeric { + trigger_name, + value, + } => Action::SetNumeric { + trigger_name, + value, + }, + parser::ActionJson::Fire { trigger_name } => Action::Fire { trigger_name }, + parser::ActionJson::SetExpression { + layer_name, + property_index, + var_name, + value, + } => Action::SetExpression { + layer_name, + property_index, + var_name, + value, + }, + parser::ActionJson::SetTheme { theme_id } => Action::SetTheme { theme_id }, + parser::ActionJson::SetFrame { value } => Action::SetFrame { value }, + parser::ActionJson::SetSlot { value } => Action::SetSlot { value }, + parser::ActionJson::FireCustomEvent { value } => Action::FireCustomEvent { value }, } + } - // Check if current_state is not None and execute the state - if let Some(ref state) = self.current_state { - let unwrapped_state = state.read().unwrap(); - let reset_key = unwrapped_state.get_reset_context_key(); - - if !reset_key.is_empty() { - if reset_key == "*" { - // Todo dont clear reset to their original values from file - // self.numeric_context.clear(); - // self.string_context.clear(); - // self.bool_context.clear(); - } else { - if self.numeric_context.contains_key(reset_key) { - // self.numeric_context.remove(reset_key); + fn build_state(&self, state_to_build: StateJson) -> State { + match state_to_build { + StateJson::PlaybackState { + name, + transitions, + animation_id, + r#loop, + autoplay, + mode, + speed, + segment, + background_color, + use_frame_interpolation, + entry_actions, + exit_actions, + } => { + let unwrapped_mode = mode.unwrap_or("Forward".to_string()); + let mode = { + match unwrapped_mode.as_str() { + "Forward" => Mode::Forward, + "Reverse" => Mode::Reverse, + "Bounce" => Mode::Bounce, + "ReverseBounce" => Mode::ReverseBounce, + _ => Mode::Forward, + } + }; + + let default_config = Config::default(); + + // Fill out a config with the state's values, if absent use default config values + let playback_config = Config { + mode, + loop_animation: r#loop.unwrap_or(default_config.loop_animation), + speed: speed.unwrap_or(default_config.speed), + use_frame_interpolation: use_frame_interpolation + .unwrap_or(default_config.use_frame_interpolation), + autoplay: autoplay.unwrap_or(default_config.autoplay), + marker: segment.unwrap_or(default_config.marker), + background_color: background_color.unwrap_or(default_config.background_color), + layout: Layout::default(), + segment: [].to_vec(), + }; + + let mut state_entry_actions: Vec = Vec::new(); + let mut state_exit_actions: Vec = Vec::new(); + + /* Create the entry-actions */ + if let Some(actions) = entry_actions { + for action in actions { + let new_action = self.build_action(action); + + state_entry_actions.push(new_action); } + } + + /* Create the exit-actions */ + if let Some(actions) = exit_actions { + for action in actions { + let new_action = self.build_action(action); - if self.string_context.contains_key(reset_key) { - // self.string_context.remove(reset_key); + state_exit_actions.push(new_action); } + } + + // Construct a State with the values we've gathered + let mut new_playback_state = State::Playback { + name, + config: playback_config, + animation_id: animation_id.unwrap_or("".to_string()), + transitions: Vec::new(), + entry_actions: Some(state_entry_actions), + exit_actions: Some(state_exit_actions), + }; + + // Build the transitions + for transition in transitions { + match transition { + TransitionJson::Transition { to_state, guards } => { + let new_transition = Transition::Transition { + target_state: to_state, + guards: Vec::new(), + }; - if self.bool_context.contains_key(reset_key) { - // self.bool_context.remove(reset_key); + new_playback_state.add_transition(&new_transition); + } } } - } - if self.player.is_some() { - return unwrapped_state.execute( - self.player.as_mut().unwrap(), - &self.string_context, - &self.bool_context, - &self.numeric_context, - ); - } else { - return 1; + new_playback_state } - } + StateJson::GlobalState { + name, + transitions, + entry_actions, + exit_actions, + } => { + let mut state_entry_actions: Vec = Vec::new(); + let mut state_exit_actions: Vec = Vec::new(); + + /* Create the entry-actions */ + if let Some(actions) = entry_actions { + for action in actions { + let new_action = self.build_action(action); + + state_entry_actions.push(new_action); + } + } - 0 - } + /* Create the exit-actions */ + if let Some(actions) = exit_actions { + for action in actions { + let new_action = self.build_action(action); - fn verify_if_guards_are_met(&self, guard: &Guard) -> bool { - match guard.compare_to { - StringNumberBool::String(_) => { - if guard.string_context_is_satisfied(&self.string_context) { - return true; - } - } - StringNumberBool::F32(_) => { - if guard.numeric_context_is_satisfied(&self.numeric_context) { - return true; + state_exit_actions.push(new_action); + } } - } - StringNumberBool::Bool(_) => { - if guard.bool_context_is_satisfied(&self.bool_context) { - return true; + + let mut new_global_state = State::Global { + name, + transitions: Vec::new(), + entry_actions: Some(state_entry_actions), + exit_actions: Some(state_exit_actions), + }; + + // Build the transitions + for transition in transitions { + match transition { + TransitionJson::Transition { to_state, guards } => { + let new_transition = Transition::Transition { + target_state: to_state, + guards: Vec::new(), + }; + + new_global_state.add_transition(&new_transition); + } + } } + + new_global_state } } - - false } - fn perform_hit_check(&self, target: &str, x: f32, y: f32) -> bool { - // A layer name was provided, we need to check if the pointer is within the layer - let pointer_target = target; + // Parses the JSON of the state machine definition and creates the states and transitions + pub fn create_state_machine( + &mut self, + sm_definition: &str, + player: &Rc>, + ) -> Result { + let parsed_state_machine = state_machine_parse(sm_definition); - let player_ref = self.player.as_ref(); + if parsed_state_machine.is_err() { + println!( + "Error parsing state machine definition: {:?}", + parsed_state_machine.err() + ); + return Err(StateMachineError::ParsingError { + reason: "Failed to parse state machine definition".to_string(), + }); + } - if player_ref.is_some() { - let player = player_ref.unwrap(); - let player_read = player.try_read(); + // TODO + // - Report PROPER errors if there are any + // - Create states and transitions based on the parsed state machine + // - Run it through a check pipeline to ensure everything is valid. Types based on their actions, inf. loops etc. - match player_read { - Ok(player) => { - let player = &*player; + // todo somehow get the trigger json without having to parse it again + // self.json_trigger = Some( + // state_machine_parse(sm_definition) + // .unwrap() + // .trigger_variables, + // ); - player.hit_check(pointer_target, x, y) - } - Err(_) => false, - } - } else { - false - } - } + let mut new_state_machine = StateMachine::default(); - fn evaluate_transition(&self, transitions: &[Arc>], event: &Event) -> i32 { - let mut tmp_state: i32 = -1; - let iter = transitions.iter(); - - for transition in iter { - let unwrapped_transition = transition.read().unwrap(); - let target_state = unwrapped_transition.get_target_state(); - let transition = &*unwrapped_transition; - let event_lock = transition.get_event(); - let event_data = event_lock.read().unwrap(); - let transition_event = &*event_data; - let transition_guards = transition.get_guards(); - - // Match the transition's event type and compare it to the received event - match transition_event { - InternalEvent::Bool { value } => { - let bool_value = value; - - if let Event::Bool { value } = event { - if *value == *bool_value { - // If there are guards loop over them and check if theyre verified - if !transition_guards.is_empty() { - for guard in transition_guards { - if self.verify_if_guards_are_met(guard) { - tmp_state = target_state as i32; - } - } - } else { - tmp_state = target_state as i32; - } - } - } - } - InternalEvent::String { value } => { - let string_value = value; - - if let Event::String { value } = event { - if string_value == value { - // If there are guards loop over them and check if theyre verified - if !transition_guards.is_empty() { - for guard in transition_guards { - if self.verify_if_guards_are_met(guard) { - tmp_state = target_state as i32; - } - } - } else { - tmp_state = target_state as i32; - } - } - } - } - InternalEvent::Numeric { value } => { - let num_value = value; - - if let Event::Numeric { value } = event { - if *value == *num_value { - // If there are guards loop over them and check if theyre verified - if !transition_guards.is_empty() { - for guard in transition_guards { - if self.verify_if_guards_are_met(guard) { - tmp_state = target_state as i32; - } - } - } else { - tmp_state = target_state as i32; - } - } - } - } - InternalEvent::OnComplete => { - if let Event::OnComplete = event { - // If there are guards loop over them and check if theyre verified - if !transition_guards.is_empty() { - for guard in transition_guards { - if self.verify_if_guards_are_met(guard) { - tmp_state = target_state as i32; - } - } - } else { - tmp_state = target_state as i32; - } + match parsed_state_machine { + Ok(parsed_state_machine) => { + /* Build every state and their transitions */ + for state in parsed_state_machine.states { + let new_state = self.build_state(state); + + if let State::Global { .. } = new_state { + new_state_machine.global_state = Some(new_state.clone()); } + + /* Insert the newly built state in to the hashmap of the state machine */ + new_state_machine + .states + .insert(new_state.get_name().to_string(), new_state); } - // This is checking the state machine's event, not the passed event - InternalEvent::OnPointerDown { target } => { - if let Event::OnPointerDown { x, y } = event { - // If there are guards loop over them and check if theyre verified - if !transition_guards.is_empty() { - for guard in transition_guards { - if self.verify_if_guards_are_met(guard) { - tmp_state = target_state as i32; - } - } - } else if target.is_some() && self.player.is_some() { - if self.perform_hit_check(target.as_ref().unwrap(), *x, *y) { - tmp_state = target_state as i32; - } - } else { - tmp_state = target_state as i32; - } + + /* Build every listener and their action */ + if let Some(listeners) = parsed_state_machine.listeners { + for listener in listeners { + let new_listener = self.build_listener(listener); + + new_state_machine.listeners.push(new_listener); } } - InternalEvent::OnPointerUp { target } => { - if let Event::OnPointerUp { x, y } = event { - // If there are guards loop over them and check if theyre verified - if !transition_guards.is_empty() { - for guard in transition_guards { - if self.verify_if_guards_are_met(guard) { - tmp_state = target_state as i32; - } + + /* Build all trigger variables */ + if let Some(triggers) = parsed_state_machine.triggers { + for trigger in triggers { + match trigger { + parser::TriggerJson::Numeric { name, value } => { + new_state_machine.set_numeric_trigger(&name, value); } - } else if target.is_some() && self.player.is_some() { - if self.perform_hit_check(target.as_ref().unwrap(), *x, *y) { - tmp_state = target_state as i32; + parser::TriggerJson::String { name, value } => { + new_state_machine.set_string_trigger(&name, &value); } - } else { - tmp_state = target_state as i32; - } - } - } - InternalEvent::OnPointerMove { target } => { - if let Event::OnPointerMove { x, y } = event { - // If there are guards loop over them and check if theyre verified - if !transition_guards.is_empty() { - for guard in transition_guards { - if self.verify_if_guards_are_met(guard) { - tmp_state = target_state as i32; - } + parser::TriggerJson::Boolean { name, value } => { + new_state_machine.set_bool_trigger(&name, value); } - } else if target.is_some() && self.player.is_some() { - if self.perform_hit_check(target.as_ref().unwrap(), *x, *y) { - tmp_state = target_state as i32; + parser::TriggerJson::Event { name } => { + new_state_machine.event_trigger.insert(name, "".to_string()); } - } else { - tmp_state = target_state as i32; } } } - InternalEvent::OnPointerEnter { target } => { - let mut received_event_values = Event::OnPointerEnter { x: 0.0, y: 0.0 }; - match event { - Event::OnPointerEnter { x, y } => { - received_event_values = Event::OnPointerEnter { x: *x, y: *y }; - } - Event::OnPointerMove { x, y } => { - received_event_values = Event::OnPointerMove { x: *x, y: *y }; - } - _ => {} - } + new_state_machine.player = Some(player.clone()); - // If there are guards loop over them and check if theyre verified - if !transition_guards.is_empty() { - for guard in transition_guards { - if self.verify_if_guards_are_met(guard) { - tmp_state = target_state as i32; - } - } - } else if target.is_some() && self.player.is_some() { - if self.perform_hit_check( - target.as_ref().unwrap(), - received_event_values.x(), - received_event_values.y(), - ) { - let current_state_name = - if let Some(current_state) = &self.current_state { - if let Ok(state) = current_state.read() { - state.get_name() - } else { - return -1; - } - } else { - return -1; - }; - - let target_state_name = if let Some(target_state) = - self.states.get(target_state as usize) - { - if let Ok(state) = target_state.read() { - state.get_name() - } else { - return 1; // Handle read lock error - } - } else { - return 1; // Handle invalid index - }; + // All states and transitions have been created, we can set the state machine's initial state + let initial_state_index = parsed_state_machine.descriptor.initial; - // This prevent the state from transitioning to itself over and over again - if current_state_name != target_state_name { - tmp_state = target_state as i32; - } - } - } else { - tmp_state = target_state as i32; - } + if let Some(state) = new_state_machine.states.get(&initial_state_index) { + /* Create a reference to the state marked as initial */ + new_state_machine.current_state = Some(Rc::new(state.clone())); } - InternalEvent::OnPointerExit { target } => { - let mut received_event_values = Event::OnPointerEnter { x: 0.0, y: 0.0 }; - match event { - Event::OnPointerExit { x, y } => { - received_event_values = Event::OnPointerExit { x: *x, y: *y }; - } - Event::OnPointerMove { x, y } => { - received_event_values = Event::OnPointerMove { x: *x, y: *y }; - } - _ => {} - } - - // If there are guards loop over them and check if theyre verified - if !transition_guards.is_empty() { - for guard in transition_guards { - if self.verify_if_guards_are_met(guard) { - tmp_state = target_state as i32; - } - } - } else if target.is_some() && self.player.is_some() { - // Check if current state is the target state - let current_state_name = if let Some(current_state) = &self.current_state { - if let Ok(state) = current_state.read() { - state.get_name() - } else { - return -1; - } - } else { - return -1; - }; - - if current_state_name == *target.as_ref().unwrap() - && !self.perform_hit_check( - target.as_ref().unwrap(), - received_event_values.x(), - received_event_values.y(), - ) - { - tmp_state = target_state as i32; - } - } else { - // Check if coordinates are outside of the player - let (width, height) = self.player.as_ref().unwrap().read().unwrap().size(); - - if received_event_values.x() < 0.0 - || received_event_values.x() > width as f32 - || received_event_values.y() < 0.0 - || received_event_values.y() > height as f32 - { - tmp_state = target_state as i32; - } - } - } - InternalEvent::SetNumericContext { key: _, value: _ } => {} + Ok(new_state_machine) } + Err(error) => return Err(error), } + } + + pub fn start(&mut self) { + self.status = StateMachineStatus::Running; + // self.execute_current_state(); + } + + pub fn pause(&mut self) { + self.status = StateMachineStatus::Paused; + } - tmp_state + pub fn end(&mut self) { + self.status = StateMachineStatus::Stopped; + } + + pub fn get_current_state(&self) -> Option> { + self.current_state.clone() + } + + // pub fn add_state(&mut self, state: Rc>) { + // self.states.push(state); + // } + + pub fn get_listeners(&self) -> &Vec { + &self.listeners } + // Return codes + // 0: Success + // 1: Failure + // 2: Play animation + // 3: Pause animation + // 4: Request and draw a new single frame of the animation (needed for sync state) + // pub fn execute_current_state(&mut self) -> i32 { + // if self.current_state.is_none() { + // return 1; + // } + + // // Check if current_state is not None and execute the state + // if let Some(ref state) = self.current_state { + // let unwrapped_state = state; + // // let reset_key = unwrapped_state.get_reset_trigger_key(); + + // if !reset_key.is_empty() { + // if reset_key == "*" { + // // Todo dont clear reset to their original values from file + // // self.numeric_trigger.clear(); + // // self.string_trigger.clear(); + // // self.bool_trigger.clear(); + // } else { + // if self.numeric_trigger.contains_key(reset_key) { + // // self.numeric_trigger.remove(reset_key); + // } + + // if self.string_trigger.contains_key(reset_key) { + // // self.string_trigger.remove(reset_key); + // } + + // if self.bool_trigger.contains_key(reset_key) { + // // self.bool_trigger.remove(reset_key); + // } + // } + // } + + // if self.player.is_some() { + // return unwrapped_state.execute( + // self.player.as_mut().unwrap(), + // &self.string_trigger, + // &self.bool_trigger, + // &self.numeric_trigger, + // &self.event_trigger, + // ); + // } else { + // return 1; + // } + // } + + // 0 + // } + + // fn verify_if_guards_are_met(&self, guard: &Guard) -> bool { + // match guard.compare_to { + // StringNumberBool::String(_) => { + // if guard.string_trigger_is_satisfied(&self.string_trigger) { + // return true; + // } + // } + // StringNumberBool::F32(_) => { + // if guard.numeric_trigger_is_satisfied(&self.numeric_trigger) { + // return true; + // } + // } + // StringNumberBool::Bool(_) => { + // if guard.bool_trigger_is_satisfied(&self.bool_trigger) { + // return true; + // } + // } + // } + + // false + // } + + // fn perform_hit_check(&self, target: &str, x: f32, y: f32) -> bool { + // // A layer name was provided, we need to check if the pointer is within the layer + // let pointer_target = target; + + // let player_ref = self.player.as_ref(); + + // if player_ref.is_some() { + // let player = player_ref.unwrap(); + // let player_read = player.try_read(); + + // match player_read { + // Ok(player) => { + // let player = &*player; + + // player.hit_check(pointer_target, x, y) + // } + // Err(_) => false, + // } + // } else { + // false + // } + // } + + // fn evaluate_transition(&self, transitions: &[Rc>], event: &Event) -> i32 { + // let mut tmp_state: i32 = -1; + // let iter = transitions.iter(); + + // for transition in iter { + // let unwrapped_transition = transition.read().unwrap(); + // let target_state = unwrapped_transition.get_target_state(); + // let transition = &*unwrapped_transition; + // let event_lock = transition.get_event(); + // let event_data = event_lock.read().unwrap(); + // let transition_event = &*event_data; + // let transition_guards = transition.get_guards(); + + // // Match the transition's event type and compare it to the received event + // match transition_event { + // InternalEvent::Bool { value } => { + // let bool_value = value; + + // if let Event::Bool { value } = event { + // if *value == *bool_value { + // // If there are guards loop over them and check if theyre verified + // if !transition_guards.is_empty() { + // for guard in transition_guards { + // if self.verify_if_guards_are_met(guard) { + // tmp_state = target_state as i32; + // } + // } + // } else { + // tmp_state = target_state as i32; + // } + // } + // } + // } + // InternalEvent::String { value } => { + // let string_value = value; + + // if let Event::String { value } = event { + // if string_value == value { + // // If there are guards loop over them and check if theyre verified + // if !transition_guards.is_empty() { + // for guard in transition_guards { + // if self.verify_if_guards_are_met(guard) { + // tmp_state = target_state as i32; + // } + // } + // } else { + // tmp_state = target_state as i32; + // } + // } + // } + // } + // InternalEvent::Numeric { value } => { + // let num_value = value; + + // if let Event::Numeric { value } = event { + // if *value == *num_value { + // // If there are guards loop over them and check if theyre verified + // if !transition_guards.is_empty() { + // for guard in transition_guards { + // if self.verify_if_guards_are_met(guard) { + // tmp_state = target_state as i32; + // } + // } + // } else { + // tmp_state = target_state as i32; + // } + // } + // } + // } + // InternalEvent::OnComplete => { + // if let Event::OnComplete = event { + // // If there are guards loop over them and check if theyre verified + // if !transition_guards.is_empty() { + // for guard in transition_guards { + // if self.verify_if_guards_are_met(guard) { + // tmp_state = target_state as i32; + // } + // } + // } else { + // tmp_state = target_state as i32; + // } + // } + // } + // // This is checking the state machine's event, not the passed event + // InternalEvent::OnPointerDown { target } => { + // if let Event::OnPointerDown { x, y } = event { + // // If there are guards loop over them and check if theyre verified + // if !transition_guards.is_empty() { + // for guard in transition_guards { + // if self.verify_if_guards_are_met(guard) { + // tmp_state = target_state as i32; + // } + // } + // } else if target.is_some() && self.player.is_some() { + // if self.perform_hit_check(target.as_ref().unwrap(), *x, *y) { + // tmp_state = target_state as i32; + // } + // } else { + // tmp_state = target_state as i32; + // } + // } + // } + // InternalEvent::OnPointerUp { target } => { + // if let Event::OnPointerUp { x, y } = event { + // // If there are guards loop over them and check if theyre verified + // if !transition_guards.is_empty() { + // for guard in transition_guards { + // if self.verify_if_guards_are_met(guard) { + // tmp_state = target_state as i32; + // } + // } + // } else if target.is_some() && self.player.is_some() { + // if self.perform_hit_check(target.as_ref().unwrap(), *x, *y) { + // tmp_state = target_state as i32; + // } + // } else { + // tmp_state = target_state as i32; + // } + // } + // } + // InternalEvent::OnPointerMove { target } => { + // if let Event::OnPointerMove { x, y } = event { + // // If there are guards loop over them and check if theyre verified + // if !transition_guards.is_empty() { + // for guard in transition_guards { + // if self.verify_if_guards_are_met(guard) { + // tmp_state = target_state as i32; + // } + // } + // } else if target.is_some() && self.player.is_some() { + // if self.perform_hit_check(target.as_ref().unwrap(), *x, *y) { + // tmp_state = target_state as i32; + // } + // } else { + // tmp_state = target_state as i32; + // } + // } + // } + // InternalEvent::OnPointerEnter { target } => { + // let mut received_event_values = Event::OnPointerEnter { x: 0.0, y: 0.0 }; + + // match event { + // Event::OnPointerEnter { x, y } => { + // received_event_values = Event::OnPointerEnter { x: *x, y: *y }; + // } + // Event::OnPointerMove { x, y } => { + // received_event_values = Event::OnPointerMove { x: *x, y: *y }; + // } + // _ => {} + // } + + // // If there are guards loop over them and check if theyre verified + // if !transition_guards.is_empty() { + // for guard in transition_guards { + // if self.verify_if_guards_are_met(guard) { + // tmp_state = target_state as i32; + // } + // } + // } else if target.is_some() && self.player.is_some() { + // if self.perform_hit_check( + // target.as_ref().unwrap(), + // received_event_values.x(), + // received_event_values.y(), + // ) { + // let current_state_name = + // if let Some(current_state) = &self.current_state { + // if let Ok(state) = current_state.read() { + // state.get_name() + // } else { + // return -1; + // } + // } else { + // return -1; + // }; + + // let target_state_name = if let Some(target_state) = + // self.states.get(target_state as usize) + // { + // if let Ok(state) = target_state.read() { + // state.get_name() + // } else { + // return 1; // Handle read lock error + // } + // } else { + // return 1; // Handle invalid index + // }; + + // // This prevent the state from transitioning to itself over and over again + // if current_state_name != target_state_name { + // tmp_state = target_state as i32; + // } + // } + // } else { + // tmp_state = target_state as i32; + // } + // } + // InternalEvent::OnPointerExit { target } => { + // let mut received_event_values = Event::OnPointerEnter { x: 0.0, y: 0.0 }; + + // match event { + // Event::OnPointerExit { x, y } => { + // received_event_values = Event::OnPointerExit { x: *x, y: *y }; + // } + // Event::OnPointerMove { x, y } => { + // received_event_values = Event::OnPointerMove { x: *x, y: *y }; + // } + // _ => {} + // } + + // // If there are guards loop over them and check if theyre verified + // if !transition_guards.is_empty() { + // for guard in transition_guards { + // if self.verify_if_guards_are_met(guard) { + // tmp_state = target_state as i32; + // } + // } + // } else if target.is_some() && self.player.is_some() { + // // Check if current state is the target state + // let current_state_name = if let Some(current_state) = &self.current_state { + // if let Ok(state) = current_state.read() { + // state.get_name() + // } else { + // return -1; + // } + // } else { + // return -1; + // }; + + // if current_state_name == *target.as_ref().unwrap() + // && !self.perform_hit_check( + // target.as_ref().unwrap(), + // received_event_values.x(), + // received_event_values.y(), + // ) + // { + // tmp_state = target_state as i32; + // } + // } else { + // // Check if coordinates are outside of the player + // let (width, height) = self.player.as_ref().unwrap().read().unwrap().size(); + + // if received_event_values.x() < 0.0 + // || received_event_values.x() > width as f32 + // || received_event_values.y() < 0.0 + // || received_event_values.y() > height as f32 + // { + // tmp_state = target_state as i32; + // } + // } + // } + // // InternalEvent::SetNumerictrigger { key: _, value: _ } => {} + // } + // } + + // tmp_state + // } + // Return codes // 0: Success // 1: Failure @@ -929,146 +954,146 @@ impl StateMachine { if self.current_state.is_none() { return 1; + } else { + return 0; } - // Only match with setNumericContext as if this is the case we return early + // Only match with setNumerictrigger as if this is the case we return early // Other event types are handled within self.evaluate_transition - if let Event::SetNumericContext { key, value } = event { - self.set_numeric_context(key, *value); + // if let Event::SetNumerictrigger { key, value } = event { + // self.set_numeric_trigger(key, *value); - let s = self.current_state.clone(); + // let s = self.current_state.clone(); - // If current state is a sync state, we need to update the frame - if let Some(state) = s { - let unwrapped_state = state.try_read(); + // // If current state is a sync state, we need to update the frame + // if let Some(state) = s { + // let unwrapped_state = state.try_read(); - if let Ok(state) = unwrapped_state { - let state_value = &*state; + // if let Ok(state) = unwrapped_state { + // let state_value = &*state; - if let State::Sync { .. } = state_value { - return self.execute_current_state(); - } - } - } + // if let State::Sync { .. } = state_value { + // return self.execute_current_state(); + // } + // } + // } - return 0; - } + // return 0; + // } // Firstly check if we have a global state within the state machine. - if self.global_state.is_some() { - let global_state = self.global_state.clone().unwrap(); - let global_state_value = global_state.try_read(); - - if global_state_value.is_ok() { - let state_value = global_state_value.unwrap(); - let tmp_state = self.evaluate_transition(state_value.get_transitions(), event); - - if tmp_state > -1 { - let next_state = self.states.get(tmp_state as usize).unwrap(); - - // Emit transtion occured event - self.observers.read().unwrap().iter().for_each(|observer| { - observer.on_transition( - (*self - .current_state - .as_ref() - .unwrap() - .read() - .unwrap() - .get_name()) - .to_string(), - (*next_state.read().unwrap().get_name()).to_string(), - ) - }); - - // Emit leaving current state event - if self.current_state.is_some() { - self.observers.read().unwrap().iter().for_each(|observer| { - observer.on_state_exit( - (*self - .current_state - .as_ref() - .unwrap() - .read() - .unwrap() - .get_name()) - .to_string(), - ); - }); - } + // if self.global_state.is_some() { + // let global_state = self.global_state.clone().unwrap(); + // let global_state_value = global_state.try_read(); + + // if global_state_value.is_ok() { + // let state_value = global_state_value.unwrap(); + // let tmp_state = self.evaluate_transition(state_value.get_transitions(), event); + + // if tmp_state > -1 { + // let next_state = self.states.get(tmp_state as usize).unwrap(); + + // // Emit transtion occured event + // self.observers.read().unwrap().iter().for_each(|observer| { + // observer.on_transition( + // (*self + // .current_state + // .as_ref() + // .unwrap() + // .read() + // .unwrap() + // .get_name()) + // .to_string(), + // (*next_state.read().unwrap().get_name()).to_string(), + // ) + // }); + + // // Emit leaving current state event + // if self.current_state.is_some() { + // self.observers.read().unwrap().iter().for_each(|observer| { + // observer.on_state_exit( + // (*self + // .current_state + // .as_ref() + // .unwrap() + // .read() + // .unwrap() + // .get_name()) + // .to_string(), + // ); + // }); + // } - self.current_state = Some(next_state.clone()); + // self.current_state = Some(next_state.clone()); - // Emit entering a new state - self.observers.read().unwrap().iter().for_each(|observer| { - observer - .on_state_entered((*next_state.read().unwrap().get_name()).to_string()); - }); + // // Emit entering a new state + // self.observers.read().unwrap().iter().for_each(|observer| { + // observer + // .on_state_entered((*next_state.read().unwrap().get_name()).to_string()); + // }); - return self.execute_current_state(); - } - } - } - - // Otherwise we evaluate the transitions of the current state - let curr_state = self.current_state.clone().unwrap(); - - let state_value_result = curr_state.read(); - - if state_value_result.is_ok() { - let state_value = state_value_result.unwrap(); - - let tmp_state = self.evaluate_transition(state_value.get_transitions(), event); - - if tmp_state > -1 { - let next_state = self.states.get(tmp_state as usize).unwrap(); - - // Emit transtion occured event - self.observers.read().unwrap().iter().for_each(|observer| { - observer.on_transition( - (*self - .current_state - .as_ref() - .unwrap() - .read() - .unwrap() - .get_name()) - .to_string(), - (*next_state.read().unwrap().get_name()).to_string(), - ) - }); - - // Emit leaving current state event - if self.current_state.is_some() { - self.observers.read().unwrap().iter().for_each(|observer| { - observer.on_state_exit( - (*self - .current_state - .as_ref() - .unwrap() - .read() - .unwrap() - .get_name()) - .to_string(), - ); - }); - } + // return self.execute_current_state(); + // } + // } + // } - self.current_state = Some(next_state.clone()); + // // Otherwise we evaluate the transitions of the current state + // let curr_state = self.current_state.clone().unwrap(); + + // let state_value_result = curr_state.read(); + + // if state_value_result.is_ok() { + // let state_value = state_value_result.unwrap(); + + // let tmp_state = self.evaluate_transition(state_value.get_transitions(), event); + + // if tmp_state > -1 { + // let next_state = self.states.get(tmp_state as usize).unwrap(); + + // // Emit transtion occured event + // self.observers.read().unwrap().iter().for_each(|observer| { + // observer.on_transition( + // (*self + // .current_state + // .as_ref() + // .unwrap() + // .read() + // .unwrap() + // .get_name()) + // .to_string(), + // (*next_state.read().unwrap().get_name()).to_string(), + // ) + // }); + + // // Emit leaving current state event + // if self.current_state.is_some() { + // self.observers.read().unwrap().iter().for_each(|observer| { + // observer.on_state_exit( + // (*self + // .current_state + // .as_ref() + // .unwrap() + // .read() + // .unwrap() + // .get_name()) + // .to_string(), + // ); + // }); + // } - // Emit entering a new state - self.observers.read().unwrap().iter().for_each(|observer| { - observer.on_state_entered((*next_state.read().unwrap().get_name()).to_string()); - }); + // self.current_state = Some(next_state.clone()); - return self.execute_current_state(); - } - } + // // Emit entering a new state + // self.observers.read().unwrap().iter().for_each(|observer| { + // observer.on_state_entered((*next_state.read().unwrap().get_name()).to_string()); + // }); - 1 + // return self.execute_current_state(); + // } + // } } - pub fn remove_state(&mut self, state: Arc>) { + pub fn remove_state(&mut self, state: Rc>) { let _ = state; // self.states.remove(state); } diff --git a/dotlottie-rs/src/state_machine/parser/mod.rs b/dotlottie-rs/src/state_machine/parser/mod.rs index 0f60992f..f4318b14 100644 --- a/dotlottie-rs/src/state_machine/parser/mod.rs +++ b/dotlottie-rs/src/state_machine/parser/mod.rs @@ -2,7 +2,7 @@ use serde::Deserialize; use crate::errors::StateMachineError; -#[derive(Debug, Deserialize, PartialEq)] +#[derive(Debug, Clone, Deserialize, PartialEq)] #[serde(untagged)] pub enum StringNumberBool { String(String), @@ -10,7 +10,7 @@ pub enum StringNumberBool { Bool(bool), } -#[derive(Deserialize, Debug, PartialEq)] +#[derive(Clone, Copy, Deserialize, Debug, PartialEq)] pub enum TransitionGuardConditionType { GreaterThan, GreaterThanOrEqual, @@ -66,7 +66,7 @@ pub enum ActionJson { layer_name: String, property_index: u32, var_name: String, - value: StringNumberBool, + value: f32, }, SetTheme { theme_id: String, @@ -106,7 +106,6 @@ pub enum StateJson { transitions: Vec, entry_actions: Option>, exit_actions: Option>, - reset_context: Option, }, } @@ -175,27 +174,27 @@ pub enum TransitionJson { pub enum ListenerJson { PointerUp { layer_name: Option, - action: ActionJson, + actions: Vec, }, PointerDown { layer_name: Option, - action: ActionJson, + actions: Vec, }, PointerEnter { layer_name: Option, - action: ActionJson, + actions: Vec, }, PointerExit { layer_name: Option, - action: ActionJson, + actions: Vec, }, PointerMove { layer_name: Option, - action: ActionJson, + actions: Vec, }, OnComplete { state_name: Option, - action: ActionJson, + actions: Vec, }, } @@ -212,8 +211,8 @@ pub enum TriggerJson { pub struct StateMachineJson { pub descriptor: DescriptorJson, pub states: Vec, - pub listeners: Vec, - pub triggers: Vec, + pub listeners: Option>, + pub triggers: Option>, } pub fn state_machine_parse(json: &str) -> Result { diff --git a/dotlottie-rs/src/state_machine/states/mod.rs b/dotlottie-rs/src/state_machine/states/mod.rs index 306e109d..59307267 100644 --- a/dotlottie-rs/src/state_machine/states/mod.rs +++ b/dotlottie-rs/src/state_machine/states/mod.rs @@ -1,36 +1,24 @@ -use std::{ - collections::HashMap, - rc::Rc, - sync::{Arc, RwLock}, -}; +use std::{collections::HashMap, rc::Rc, sync::RwLock}; use crate::{Config, DotLottiePlayerContainer}; -use super::transitions::Transition; +use super::{actions::Action, transitions::Transition}; pub trait StateTrait { fn execute( &self, player: &Rc>, - string_context: &HashMap, - bool_context: &HashMap, - numeric_context: &HashMap, + string_trigger: &HashMap, + bool_trigger: &HashMap, + numeric_trigger: &HashMap, + event_trigger: &HashMap, ) -> i32; - fn get_reset_context_key(&self) -> &String; fn get_animation_id(&self) -> Option<&String>; - fn get_transitions(&self) -> &Vec>>; - fn add_transition(&mut self, transition: Transition); + fn get_transitions(&self) -> &Vec; + fn add_transition(&mut self, transition: &Transition); fn get_config(&self) -> Option<&Config>; fn get_name(&self) -> String; fn get_type(&self) -> String; - // fn set_reset_context(&mut self, reset_context: bool); - - // fn add_entry_action(&mut self, action: String); - // fn add_exit_action(&mut self, action: String); - // fn remove_entry_action(&mut self, action: String); - // fn remove_exit_action(&mut self, action: String); - // fn get_entry_actions(&self) -> Vec; - // fn get_exit_actions(&self) -> Vec; } #[derive(Clone, Debug)] @@ -38,35 +26,19 @@ pub enum State { Playback { name: String, config: Config, - reset_context: String, animation_id: String, - transitions: Vec>>, - }, - Sync { - name: String, - config: Config, - frame_context_key: String, - reset_context: String, - animation_id: String, - transitions: Vec>>, + transitions: Vec, + entry_actions: Option>, + exit_actions: Option>, }, Global { name: String, - reset_context: String, - transitions: Vec>>, + transitions: Vec, + entry_actions: Option>, + exit_actions: Option>, }, } -impl State { - pub fn as_str(&self) -> &str { - match self { - State::Playback { .. } => "Playback", - State::Sync { .. } => "Sync", - State::Global { .. } => "Global", - } - } -} - impl StateTrait for State { // Return codes // 0: Success @@ -79,7 +51,8 @@ impl StateTrait for State { player: &Rc>, _: &HashMap, _: &HashMap, - numeric_context: &HashMap, + _: &HashMap, + _: &HashMap, ) -> i32 { match self { State::Playback { @@ -113,80 +86,37 @@ impl StateTrait for State { return 1; } } - State::Sync { - config, - frame_context_key, - animation_id, - .. - } => { - if let Ok(player_read) = player.try_read() { - let size = player_read.size(); - let frame = numeric_context.get(frame_context_key); - - // Tell player to load new animation - if !animation_id.is_empty() { - player_read.load_animation(animation_id, size.0, size.1); - } - - player_read.set_config(config.clone()); - if let Some(frame_value) = frame { - let ret = player_read.set_frame(*frame_value); - - if ret { - return 4; - } - } - } - } State::Global { .. } => {} } 0 } - fn get_reset_context_key(&self) -> &String { - match self { - State::Playback { reset_context, .. } => reset_context, - State::Sync { reset_context, .. } => reset_context, - State::Global { reset_context, .. } => reset_context, - } - } - fn get_animation_id(&self) -> Option<&String> { match self { State::Playback { animation_id, .. } => Some(animation_id), - State::Sync { animation_id, .. } => Some(animation_id), State::Global { .. } => None, } } - fn get_transitions(&self) -> &Vec>> { + fn get_transitions(&self) -> &Vec { match self { State::Playback { transitions, .. } => transitions, - State::Sync { transitions, .. } => transitions, State::Global { transitions, .. } => transitions, } } - fn add_transition(&mut self, transition: Transition) { + fn add_transition(&mut self, transition: &Transition) { match self { - State::Playback { transitions, .. } => { - transitions.push(Arc::new(RwLock::new(transition))); - } - State::Sync { transitions, .. } => { - transitions.push(Arc::new(RwLock::new(transition))); - } - State::Global { transitions, .. } => { - transitions.push(Arc::new(RwLock::new(transition))); - } + State::Playback { transitions, .. } => transitions.push(transition.clone()), + State::Global { transitions, .. } => transitions.push(transition.clone()), } } fn get_config(&self) -> Option<&Config> { match self { State::Playback { config, .. } => Some(config), - State::Sync { .. } => None, State::Global { .. } => None, } } @@ -194,7 +124,6 @@ impl StateTrait for State { fn get_name(&self) -> String { match self { State::Playback { name, .. } => name.to_string(), - State::Sync { name, .. } => name.to_string(), State::Global { name, .. } => name.to_string(), } } @@ -202,12 +131,7 @@ impl StateTrait for State { fn get_type(&self) -> String { match self { State::Playback { .. } => "PlaybackState".to_string(), - State::Sync { .. } => "SyncState".to_string(), State::Global { .. } => "GlobalState".to_string(), } } - - // fn set_reset_context(&mut self, reset_context: bool) { - // todo!() - // } } diff --git a/dotlottie-rs/src/state_machine/transitions/guard.rs b/dotlottie-rs/src/state_machine/transitions/guard.rs index 463e973b..3f821a6c 100644 --- a/dotlottie-rs/src/state_machine/transitions/guard.rs +++ b/dotlottie-rs/src/state_machine/transitions/guard.rs @@ -2,28 +2,28 @@ use std::collections::HashMap; use crate::parser::{StringNumberBool, TransitionGuardConditionType}; -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Guard { - pub context_key: String, + pub trigger_name: String, pub condition_type: TransitionGuardConditionType, pub compare_to: StringNumberBool, } impl Guard { pub fn new( - context_key: String, + trigger_name: String, condition_type: TransitionGuardConditionType, compare_to: StringNumberBool, ) -> Self { Self { - context_key, + trigger_name, condition_type, compare_to, } } - pub fn string_context_is_satisfied(&self, context: &HashMap) -> bool { - let context_value = context.get(&self.context_key); + pub fn string_trigger_is_satisfied(&self, context: &HashMap) -> bool { + let context_value = context.get(&self.trigger_name); if context_value.is_none() { return false; @@ -42,8 +42,8 @@ impl Guard { false } - pub fn bool_context_is_satisfied(&self, context: &HashMap) -> bool { - let context_value = context.get(&self.context_key); + pub fn bool_trigger_is_satisfied(&self, context: &HashMap) -> bool { + let context_value = context.get(&self.trigger_name); if context_value.is_none() { return false; @@ -64,8 +64,8 @@ impl Guard { false } - pub fn numeric_context_is_satisfied(&self, context: &HashMap) -> bool { - let context_value = context.get(&self.context_key); + pub fn numeric_trigger_is_satisfied(&self, context: &HashMap) -> bool { + let context_value = context.get(&self.trigger_name); if context_value.is_none() { return false; diff --git a/dotlottie-rs/src/state_machine/transitions/mod.rs b/dotlottie-rs/src/state_machine/transitions/mod.rs index 1ff26253..cd194c3c 100644 --- a/dotlottie-rs/src/state_machine/transitions/mod.rs +++ b/dotlottie-rs/src/state_machine/transitions/mod.rs @@ -1,55 +1,23 @@ pub mod guard; - -use std::sync::{Arc, RwLock}; - -use crate::state_machine::events::InternalEvent; - use self::guard::Guard; pub trait TransitionTrait { - fn set_target_state(&mut self, target_state: u32); - fn set_event(&mut self, event: Arc>); - - fn get_target_state(&self) -> u32; + fn get_target_state(&self) -> &str; fn get_guards(&self) -> &Vec; - fn get_event(&self) -> Arc>; } -#[derive(Debug)] +#[derive(Clone, Debug)] pub enum Transition { Transition { - target_state: u32, - event: Arc>, + target_state: String, guards: Vec, }, } impl TransitionTrait for Transition { - fn set_target_state(&mut self, state: u32) { - match self { - Transition::Transition { target_state, .. } => { - *target_state = state; - } - } - } - - fn get_target_state(&self) -> u32 { - match self { - Transition::Transition { target_state, .. } => *target_state, - } - } - - fn set_event(&mut self, ev: Arc>) { - match self { - Transition::Transition { event, .. } => { - *event = ev; - } - } - } - - fn get_event(&self) -> Arc> { + fn get_target_state(&self) -> &str { match self { - Transition::Transition { event, .. } => event.clone(), + Transition::Transition { target_state, .. } => target_state, } }