A lot more work on WASM script execution
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2022-09-07 18:01:26 +02:00
parent f9761f61da
commit b1890681a1
102 changed files with 748 additions and 202 deletions

50
src/dynamic_data/choices.rs Normal file → Executable file
View File

@@ -1,4 +1,4 @@
use std::cmp::Ordering;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use parking_lot::RwLock;
@@ -20,7 +20,7 @@ struct CommonChoiceData {
random_value: u32,
/// Whether or not the choice has failed. A failed choice will stop running, and execute special
/// fail handling during turn execution.
has_failed: bool,
has_failed: AtomicBool,
/// The data we can use to retrieve scripts that are affecting this choice. This will be written
/// to once: when we need to initialize it. After that, this is only used to read. To prevent a
/// read while we're writing to it, this is a RwLock.
@@ -85,13 +85,13 @@ impl TurnChoice {
/// Gets whether or not the choice has failed. If we notice this when we execute the choice, we
/// will not execute it.
pub fn has_failed(&self) -> bool {
self.choice_data().has_failed
self.choice_data().has_failed.load(Ordering::SeqCst)
}
/// Fails the choice. This will prevent it from executing and run a specific fail handling during
/// execution. Note that this can not be undone.
pub fn fail(&mut self) {
self.choice_data_mut().has_failed = true
pub fn fail(&self) {
self.choice_data().has_failed.store(true, Ordering::SeqCst)
}
/// The random value of a turn choice gets set during the start of a choice, and is used for tie
@@ -188,7 +188,7 @@ impl MoveChoice {
user,
speed: 0,
random_value: 0,
has_failed: false,
has_failed: Default::default(),
script_source_data: Default::default(),
}),
}
@@ -259,7 +259,7 @@ impl ItemChoice {
user,
speed: 0,
random_value: 0,
has_failed: false,
has_failed: Default::default(),
script_source_data: Default::default(),
}),
}
@@ -297,7 +297,7 @@ impl SwitchChoice {
user,
speed: 0,
random_value: 0,
has_failed: false,
has_failed: Default::default(),
script_source_data: Default::default(),
}),
}
@@ -335,7 +335,7 @@ impl FleeChoice {
user,
speed: 0,
random_value: 0,
has_failed: false,
has_failed: Default::default(),
script_source_data: Default::default(),
}),
}
@@ -373,7 +373,7 @@ impl PassChoice {
user,
speed: 0,
random_value: 0,
has_failed: false,
has_failed: Default::default(),
script_source_data: Default::default(),
}),
}
@@ -405,68 +405,68 @@ impl PartialEq<Self> for TurnChoice {
impl Eq for TurnChoice {}
impl PartialOrd for TurnChoice {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for TurnChoice {
fn cmp(&self, other: &Self) -> Ordering {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
match self {
TurnChoice::Move(data) => {
if let TurnChoice::Move(other_data) = other {
let priority_compare = data.priority.cmp(&other_data.priority);
if priority_compare != Ordering::Equal {
if priority_compare != std::cmp::Ordering::Equal {
return priority_compare;
}
let speed_compare = self.speed().cmp(&other.speed());
if speed_compare != Ordering::Equal {
if speed_compare != std::cmp::Ordering::Equal {
return speed_compare;
}
return self.random_value().cmp(&other.random_value());
}
Ordering::Greater
std::cmp::Ordering::Greater
}
TurnChoice::Item { .. } => {
if let TurnChoice::Move { .. } = other {
return Ordering::Less;
return std::cmp::Ordering::Less;
}
if let TurnChoice::Item { .. } = other {
let speed_compare = self.speed().cmp(&other.speed());
if speed_compare != Ordering::Equal {
if speed_compare != std::cmp::Ordering::Equal {
return speed_compare;
}
return self.random_value().cmp(&other.random_value());
}
Ordering::Greater
std::cmp::Ordering::Greater
}
TurnChoice::Switch { .. } => {
if let TurnChoice::Move { .. } = other {
return Ordering::Less;
return std::cmp::Ordering::Less;
}
if let TurnChoice::Item { .. } = other {
return Ordering::Less;
return std::cmp::Ordering::Less;
}
if let TurnChoice::Switch { .. } = other {
let speed_compare = self.speed().cmp(&other.speed());
if speed_compare != Ordering::Equal {
if speed_compare != std::cmp::Ordering::Equal {
return speed_compare;
}
return self.random_value().cmp(&other.random_value());
}
Ordering::Greater
std::cmp::Ordering::Greater
}
TurnChoice::Flee { .. } => {
if let TurnChoice::Flee { .. } = other {
let speed_compare = self.speed().cmp(&other.speed());
if speed_compare != Ordering::Equal {
if speed_compare != std::cmp::Ordering::Equal {
return speed_compare;
}
return self.random_value().cmp(&other.random_value());
}
Ordering::Less
std::cmp::Ordering::Less
}
TurnChoice::Pass(..) => Ordering::Less,
TurnChoice::Pass(..) => std::cmp::Ordering::Less,
}
}
}

0
src/dynamic_data/event_hooks.rs Normal file → Executable file
View File

41
src/dynamic_data/flow/choice_queue.rs Normal file → Executable file
View File

@@ -1,5 +1,7 @@
use crate::dynamic_data::choices::TurnChoice;
use crate::dynamic_data::Pokemon;
use parking_lot::lock_api::MappedRwLockReadGuard;
use parking_lot::{RawRwLock, RwLock, RwLockReadGuard};
/// The ChoiceQueue is used to run choices one by one.
///
@@ -8,10 +10,11 @@ use crate::dynamic_data::Pokemon;
/// helper functions to change the turn order while doing the execution. This is needed, as several
/// moves in Pokemon actively mess with this order.
#[derive(Debug)]
#[cfg_attr(feature = "wasm", derive(unique_type_id_derive::UniqueTypeId))]
pub struct ChoiceQueue {
/// Our storage of turn choices. Starts out completely filled, then slowly empties as turns get
/// executed.
queue: Vec<Option<TurnChoice>>,
queue: RwLock<Vec<Option<TurnChoice>>>,
/// The current index of the turn we need to execute next.
current: usize,
}
@@ -19,42 +22,47 @@ pub struct ChoiceQueue {
impl ChoiceQueue {
/// Initializes a ChoiceQueue. We expect the given queue to already be sorted here.
pub(crate) fn new(queue: Vec<Option<TurnChoice>>) -> Self {
Self { queue, current: 0 }
Self {
queue: RwLock::new(queue),
current: 0,
}
}
/// Dequeues the next turn choice to be executed. This gives ownership to the callee, and replaces
/// our own reference to the turn choice with an empty spot. It also increments the current position
/// by one.
pub fn dequeue(&mut self) -> TurnChoice {
let c = self.queue[self.current].take();
let c = self.queue.write()[self.current].take();
self.current += 1;
c.unwrap()
}
/// This reads what the next choice to execute will be, without modifying state.
pub fn peek(&self) -> &TurnChoice {
self.queue[self.current].as_ref().unwrap()
pub fn peek(&self) -> MappedRwLockReadGuard<'_, RawRwLock, TurnChoice> {
let read_lock = self.queue.read();
RwLockReadGuard::map(read_lock, |a| a[self.current].as_ref().unwrap())
}
/// Check if we have any choices remaining.
pub fn has_next(&self) -> bool {
self.current < self.queue.len()
self.current < self.queue.read().len()
}
/// This resorts the yet to be executed choices. This can be useful for dealing with situations
/// such as Pokemon changing forms just after the very start of a turn, when turn order has
/// technically already been decided.
pub fn resort(&mut self) {
let len = self.queue.len();
self.queue[self.current..len].sort_unstable_by(|a, b| b.cmp(a));
let len = self.queue.read().len();
self.queue.write()[self.current..len].sort_unstable_by(|a, b| b.cmp(a));
}
/// This moves the choice of a specific Pokemon up to the next choice to be executed.
pub fn move_pokemon_choice_next(&mut self, pokemon: &Pokemon) -> bool {
pub fn move_pokemon_choice_next(&self, pokemon: &Pokemon) -> bool {
let mut queue_lock = self.queue.write();
let mut desired_index = None;
// Find the index for the choice we want to move up.
for index in self.current..self.queue.len() {
if let Some(choice) = &self.queue[index] {
for index in self.current..queue_lock.len() {
if let Some(choice) = &queue_lock[index] {
if std::ptr::eq(choice.user().as_ref(), pokemon) {
desired_index = Some(index);
break;
@@ -72,19 +80,20 @@ impl ChoiceQueue {
}
// Take the choice we want to move forward out of it's place.
let choice = self.queue[desired_index].take().unwrap();
let choice = queue_lock[desired_index].take().unwrap();
// Iterate backwards from the spot before the choice we want to move up, push them all back
// by 1 spot.
for index in (desired_index - 1)..self.current {
self.queue.swap(index, index + 1);
queue_lock.swap(index, index + 1);
}
// Place the choice that needs to be next in the next to be executed position.
let _ = self.queue[self.current].insert(choice);
let _ = queue_lock[self.current].insert(choice);
true
}
/// Internal helper function to be easily able to iterate over the yet to be executed choices.
pub(crate) fn get_queue(&self) -> &[Option<TurnChoice>] {
&self.queue[self.current..self.queue.len()]
pub(crate) fn get_queue(&self) -> MappedRwLockReadGuard<'_, RawRwLock, [Option<TurnChoice>]> {
let read_lock = self.queue.read();
RwLockReadGuard::map(read_lock, |a| &a[self.current..self.queue.read().len()])
}
}

0
src/dynamic_data/flow/mod.rs Normal file → Executable file
View File

0
src/dynamic_data/flow/target_resolver.rs Normal file → Executable file
View File

0
src/dynamic_data/flow/turn_runner.rs Normal file → Executable file
View File

0
src/dynamic_data/libraries/battle_stat_calculator.rs Normal file → Executable file
View File

0
src/dynamic_data/libraries/damage_library.rs Normal file → Executable file
View File

4
src/dynamic_data/libraries/dynamic_library.rs Normal file → Executable file
View File

@@ -5,8 +5,8 @@ use crate::dynamic_data::libraries::battle_stat_calculator::BattleStatCalculator
use crate::dynamic_data::libraries::damage_library::DamageLibrary;
use crate::dynamic_data::libraries::misc_library::MiscLibrary;
use crate::dynamic_data::libraries::script_resolver::ScriptCategory;
use crate::dynamic_data::Script;
use crate::dynamic_data::{ItemScript, ScriptResolver};
use crate::dynamic_data::{Script, ScriptOwnerData};
use crate::static_data::Item;
use crate::static_data::StaticData;
use crate::{PkmnResult, StringKey};
@@ -75,7 +75,7 @@ impl DynamicLibrary {
/// can be created with this combination, returns None.
pub fn load_script(
&self,
owner: *const u8,
owner: ScriptOwnerData,
_category: ScriptCategory,
_key: &StringKey,
) -> PkmnResult<Option<Arc<dyn Script>>> {

0
src/dynamic_data/libraries/misc_library.rs Normal file → Executable file
View File

0
src/dynamic_data/libraries/mod.rs Normal file → Executable file
View File

6
src/dynamic_data/libraries/script_resolver.rs Normal file → Executable file
View File

@@ -1,7 +1,7 @@
use std::fmt::Debug;
use std::sync::Arc;
use crate::dynamic_data::{ItemScript, Script};
use crate::dynamic_data::{ItemScript, Script, ScriptOwnerData};
use crate::static_data::Item;
use crate::{PkmnResult, StringKey};
@@ -13,7 +13,7 @@ pub trait ScriptResolver: Debug {
/// can be created with this combination, returns None.
fn load_script(
&self,
owner: *const u8,
owner: ScriptOwnerData,
category: ScriptCategory,
script_key: &StringKey,
) -> PkmnResult<Option<Arc<dyn Script>>>;
@@ -58,7 +58,7 @@ pub struct EmptyScriptResolver {}
impl ScriptResolver for EmptyScriptResolver {
fn load_script(
&self,
_owner: *const u8,
_owner: ScriptOwnerData,
_category: ScriptCategory,
_script_key: &StringKey,
) -> PkmnResult<Option<Arc<dyn Script>>> {

0
src/dynamic_data/mod.rs Normal file → Executable file
View File

5
src/dynamic_data/models/battle.rs Normal file → Executable file
View File

@@ -194,7 +194,7 @@ impl Battle {
let mut winning_side = None;
for (side_index, side) in self.sides.iter().enumerate() {
// If any side has fled, the battle end.
if side.has_fled() {
if side.has_fled_battle() {
let _w = self.result.write();
unsafe {
self.result.data_ptr().replace(BattleResult::Inconclusive);
@@ -339,8 +339,7 @@ impl VolatileScriptsOwner for Battle {
}
fn load_volatile_script(&self, key: &StringKey) -> PkmnResult<Option<Arc<dyn Script>>> {
self.library
.load_script((self as *const Self).cast(), ScriptCategory::Battle, key)
self.library.load_script(self.into(), ScriptCategory::Battle, key)
}
}

6
src/dynamic_data/models/battle_party.rs Normal file → Executable file
View File

@@ -6,6 +6,7 @@ use crate::dynamic_data::models::pokemon_party::PokemonParty;
/// A battle party is a wrapper around a party, with the indices for which the party is responsible
/// on the field attached.
#[derive(Debug)]
#[cfg_attr(feature = "wasm", derive(unique_type_id_derive::UniqueTypeId))]
pub struct BattleParty {
/// The party the BattleParty is holding.
party: Arc<PokemonParty>,
@@ -47,4 +48,9 @@ impl BattleParty {
pub fn get_pokemon(&self, index: usize) -> &Option<Arc<Pokemon>> {
self.party.at(index)
}
/// Gets the underlying Pokemon Party
pub fn party(&self) -> &Arc<PokemonParty> {
&self.party
}
}

1
src/dynamic_data/models/battle_random.rs Normal file → Executable file
View File

@@ -9,6 +9,7 @@ use crate::utils::Random;
/// The RNG for a battle.
#[derive(Default)]
#[cfg_attr(feature = "wasm", derive(unique_type_id_derive::UniqueTypeId))]
pub struct BattleRandom {
/// The actual underlying RNG. This is in a mutex, so it is thread safe, and can be ran
/// predictably, with guaranteed the same outputs.

8
src/dynamic_data/models/battle_side.rs Normal file → Executable file
View File

@@ -18,6 +18,7 @@ use crate::{script_hook, PkmnResult, StringKey};
/// A side on a battle.
#[derive(Debug)]
#[cfg_attr(feature = "wasm", derive(unique_type_id_derive::UniqueTypeId))]
pub struct BattleSide {
/// The index of the side on the battle.
index: u8,
@@ -239,11 +240,6 @@ impl BattleSide {
true
}
/// Checks whether the side has fled.
pub fn has_fled(&self) -> bool {
self.has_fled_battle
}
/// Mark the side as fled.
pub fn mark_as_fled(&mut self) {
self.has_fled_battle = true;
@@ -304,7 +300,7 @@ impl VolatileScriptsOwner for BattleSide {
fn load_volatile_script(&self, key: &StringKey) -> PkmnResult<Option<Arc<dyn Script>>> {
self.battle()
.library()
.load_script((self as *const Self).cast(), crate::ScriptCategory::Side, key)
.load_script(self.into(), crate::ScriptCategory::Side, key)
}
}

3
src/dynamic_data/models/executing_move.rs Normal file → Executable file
View File

@@ -15,6 +15,7 @@ use crate::{PkmnResult, PokemonError};
/// A hit data is the data for a single hit, on a single target.
#[derive(Default, Debug)]
#[cfg_attr(feature = "wasm", derive(unique_type_id_derive::UniqueTypeId))]
pub struct HitData {
/// Whether or not the hit is critical.
critical: AtomicBool,
@@ -158,7 +159,7 @@ impl ExecutingMove {
}
/// Gets a hit data for a target, with a specific index.
pub fn get_hit_data<'func>(&'func self, for_target: &'func Arc<Pokemon>, hit: u8) -> PkmnResult<&'func HitData> {
pub fn get_hit_data(&self, for_target: &Pokemon, hit: u8) -> PkmnResult<&HitData> {
for (index, target) in self.targets.iter().enumerate() {
if let Some(target) = target {
if std::ptr::eq(target.deref().deref(), for_target.deref().deref()) {

0
src/dynamic_data/models/learned_move.rs Normal file → Executable file
View File

0
src/dynamic_data/models/mod.rs Normal file → Executable file
View File

9
src/dynamic_data/models/pokemon.rs Normal file → Executable file
View File

@@ -502,11 +502,7 @@ impl Pokemon {
let ability_script = self
.library
.load_script(
(self as *const Self).cast(),
ScriptCategory::Ability,
self.active_ability().name(),
)
.load_script((&*self).into(), ScriptCategory::Ability, self.active_ability().name())
.unwrap();
if let Some(ability_script) = ability_script {
self.ability_script
@@ -772,8 +768,7 @@ impl VolatileScriptsOwner for Pokemon {
}
fn load_volatile_script(&self, key: &StringKey) -> PkmnResult<Option<Arc<dyn Script>>> {
self.library
.load_script((self as *const Self).cast(), ScriptCategory::Pokemon, key)
self.library.load_script(self.into(), ScriptCategory::Pokemon, key)
}
}

0
src/dynamic_data/models/pokemon_builder.rs Normal file → Executable file
View File

13
src/dynamic_data/models/pokemon_party.rs Normal file → Executable file
View File

@@ -4,6 +4,7 @@ use crate::dynamic_data::models::pokemon::Pokemon;
/// A list of Pokemon belonging to a trainer.
#[derive(Debug)]
#[cfg_attr(feature = "wasm", derive(unique_type_id_derive::UniqueTypeId))]
pub struct PokemonParty {
/// The underlying list of Pokemon.
pokemon: Vec<Option<Arc<Pokemon>>>,
@@ -89,4 +90,16 @@ impl PokemonParty {
}
}
}
/// Checks if the party contains a given pokemon.
pub fn has_pokemon(&self, pokemon: &Pokemon) -> bool {
for p in &self.pokemon {
if let Some(p) = p {
if std::ptr::eq(p.as_ref(), pokemon) {
return true;
}
}
}
false
}
}

0
src/dynamic_data/script_handling/item_script.rs Normal file → Executable file
View File

0
src/dynamic_data/script_handling/mod.rs Normal file → Executable file
View File

34
src/dynamic_data/script_handling/script.rs Normal file → Executable file
View File

@@ -1,17 +1,17 @@
use std::any::Any;
use std::fmt::{Debug, Formatter};
use std::ops::Deref;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering};
use std::sync::Arc;
use std::thread::JoinHandle;
use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard};
use crate::dynamic_data::choices::TurnChoice;
use crate::dynamic_data::DamageSource;
use crate::dynamic_data::ExecutingMove;
use crate::dynamic_data::Pokemon;
use crate::dynamic_data::{Battle, DynamicLibrary};
use crate::dynamic_data::{BattleSide, DamageSource};
use crate::static_data::{EffectParameter, TypeIdentifier};
use crate::static_data::{Item, Statistic};
use crate::StringKey;
@@ -543,3 +543,33 @@ mod tests {
);
}
}
/// Data to store references to their owning objects on scripts.
pub enum ScriptOwnerData {
/// A script attached to a Pokemon has a reference to that Pokemon.
Pokemon(AtomicPtr<Pokemon>),
/// A script attached to a Battle Side has a reference to that Battle Side.
BattleSide(AtomicPtr<BattleSide>),
/// A script attached to a Battle has a reference to that Battle.
Battle(AtomicPtr<Battle>),
/// A script also can have no owner.
None,
}
impl Into<ScriptOwnerData> for &Pokemon {
fn into(self) -> ScriptOwnerData {
ScriptOwnerData::Pokemon(AtomicPtr::new(self as *const Pokemon as *mut Pokemon))
}
}
impl Into<ScriptOwnerData> for &BattleSide {
fn into(self) -> ScriptOwnerData {
ScriptOwnerData::BattleSide(AtomicPtr::new(self as *const BattleSide as *mut BattleSide))
}
}
impl Into<ScriptOwnerData> for &Battle {
fn into(self) -> ScriptOwnerData {
ScriptOwnerData::Battle(AtomicPtr::new(self as *const Battle as *mut Battle))
}
}

0
src/dynamic_data/script_handling/script_set.rs Normal file → Executable file
View File

View File

@@ -22,13 +22,19 @@ pub trait VolatileScriptsOwner {
}
/// Adds a volatile script by name.
fn add_volatile_script(&mut self, key: &StringKey) -> PkmnResult<Option<ScriptContainer>> {
fn add_volatile_script(&self, key: &StringKey) -> PkmnResult<Option<ScriptContainer>> {
self.volatile_scripts()
.stack_or_add(key, &|| self.load_volatile_script(key))
}
/// Adds a volatile script by name.
fn add_volatile_script_with_script(&self, script: Arc<dyn Script>) -> PkmnResult<Option<ScriptContainer>> {
self.volatile_scripts()
.stack_or_add(&script.name().clone(), &|| Ok(Some(script.clone())))
}
/// Removes a volatile script by name.
fn remove_volatile_script(&mut self, key: &StringKey) {
fn remove_volatile_script(&self, key: &StringKey) {
self.volatile_scripts().remove(key)
}
}