use crate::dynamic_data::choices::{MoveChoice, TurnChoice}; use crate::dynamic_data::models::battle::Battle; use crate::dynamic_data::models::damage_source::DamageSource; use crate::dynamic_data::models::executing_move::ExecutingMove; use crate::dynamic_data::models::pokemon::Pokemon; use crate::static_data::moves::secondary_effect::EffectParameter; use crate::static_data::{Item, Statistic}; use crate::StringKey; use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard}; use std::any::Any; use std::fmt::{Debug, Formatter}; use std::ops::Deref; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::Arc; pub trait Script: Send + Sync { fn name(&self) -> &StringKey; fn get_marked_for_deletion(&self) -> &AtomicBool; fn mark_for_deletion(&self) { self.get_marked_for_deletion().store(true, Ordering::SeqCst); } fn is_marked_for_deletion(&self) -> bool { self.get_marked_for_deletion().load(Ordering::SeqCst) } fn get_suppressed_count(&self) -> &AtomicUsize; fn is_suppressed(&self) -> bool { self.get_suppressed_count().load(Ordering::SeqCst) > 0 } fn add_suppression(&self) { self.get_suppressed_count().fetch_add(1, Ordering::SeqCst); } fn remove_suppression(&self) { self.get_suppressed_count().fetch_sub(1, Ordering::SeqCst); } fn stack(&self) {} fn on_remove(&self) {} fn on_initialize(&self, _pars: &[EffectParameter]) {} fn on_before_turn(&self, _choice: &TurnChoice) {} fn change_speed(&self, _choice: &TurnChoice, _speed: &mut u32) {} fn change_priority(&self, _choice: &TurnChoice, _priority: &mut i8) {} fn change_move(&self, _choice: &MoveChoice, _move_name: &mut StringKey) {} fn change_number_of_hits(&self, _choice: &MoveChoice, _number_of_hits: &mut u8) {} fn prevent_move(&self, _move: &ExecutingMove, _prevent: &mut bool) {} fn fail_move(&self, _move: &ExecutingMove, _fail: &mut bool) {} fn stop_before_move(&self, _move: &ExecutingMove, _stop: &mut bool) {} fn on_before_move(&self, _move: &ExecutingMove) {} fn fail_incoming_move(&self, _move: &ExecutingMove, _target: &Arc, _fail: &mut bool) {} fn is_invulnerable(&self, _move: &ExecutingMove, _target: &Arc, _invulnerable: &mut bool) {} fn on_move_miss(&self, _move: &ExecutingMove, _target: &Arc) {} fn change_move_type(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _move_type: &mut u8) {} fn change_effectiveness(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _effectiveness: &mut f32) {} fn block_critical(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _block_critical: &mut bool) {} fn block_incoming_critical( &self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _block_critical: &mut bool, ) { } fn change_critical_stage(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _stage: &mut u8) {} fn change_critical_modifier(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _modifier: &mut f32) {} fn change_stab_modifier(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _modifier: &mut f32) {} fn change_base_power(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _base_power: &mut u8) {} fn change_damage_stats_user( &self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _stats_user: &mut Arc, ) { } fn bypass_defensive_stat_boost(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _bypass: &mut bool) { } fn bypass_offensive_stat_boost(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _bypass: &mut bool) { } fn change_offensive_stat_value(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _amount: &mut u32) {} fn change_defensive_stat_value(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _amount: &mut u32) {} fn change_damage_stat_modifier( &self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _modifier: &mut f32, ) { } fn change_damage_modifier(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _modifier: &mut f32) {} fn change_damage(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _damage: &mut u32) {} fn change_incoming_damage(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _damage: &mut u32) {} fn on_incoming_hit(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8) {} fn on_opponent_faints(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8) {} fn prevent_stat_boost_change( &self, _target: &Pokemon, _stat: Statistic, _amount: i8, _self_inflicted: bool, _prevent: &mut bool, ) { } fn change_stat_boost_change(&self, _target: &Pokemon, _stat: Statistic, _self_inflicted: bool, _amount: *mut i8) {} fn prevent_secondary_effect(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _prevent: &mut bool) {} fn change_effect_chance(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _chance: &mut f32) {} fn change_incoming_effect_chance( &self, _move: &ExecutingMove, _target: &Arc, _hit: u8, _chance: &mut f32, ) { } fn on_secondary_effect(&self, _move: &ExecutingMove, _target: &Arc, _hit: u8) {} fn on_after_hits(&self, _move: &ExecutingMove, _target: &Arc) {} fn prevent_self_switch(&self, _choice: &TurnChoice, _prevent: &mut bool) {} fn prevent_opponent_switch(&self, _choice: &TurnChoice, _prevent: &mut bool) {} fn on_fail(&self, _target: &Pokemon) {} fn on_opponent_fail(&self, _target: &Pokemon) {} fn prevent_self_run_away(&self, _choice: &TurnChoice, _prevent: &mut bool) {} fn prevent_opponent_run_away(&self, _choice: &TurnChoice, _prevent: &mut bool) {} fn on_end_turn(&self) {} fn on_damage(&self, _pokemon: &Pokemon, _source: DamageSource, _old_health: u32, _new_health: u32) {} fn on_faint(&self, _pokemon: &Pokemon, _source: DamageSource) {} fn on_switch_in(&self, _pokemon: &Pokemon) {} fn on_after_held_item_consume(&self, _pokemon: &Pokemon, _item: &Item) {} fn change_experience_gained(&self, _fainted_mon: &Pokemon, _winning_mon: &Pokemon, _amount: &mut u32) {} fn share_experience(&self, _fainted_mon: &Pokemon, _winning_mon: &Pokemon, _shares: &mut bool) {} fn block_weather(&self, _battle: &Battle, _blocked: &mut bool) {} fn change_capture_rate_bonus(&self, _target: &Pokemon, _pokeball: &Item, _modifier: &mut u8) {} fn as_any(&self) -> &dyn Any; fn as_any_mut(&mut self) -> &mut dyn Any; } impl Debug for dyn Script { fn fmt(&self, _f: &mut Formatter<'_>) -> std::fmt::Result { Ok(()) } } type ScriptHolder = Arc>>>; #[derive(Default, Debug, Clone)] pub struct ScriptContainer { script: ScriptHolder, } impl ScriptContainer { pub fn new(script: Arc) -> ScriptContainer { Self { script: Arc::new(RwLock::new(Some(script))), } } pub fn get(&self) -> Option<&ScriptHolder> { if self.script.read().is_some_and(|a| a.is_marked_for_deletion()) { if !self.script.is_locked() { self.script.write().take(); } None } else { Some(&self.script) } } pub fn set(&self, script: Arc) -> Arc { if let Some(v) = self.script.read().deref() { v.mark_for_deletion(); } if self.script.is_locked() { let holder = self.script.clone(); let new = script.clone(); std::thread::spawn(move || { holder.write().replace(new); }); } else { self.script.write().replace(script.clone()); }; script } pub fn clear(&self) { if let Some(v) = self.script.read().deref() { v.mark_for_deletion(); } if !self.script.is_locked() { self.script.write().take(); } } pub fn arc(&self) -> &ScriptHolder { &self.script } pub fn get_as(&self) -> MappedRwLockReadGuard { RwLockReadGuard::map(self.script.read(), |a| unsafe { let ptr = a.as_ref().as_ref().unwrap().as_ref() as *const dyn Script; let any = ptr.as_ref().unwrap().as_any(); any.downcast_ref::().unwrap() }) } } impl From for ScriptContainer { fn from(a: ScriptHolder) -> Self { Self { script: a } } } #[cfg(test)] mod tests { use super::*; use std::sync::atomic::{AtomicBool, AtomicPtr}; use std::thread::sleep; use std::time::Duration; pub struct TestScript { name: StringKey, container: AtomicPtr, suppressed_count: AtomicUsize, marked_for_deletion: AtomicBool, } impl TestScript { fn new() -> Self { Self { name: StringKey::new("test"), container: AtomicPtr::::default(), suppressed_count: AtomicUsize::new(0), marked_for_deletion: Default::default(), } } } unsafe impl Sync for TestScript {} unsafe impl Send for TestScript {} impl Script for TestScript { fn name(&self) -> &StringKey { &self.name } fn get_marked_for_deletion(&self) -> &AtomicBool { &self.marked_for_deletion } fn get_suppressed_count(&self) -> &AtomicUsize { &self.suppressed_count } fn on_initialize(&self, _pars: &[EffectParameter]) { unsafe { self.container.load(Ordering::Relaxed).as_ref().unwrap().clear() } } fn as_any(&self) -> &dyn Any { self } fn as_any_mut(&mut self) -> &mut dyn Any { self } } // Removing yourself while active should be completely valid for a script. Consider for example // a status effect such as sleep clearing itself in a script hook. As the script is actively // being read from at that time, we should wait until the script hook is finished reading, and // then dispose of the script. #[test] fn clear_self_while_active() { let script = Arc::new(TestScript::new()); let mut container = ScriptContainer::new(script); let c = &mut container as *mut ScriptContainer; let w = container.script.read(); let script: &TestScript = w.as_ref().unwrap().as_any().downcast_ref().unwrap(); script.container.store(c, Ordering::SeqCst); drop(w); // Initialize with the script being taken as read lock. This prevents the script from actually // removing itself, as it's still doing things. container.script.read().as_ref().unwrap().on_initialize(&[]); // If we now try and get the script, it will be none the first time. This has the side effect // of actually disposing of the script. assert!(container.get().is_none()); // As we've properly disposed of the script now, the next fetch will return something. assert!(container.get().is_some()); // But the value it returns shows that it is empty. assert!(container.get().unwrap().read().is_none()); } pub struct ReplaceTestScript { name: StringKey, container: AtomicPtr, suppressed_count: AtomicUsize, marked_for_deletion: AtomicBool, } impl ReplaceTestScript { fn new(name: StringKey) -> Self { Self { name, container: AtomicPtr::::default(), suppressed_count: AtomicUsize::new(0), marked_for_deletion: Default::default(), } } fn test(&self, script: Arc) { unsafe { self.container.load(Ordering::Relaxed).as_ref().unwrap().set(script); } } } unsafe impl Sync for ReplaceTestScript {} unsafe impl Send for ReplaceTestScript {} impl Script for ReplaceTestScript { fn name(&self) -> &StringKey { &self.name } fn get_marked_for_deletion(&self) -> &AtomicBool { &self.marked_for_deletion } fn get_suppressed_count(&self) -> &AtomicUsize { &self.suppressed_count } fn as_any(&self) -> &dyn Any { self } fn as_any_mut(&mut self) -> &mut dyn Any { self } } #[test] fn replace_self_while_active() { let script = Arc::new(ReplaceTestScript::new("script1".into())); let mut container = ScriptContainer::new(script); let c = &mut container as *mut ScriptContainer; let w = container.script.read(); let script: &ReplaceTestScript = w.as_ref().unwrap().as_any().downcast_ref().unwrap(); script.container.store(c, Ordering::SeqCst); drop(w); let script2 = Arc::new(ReplaceTestScript::new("script2".into())); container .script .read() .as_ref() .unwrap() .as_any() .downcast_ref::() .unwrap() .test(script2); sleep(Duration::from_micros(500)); assert_eq!( container.script.read().as_ref().unwrap().name(), &StringKey::new("script2") ); } }