FFI for Battle
continuous-integration/drone/push Build is passing Details

This commit is contained in:
Deukhoofd 2022-10-15 17:21:24 +02:00
parent 6c976216b8
commit ddfb00d36b
Signed by: Deukhoofd
GPG Key ID: F63E044490819F6F
10 changed files with 313 additions and 10 deletions

View File

@ -57,6 +57,7 @@ unique-type-id = { version = "1.0.0", optional = true }
unique-type-id-derive = { version = "1.0.0", optional = true }
paste = { version = "1.0.8" }
arcstr = { version = "1.1.4", features = ["std"] }
enum-display-derive = "0.1.1"
[dev-dependencies]
csv = "1.1.6"

View File

@ -1,3 +1,4 @@
use parking_lot::RwLock;
use std::fmt::{Debug, Formatter};
use std::sync::Arc;
@ -7,6 +8,11 @@ use crate::dynamic_data::Pokemon;
use crate::static_data::Species;
use crate::static_data::{Form, Statistic};
/// A function that will be called when an event occured.
type EvtHookFn = Box<dyn Fn(&Box<&Event>)>;
/// A collection of event hooks.
type EvtHookCollection = Vec<EvtHookFn>;
/// The event hook is used to store external functions that listen to events.
///
/// Events happen in many
@ -15,22 +21,22 @@ use crate::static_data::{Form, Statistic};
#[derive(Default)]
pub struct EventHook {
/// All the registered event listeners on the hook.
evt_hook_function: Vec<fn(&Box<&Event>)>,
evt_hook_function: RwLock<EvtHookCollection>,
}
impl EventHook {
/// Register a new listener. This will start receiving all events in the battle. Multiple event
/// listeners can exist at the same time. Note that for these functions the event will be disposed
/// of after the event is finished being sent.
pub fn register_listener(&mut self, func: fn(&Box<&Event>)) {
self.evt_hook_function.push(func);
pub fn register_listener(&self, func: EvtHookFn) {
self.evt_hook_function.write().push(func);
}
/// Run a new event. This will send the event to all externally defined event listeners. It will
/// dispose of the event afterwards.
pub fn trigger(&self, evt: Event) {
let b = Box::new(&evt);
for f in &self.evt_hook_function {
for f in self.evt_hook_function.read().iter() {
f(&b);
}
}

View File

@ -24,11 +24,12 @@ pub trait ScriptResolver: Debug + ValueIdentifiable {
fn load_item_script(&self, _key: &Item) -> PkmnResult<Option<Arc<dyn ItemScript>>>;
}
use std::fmt::Display;
/// A script category defines a sub-group of scripts. This can be used to have multiple scripts with
/// the same name, but a different script. It should be completely valid for a move to have the same
/// name as an ability, or more commonly: for a script attached to a Pokemon to have the same name as
/// a move that placed it there.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Display)]
#[repr(u8)]
pub enum ScriptCategory {
/// A script that belongs to a move. This generally is only the script that is attached to a

View File

@ -158,7 +158,7 @@ impl Battle {
pub fn current_turn(&self) -> u32 {
self.current_turn.load(Ordering::Relaxed)
}
/// The time the last turn took to run. Defaults to 0.
/// The time in nanoseconds the last turn took to run. Defaults to 0.
pub fn last_turn_time(&self) -> u64 {
self.last_turn_time.load(Ordering::Relaxed)
}
@ -261,7 +261,7 @@ impl Battle {
}
/// Try and set the choice for the battle. If the choice is not valid, this returns false.
pub fn try_set_choice(&mut self, choice: TurnChoice) -> PkmnResult<bool> {
pub fn try_set_choice(&self, choice: TurnChoice) -> PkmnResult<bool> {
if !self.can_use(&choice) {
return Ok(false);
}
@ -399,7 +399,7 @@ impl ValueIdentifiable for Battle {
}
/// The result of a battle.
#[derive(Debug, Copy, Clone)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum BattleResult {
/// The battle has no winner. Either the battle has not ended, or everyone is dead, or one of
/// the parties has ran away.

View File

@ -4,13 +4,15 @@ use std::sync::{Arc, Mutex};
use crate::dynamic_data::models::executing_move::ExecutingMove;
use crate::dynamic_data::models::pokemon::Pokemon;
use crate::dynamic_data::script_handling::ScriptSource;
use crate::script_hook;
use crate::utils::Random;
use crate::{script_hook, ValueIdentifiable, ValueIdentifier};
/// The RNG for a battle.
#[derive(Default)]
#[cfg_attr(feature = "wasm", derive(unique_type_id_derive::UniqueTypeId))]
pub struct BattleRandom {
/// A unique identifier so we know what value this is.
identifier: ValueIdentifier,
/// The actual underlying RNG. This is in a mutex, so it is thread safe, and can be ran
/// predictably, with guaranteed the same outputs.
random: Mutex<Random>,
@ -20,6 +22,7 @@ impl BattleRandom {
/// Initializes a new RNG with a given seed.
pub fn new_with_seed(seed: u128) -> Self {
BattleRandom {
identifier: Default::default(),
random: Mutex::new(Random::new(seed)),
}
}
@ -89,7 +92,14 @@ impl Debug for BattleRandom {
impl Clone for BattleRandom {
fn clone(&self) -> Self {
Self {
identifier: Default::default(),
random: Mutex::new(self.random.lock().unwrap().clone()),
}
}
}
impl ValueIdentifiable for BattleRandom {
fn value_identifier(&self) -> ValueIdentifier {
self.identifier
}
}

View File

@ -14,12 +14,14 @@ use crate::dynamic_data::script_handling::{ScriptSource, ScriptSourceData, Scrip
use crate::dynamic_data::Script;
use crate::dynamic_data::ScriptSet;
use crate::dynamic_data::VolatileScriptsOwner;
use crate::{script_hook, PkmnResult, StringKey};
use crate::{script_hook, PkmnResult, StringKey, ValueIdentifiable, ValueIdentifier};
/// A side on a battle.
#[derive(Debug)]
#[cfg_attr(feature = "wasm", derive(unique_type_id_derive::UniqueTypeId))]
pub struct BattleSide {
/// A unique identifier so we know what value this is.
identifier: ValueIdentifier,
/// The index of the side on the battle.
index: u8,
/// The number of Pokemon that can be on the side.
@ -60,6 +62,7 @@ impl BattleSide {
let pokemon = RwLock::new(pokemon);
Self {
identifier: Default::default(),
index,
pokemon_per_side,
pokemon,
@ -322,3 +325,9 @@ impl ScriptSource for BattleSide {
self.battle().collect_scripts(scripts);
}
}
impl ValueIdentifiable for BattleSide {
fn value_identifier(&self) -> ValueIdentifier {
self.identifier
}
}

View File

@ -0,0 +1,210 @@
use crate::dynamic_data::{
Battle, BattleParty, BattleRandom, BattleResult, BattleSide, DynamicLibrary, Pokemon, TurnChoice,
};
use crate::ffi::dynamic_data::models::native_event_hook::NativeEventHook;
use crate::ffi::{ExternPointer, IdentifiablePointer, OwnedPtr};
use std::ffi::{c_char, CStr, CString};
use std::sync::Arc;
/// Initializes a new battle.
#[no_mangle]
extern "C" fn battle_new(
library: ExternPointer<Arc<DynamicLibrary>>,
parties: *const OwnedPtr<BattleParty>,
parties_length: usize,
can_flee: u8,
number_of_sides: u8,
pokemon_per_side: u8,
// NOTE: Split into two due to u128 not being ABI safe: https://github.com/rust-lang/rust/issues/54341
random_seed_1: u64,
random_seed_2: u64,
) -> IdentifiablePointer<Battle> {
let parties = unsafe {
std::slice::from_raw_parts(parties, parties_length)
.iter()
.map(|x| *Box::from_raw(*x))
.collect()
};
let random_seed = if cfg!(target_endian = "big") {
((random_seed_1 as u128) << 64) + (random_seed_2 as u128)
} else {
(random_seed_1 as u128) + ((random_seed_2 as u128) << 64)
};
let random_seed = if random_seed == 0 { None } else { Some(random_seed) };
Box::new(Battle::new(
library.as_ref().clone(),
parties,
can_flee == 1,
number_of_sides,
pokemon_per_side,
random_seed,
))
.into()
}
/// The library the battle uses for handling.
#[no_mangle]
extern "C" fn battle_library(ptr: ExternPointer<Arc<Battle>>) -> IdentifiablePointer<Arc<DynamicLibrary>> {
ptr.as_ref().library().clone().into()
}
/// The length of the list of all different parties in the battle.
#[no_mangle]
extern "C" fn battle_parties_length(ptr: ExternPointer<Arc<Battle>>) -> usize {
ptr.as_ref().parties().len()
}
/// Get a party in the battle.
#[no_mangle]
extern "C" fn battle_parties_get(ptr: ExternPointer<Arc<Battle>>, index: usize) -> IdentifiablePointer<BattleParty> {
if let Some(v) = ptr.as_ref().parties().get(index) {
(v as *const BattleParty).into()
} else {
IdentifiablePointer::none()
}
}
/// Whether or not Pokemon can flee from the battle.
#[no_mangle]
extern "C" fn battle_can_flee(ptr: ExternPointer<Arc<Battle>>) -> u8 {
u8::from(ptr.as_ref().can_flee())
}
/// The number of sides in the battle. Typically 2.
#[no_mangle]
extern "C" fn battle_number_of_sides(ptr: ExternPointer<Arc<Battle>>) -> u8 {
ptr.as_ref().number_of_sides()
}
/// The number of Pokemon that can be on each side.
#[no_mangle]
extern "C" fn battle_pokemon_per_side(ptr: ExternPointer<Arc<Battle>>) -> u8 {
ptr.as_ref().pokemon_per_side()
}
/// The length of the list of all different sides in the battle.
#[no_mangle]
extern "C" fn battle_sides_length(ptr: ExternPointer<Arc<Battle>>) -> usize {
ptr.as_ref().sides().len()
}
/// Get a side in the battle.
#[no_mangle]
extern "C" fn battle_sides_get(ptr: ExternPointer<Arc<Battle>>, index: usize) -> IdentifiablePointer<BattleSide> {
if let Some(v) = ptr.as_ref().sides().get(index) {
(v as *const BattleSide).into()
} else {
IdentifiablePointer::none()
}
}
/// The RNG used for the battle.
#[no_mangle]
extern "C" fn battle_random(ptr: ExternPointer<Arc<Battle>>) -> IdentifiablePointer<BattleRandom> {
(ptr.as_ref().random() as *const BattleRandom).into()
}
/// Whether or not the battle has ended.
#[no_mangle]
extern "C" fn battle_has_ended(ptr: ExternPointer<Arc<Battle>>) -> u8 {
u8::from(ptr.as_ref().has_ended())
}
/// Whether or not we have a conclusive winner
#[no_mangle]
extern "C" fn battle_has_conclusive_result(ptr: ExternPointer<Arc<Battle>>) -> u8 {
u8::from(ptr.as_ref().result() != BattleResult::Inconclusive)
}
/// If we have a conclusive winner, the side that has won. If we don't have a conclusive winner, this
/// always returns 0.
#[no_mangle]
extern "C" fn battle_winning_side(ptr: ExternPointer<Arc<Battle>>) -> u8 {
if let BattleResult::Conclusive(winner) = ptr.as_ref().result() {
winner
} else {
0
}
}
/// Register a function to be triggered when an event in a battle occurs.
#[no_mangle]
extern "C" fn battle_register_event_hook(ptr: ExternPointer<Arc<Battle>>, f: NativeEventHook) {
ptr.as_ref().event_hook().register_listener(Box::new(f))
}
/// The index of the current turn. 0 until all choices
#[no_mangle]
extern "C" fn battle_current_turn(ptr: ExternPointer<Arc<Battle>>) -> u32 {
ptr.as_ref().current_turn()
}
/// The time in nanoseconds the last turn took to run. Defaults to 0.
#[no_mangle]
extern "C" fn battle_last_turn_time(ptr: ExternPointer<Arc<Battle>>) -> u64 {
ptr.as_ref().last_turn_time()
}
/// Get a Pokemon on the battlefield, on a specific side and an index on that side.
#[no_mangle]
extern "C" fn battle_get_pokemon(
ptr: ExternPointer<Arc<Battle>>,
side: u8,
index: u8,
) -> IdentifiablePointer<Arc<Pokemon>> {
if let Some(v) = ptr.as_ref().get_pokemon(side, index) {
v.into()
} else {
IdentifiablePointer::none()
}
}
/// Returns whether a slot on the battlefield can still be filled. If no party is responsible
/// for that slot, or a party is responsible, but has no remaining Pokemon to throw out anymore,
/// this returns false.
#[no_mangle]
extern "C" fn battle_can_slot_be_filled(ptr: ExternPointer<Arc<Battle>>, side: u8, index: u8) -> u8 {
u8::from(ptr.as_ref().can_slot_be_filled(side, index))
}
/// Checks whether a choice is actually possible.
#[no_mangle]
extern "C" fn battle_can_use(ptr: ExternPointer<Arc<Battle>>, choice: ExternPointer<TurnChoice>) -> u8 {
u8::from(ptr.as_ref().can_use(choice.as_ref()))
}
/// Checks to see whether all Pokemon on the field have set their choices. If so, we then run
/// the turn.
#[no_mangle]
extern "C" fn battle_try_set_choice(ptr: ExternPointer<Arc<Battle>>, choice: OwnedPtr<TurnChoice>) -> u8 {
let choice = unsafe { choice.read() };
let result = ptr.as_ref().try_set_choice(choice);
match result {
Ok(b) => u8::from(b),
Err(e) => {
panic!("Encountered error: {}", e)
}
}
}
/// Sets the current weather for the battle. If nullptr is passed, this clears the weather.
#[no_mangle]
extern "C" fn battle_set_weather(ptr: ExternPointer<Arc<Battle>>, weather: *const c_char) {
if weather.is_null() {
ptr.as_ref().set_weather(None)
} else {
unsafe { ptr.as_ref().set_weather(Some(CStr::from_ptr(weather).into())) }
}
}
/// Gets the current weather of the battle. If no weather is present, this returns nullptr.
#[no_mangle]
extern "C" fn battle_weather_name(ptr: ExternPointer<Arc<Battle>>) -> *mut c_char {
if let Some(w) = ptr.as_ref().weather_name() {
CString::new(w.str()).unwrap().into_raw()
} else {
std::ptr::null_mut()
}
}

View File

@ -1,7 +1,11 @@
/// The foreign function interface for a battle.
mod battle;
/// The foreign function interface for a battle wrapper of a party.
mod battle_party;
/// The foreign function interface for a Learned Move.
mod learned_move;
/// Wrapper classed for the event hooks.
mod native_event_hook;
/// The foreign function interface for a Pokemon.
mod pokemon;
/// The foreign function interface for a party of Pokemon.

View File

@ -0,0 +1,38 @@
use crate::dynamic_data::Event;
/// Wrapper class for easier use of an external function pointer.
#[repr(C)]
pub(super) struct NativeEventHook {
/// The actual C function to be called.
f: extern "C" fn(*const Event),
}
impl NativeEventHook {
/// Calls the actual wrapped function in the correct format.
fn call_self(&self, b: &&Event<'_>) {
(self.f)(*b as *const Event)
}
}
/// A tuple with the arguments of the event hook function
type EventHookArgs<'a, 'b, 'c> = (&'a Box<&'b Event<'c>>,);
impl FnMut<EventHookArgs<'_, '_, '_>> for NativeEventHook {
extern "rust-call" fn call_mut(&mut self, args: EventHookArgs<'_, '_, '_>) -> Self::Output {
self.call_self(args.0)
}
}
impl FnOnce<EventHookArgs<'_, '_, '_>> for NativeEventHook {
type Output = ();
extern "rust-call" fn call_once(self, args: EventHookArgs<'_, '_, '_>) -> Self::Output {
self.call_self(args.0)
}
}
impl Fn<EventHookArgs<'_, '_, '_>> for NativeEventHook {
extern "rust-call" fn call(&self, args: EventHookArgs<'_, '_, '_>) -> Self::Output {
self.call_self(args.0)
}
}

View File

@ -10,6 +10,8 @@
#![feature(new_uninit)]
#![feature(get_mut_unchecked)]
#![feature(strict_provenance)]
#![feature(fn_traits)]
#![feature(unboxed_closures)]
//! PkmnLib
//! PkmnLib is a full featured implementation of Pokemon. while currently focused on implementing
@ -17,6 +19,12 @@
//! to a scripting library.
//!
extern crate core;
#[macro_use]
extern crate enum_display_derive;
use std::fmt::{Display, Formatter};
#[doc(hidden)]
pub use utils::*;
@ -57,3 +65,19 @@ pub enum PokemonError {
/// A simple result type.
pub type PkmnResult<T> = Result<T, PokemonError>;
impl Display for PokemonError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
PokemonError::ScriptNotFound { category, name } => {
write!(f, "No script found with category `{}` and name `{}`", category, name)
}
PokemonError::InvalidTargetRequested => {
write!(f, "Invalid target was requested")
}
PokemonError::MiscError => {
write!(f, "An unknown error occurred")
}
}
}
}