Initial work on rune as scripting library
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
2024-04-07 18:55:41 +02:00
parent 6379abf446
commit 67b0abe59f
24 changed files with 1186 additions and 739 deletions

View File

@@ -1,3 +1,7 @@
/// The WASM module handles loading dynamic scripts through WebAssembly.
#[cfg(feature = "wasm")]
pub mod wasm;
/// The Rune module handles loading dynamic scripts through the Rune language.
#[cfg(feature = "rune")]
pub mod rune;

View File

@@ -0,0 +1,134 @@
use crate::dynamic_data::{Battle, DamageSource, ExecutingMove, Pokemon, TurnChoice};
use crate::static_data::{Item, Statistic, TypeIdentifier};
use crate::StringKey;
use rune::Hash;
use std::sync::Arc;
mod script;
pub mod script_resolver;
pub(self) mod wrappers;
#[derive(Debug, Default)]
pub(self) struct RuneScriptType {
pub fn_on_initialize: Option<Hash>,
pub fn_on_stack: Option<Hash>,
pub fn_on_remove: Option<Hash>,
pub fn_on_before_turn: Option<Hash>,
pub fn_change_speed: Option<Hash>,
pub fn_change_priority: Option<Hash>,
pub fn_change_move: Option<Hash>,
pub fn_change_number_of_hits: Option<Hash>,
pub fn_prevent_move: Option<Hash>,
pub fn_fail_move: Option<Hash>,
pub fn_stop_before_move: Option<Hash>,
pub fn_on_before_move: Option<Hash>,
pub fn_fail_incoming_move: Option<Hash>,
pub fn_is_invulnerable: Option<Hash>,
pub fn_on_move_miss: Option<Hash>,
pub fn_change_move_type: Option<Hash>,
pub fn_change_effectiveness: Option<Hash>,
pub fn_block_critical: Option<Hash>,
pub fn_block_incoming_critical: Option<Hash>,
pub fn_change_accuracy: Option<Hash>,
pub fn_change_critical_stage: Option<Hash>,
pub fn_change_critical_modifier: Option<Hash>,
pub fn_change_stab_modifier: Option<Hash>,
pub fn_change_base_power: Option<Hash>,
pub fn_bypass_defensive_stat_boost: Option<Hash>,
pub fn_bypass_offensive_stat_boost: Option<Hash>,
pub fn_change_offensive_stat_value: Option<Hash>,
pub fn_change_defensive_stat_value: Option<Hash>,
pub fn_change_damage_stat_modifier: Option<Hash>,
pub fn_change_damage_modifier: Option<Hash>,
pub fn_change_damage: Option<Hash>,
pub fn_change_incoming_damage: Option<Hash>,
pub fn_on_incoming_hit: Option<Hash>,
pub fn_on_opponent_faints: Option<Hash>,
pub fn_prevent_stat_boost_change: Option<Hash>,
pub fn_change_stat_boost_change: Option<Hash>,
pub fn_prevent_secondary_effect: Option<Hash>,
pub fn_change_effect_chance: Option<Hash>,
pub fn_change_incoming_effect_chance: Option<Hash>,
pub fn_on_secondary_effect: Option<Hash>,
pub fn_on_after_hits: Option<Hash>,
pub fn_prevent_self_switch: Option<Hash>,
pub fn_prevent_opponent_switch: Option<Hash>,
pub fn_on_fail: Option<Hash>,
pub fn_on_opponent_fail: Option<Hash>,
pub fn_prevent_self_run_away: Option<Hash>,
pub fn_prevent_opponent_run_away: Option<Hash>,
pub fn_on_end_turn: Option<Hash>,
pub fn_on_damage: Option<Hash>,
pub fn_on_faint: Option<Hash>,
pub fn_on_switch_in: Option<Hash>,
pub fn_on_after_held_item_consume: Option<Hash>,
pub fn_change_experience_gained: Option<Hash>,
pub fn_share_experience: Option<Hash>,
pub fn_block_weather: Option<Hash>,
pub fn_change_capture_rate_bonus: Option<Hash>,
}
impl RuneScriptType {
pub fn on_found_fn(&mut self, fn_name: &str, hash: Hash) {
match fn_name {
"on_initialize" => self.fn_on_initialize = Some(hash),
"on_stack" => self.fn_on_stack = Some(hash),
"on_remove" => self.fn_on_remove = Some(hash),
"on_before_turn" => self.fn_on_before_turn = Some(hash),
"change_speed" => self.fn_change_speed = Some(hash),
"change_priority" => self.fn_change_priority = Some(hash),
"change_move" => self.fn_change_move = Some(hash),
"change_number_of_hits" => self.fn_change_number_of_hits = Some(hash),
"prevent_move" => self.fn_prevent_move = Some(hash),
"fail_move" => self.fn_fail_move = Some(hash),
"stop_before_move" => self.fn_stop_before_move = Some(hash),
"on_before_move" => self.fn_on_before_move = Some(hash),
"fail_incoming_move" => self.fn_fail_incoming_move = Some(hash),
"is_invulnerable" => self.fn_is_invulnerable = Some(hash),
"on_move_miss" => self.fn_on_move_miss = Some(hash),
"change_move_type" => self.fn_change_move_type = Some(hash),
"change_effectiveness" => self.fn_change_effectiveness = Some(hash),
"block_critical" => self.fn_block_critical = Some(hash),
"block_incoming_critical" => self.fn_block_incoming_critical = Some(hash),
"change_accuracy" => self.fn_change_accuracy = Some(hash),
"change_critical_stage" => self.fn_change_critical_stage = Some(hash),
"change_critical_modifier" => self.fn_change_critical_modifier = Some(hash),
"change_stab_modifier" => self.fn_change_stab_modifier = Some(hash),
"change_base_power" => self.fn_change_base_power = Some(hash),
"bypass_defensive_stat_boost" => self.fn_bypass_defensive_stat_boost = Some(hash),
"bypass_offensive_stat_boost" => self.fn_bypass_offensive_stat_boost = Some(hash),
"change_offensive_stat_value" => self.fn_change_offensive_stat_value = Some(hash),
"change_defensive_stat_value" => self.fn_change_defensive_stat_value = Some(hash),
"change_damage_stat_modifier" => self.fn_change_damage_stat_modifier = Some(hash),
"change_damage_modifier" => self.fn_change_damage_modifier = Some(hash),
"change_damage" => self.fn_change_damage = Some(hash),
"change_incoming_damage" => self.fn_change_incoming_damage = Some(hash),
"on_incoming_hit" => self.fn_on_incoming_hit = Some(hash),
"on_opponent_faints" => self.fn_on_opponent_faints = Some(hash),
"prevent_stat_boost_change" => self.fn_prevent_stat_boost_change = Some(hash),
"change_stat_boost_change" => self.fn_change_stat_boost_change = Some(hash),
"prevent_secondary_effect" => self.fn_prevent_secondary_effect = Some(hash),
"change_effect_chance" => self.fn_change_effect_chance = Some(hash),
"change_incoming_effect_chance" => self.fn_change_incoming_effect_chance = Some(hash),
"on_secondary_effect" => self.fn_on_secondary_effect = Some(hash),
"on_after_hits" => self.fn_on_after_hits = Some(hash),
"prevent_self_switch" => self.fn_prevent_self_switch = Some(hash),
"prevent_opponent_switch" => self.fn_prevent_opponent_switch = Some(hash),
"on_fail" => self.fn_on_fail = Some(hash),
"on_opponent_fail" => self.fn_on_opponent_fail = Some(hash),
"prevent_self_run_away" => self.fn_prevent_self_run_away = Some(hash),
"prevent_opponent_run_away" => self.fn_prevent_opponent_run_away = Some(hash),
"on_end_turn" => self.fn_on_end_turn = Some(hash),
"on_damage" => self.fn_on_damage = Some(hash),
"on_faint" => self.fn_on_faint = Some(hash),
"on_switch_in" => self.fn_on_switch_in = Some(hash),
"on_after_held_item_consume" => self.fn_on_after_held_item_consume = Some(hash),
"change_experience_gained" => self.fn_change_experience_gained = Some(hash),
"share_experience" => self.fn_share_experience = Some(hash),
"block_weather" => self.fn_block_weather = Some(hash),
"change_capture_rate_bonus" => self.fn_change_capture_rate_bonus = Some(hash),
_ => {}
}
}
}

View File

@@ -0,0 +1,153 @@
use crate::dynamic_data::{DynamicLibrary, ExecutingMove, Pokemon, Script, ScriptOwnerData, TurnChoice};
use crate::script_implementations::rune::wrappers::*;
use crate::script_implementations::rune::RuneScriptType;
use crate::static_data::Parameter;
use crate::StringKey;
use hashbrown::HashMap;
use parking_lot::RwLock;
use rune::runtime::{Object, RuntimeContext, Shared, VmError, VmResult};
use rune::{Any, Unit, Value};
use std::convert::TryFrom;
use std::error::Error;
use std::ops::Deref;
use std::sync::atomic::{AtomicBool, AtomicUsize};
use std::sync::Arc;
pub struct RuneScript {
name: StringKey,
state: RwLock<Shared<Object>>,
/// Returns an atomic bool for internal marking of deletion. This is currently only specifically
/// used for deletion of a script while we are holding a reference to it (i.e. executing a script
/// hook on it).
marked_for_deletion: AtomicBool,
/// A script can be suppressed by other scripts. If a script is suppressed by at least one script
/// we will not execute its methods. This holds the number of suppressions on the script.
suppressed_count: AtomicUsize,
/// The owner of this script (where the script is attached to)
owner: ScriptOwnerData,
script_type: Arc<RuneScriptType>,
runtime: Arc<RuntimeContext>,
unit: Arc<Unit>,
}
unsafe impl Send for RuneScript {}
unsafe impl Sync for RuneScript {}
impl RuneScript {
pub fn new(
name: StringKey,
object: Shared<Object>,
owner: ScriptOwnerData,
script_type: Arc<RuneScriptType>,
runtime: Arc<RuntimeContext>,
unit: Arc<Unit>,
) -> Self {
Self {
name,
state: RwLock::new(object),
marked_for_deletion: Default::default(),
suppressed_count: Default::default(),
owner,
script_type,
runtime,
unit,
}
}
}
impl Script for RuneScript {
fn name(&self) -> anyhow::Result<&StringKey> { Ok(&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,
_: &Arc<dyn DynamicLibrary>,
pars: &HashMap<StringKey, Arc<Parameter>>,
) -> anyhow::Result<()> {
if pars.is_empty() {
return Ok(());
}
let mut write_lock = self.state.write();
for par in pars {
let key = rune::alloc::string::String::try_from(par.0.str())?;
write_lock
.borrow_mut()?
.insert(key, parameter_to_rune_value(par.1.as_ref())?)?;
}
Ok(())
}
fn block_critical(
&self,
move_data: &Arc<ExecutingMove>,
target: &Pokemon,
hit: u8,
block_critical: &mut bool,
) -> anyhow::Result<()> {
if let Some(hash) = self.script_type.fn_block_critical {
let mut vm = rune::runtime::Vm::new(self.runtime.clone(), self.unit.clone());
todo!()
// let block_critical_handle = RuneValueWrapper::new_mut(block_critical);
// let read_lock = self.state.read();
// let state = read_lock.deref();
//
// vm.execute(
// hash,
// vec![
// Value::Object(state.clone()),
// Value::from(move_data.wrap()?),
// Value::from(target.wrap()?),
// Value::from(hit),
// Value::from(block_critical_handle.clone().wrap()?),
// ],
// )?;
// *block_critical = block_critical_handle.value();
}
Ok(())
}
fn change_speed(&self, choice: &Arc<TurnChoice>, speed: &mut u32) -> anyhow::Result<()> {
if let Some(hash) = self.script_type.fn_change_speed {
let mut vm = rune::runtime::Vm::new(self.runtime.clone(), self.unit.clone());
let speed_handle = wrap_value_reference(*speed as i64)?;
let read_lock = self.state.read();
let state = read_lock.deref();
let res = vm
.execute(
hash,
vec![
Value::Object(state.clone()),
Value::from(choice.wrap()),
speed_handle.clone(),
],
)?
.complete();
if let VmResult::Err(e) = res {
return Err(anyhow::anyhow!("Error executing script: {}", e));
}
*speed = get_value_reference(speed_handle)? as u32;
}
Ok(())
}
fn as_any(&self) -> &dyn std::any::Any { self }
fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
}
fn parameter_to_rune_value(parameter: &Parameter) -> Result<Value, VmError> {
match parameter {
Parameter::Bool(b) => rune::to_value(*b),
Parameter::Int(i) => rune::to_value(*i),
Parameter::Float(f) => rune::to_value(*f),
Parameter::String(s) => rune::to_value(s.str()),
}
}

View File

@@ -0,0 +1,179 @@
use crate::dynamic_data::{ItemScript, Script, ScriptCategory, ScriptOwnerData, ScriptResolver};
use crate::script_implementations::rune::script::RuneScript;
use crate::script_implementations::rune::RuneScriptType;
use crate::static_data::Item;
use crate::StringKey;
use hashbrown::HashMap;
use parking_lot::RwLock;
use rune::compile::meta::AssociatedKind;
use rune::compile::{ComponentRef, MetaError};
use rune::diagnostics::Diagnostic;
use rune::runtime::{RuntimeContext, Shared};
use rune::{Context, Diagnostics, Hash, Options, Source, Sources, Unit, Vm};
use std::any::Any;
use std::path::Path;
use std::sync::Arc;
#[derive(Debug)]
pub struct RuneScriptResolver {
runtime: Arc<RuntimeContext>,
unit: Arc<Unit>,
script_types: HashMap<(ScriptCategory, StringKey), Arc<RuneScriptType>>,
}
pub struct RuneScriptResolverBuilder {
context: Context,
scripts: Sources,
}
impl ScriptResolver for RuneScriptResolver {
fn load_script(
&self,
owner: ScriptOwnerData,
category: ScriptCategory,
script_key: &StringKey,
) -> anyhow::Result<Option<Arc<dyn Script>>> {
let script_type = if let Some(script_type) = self.script_types.get(&(category, script_key.clone())) {
script_type
} else {
return Ok(None);
};
let state = Shared::new(rune::runtime::Object::new())?;
let script = Arc::new(RuneScript::new(
script_key.clone(),
state,
owner,
script_type.clone(),
self.runtime.clone(),
self.unit.clone(),
));
Ok(Some(script))
}
fn load_item_script(&self, _key: &dyn Item) -> anyhow::Result<Option<Arc<dyn ItemScript>>> { Ok(None) }
fn as_any(&self) -> &dyn Any { self }
}
impl RuneScriptResolverBuilder {
pub fn new() -> anyhow::Result<Self> {
let mut context = Context::with_default_modules()?;
context.install(super::wrappers::module()?)?;
Ok(Self {
context,
scripts: Sources::default(),
})
}
pub fn insert_script(&mut self, path: &Path, name: &str, script: &str) -> anyhow::Result<&mut Self> {
self.scripts
.insert(Source::with_path(name, script.to_string(), path)?)?;
Ok(self)
}
pub fn build(mut self) -> anyhow::Result<Arc<dyn ScriptResolver>> {
let mut visitor = FindScriptTypeVisitor::default();
let mut diagnostics = Diagnostics::new();
let mut options = Options::default();
options.debug_info(true);
options.memoize_instance_fn(true);
options.macros(true);
options.bytecode(true);
let result = rune::prepare(&mut self.scripts)
.with_context(&self.context)
.with_visitor(&mut visitor)?
.with_diagnostics(&mut diagnostics)
.with_options(&options)
.build();
if diagnostics.has_error() {
let error_message = diagnostics
.diagnostics()
.iter()
.filter_map(|d| match d {
Diagnostic::Fatal(f) => Some(f.to_string()),
_ => None,
})
.collect::<Vec<String>>()
.join("\n");
return Err(anyhow::anyhow!("Error building Rune script: {}", error_message));
}
let mut script_types = HashMap::with_capacity(visitor.script_types.len());
for (key, script_type) in visitor.script_types {
script_types.insert(key, Arc::new(script_type));
}
Ok(Arc::new(RuneScriptResolver {
runtime: Arc::new(self.context.runtime()?),
unit: Arc::new(result?),
script_types,
}))
}
}
#[derive(Debug, Default)]
struct FindScriptTypeVisitor {
script_types: HashMap<(ScriptCategory, StringKey), RuneScriptType>,
}
impl rune::compile::CompileVisitor for FindScriptTypeVisitor {
fn register_meta(&mut self, meta: rune::compile::MetaRef<'_>) -> Result<(), MetaError> {
match meta.kind {
rune::compile::meta::Kind::Struct { .. } => {
if meta.item.iter().count() < 2 {
return Ok(());
}
let mod_name = meta.item.iter().nth(0).unwrap();
let category = match get_mod_category(mod_name) {
Ok(value) => value,
Err(value) => return value,
};
let name = meta.item.last().unwrap();
self.script_types
.insert((category, name.to_string().as_str().into()), RuneScriptType::default());
}
rune::compile::meta::Kind::Function {
associated: Some(AssociatedKind::Instance(associated)),
..
} => {
if meta.item.iter().count() < 3 {
return Ok(());
}
let mod_name = meta.item.iter().nth(0).unwrap();
let category = match get_mod_category(mod_name) {
Ok(value) => value,
Err(value) => return value,
};
let instance = meta.item.iter().nth_back(1).unwrap();
if let Some(script_type) = self
.script_types
.get_mut(&(category, instance.to_string().as_str().into()))
{
script_type.on_found_fn(associated.to_string().as_str(), meta.hash);
}
}
_ => {}
}
Ok(())
}
}
fn get_mod_category(mod_name: ComponentRef) -> Result<ScriptCategory, Result<(), MetaError>> {
Ok(match mod_name.to_string().as_str() {
"moves" => ScriptCategory::Move,
"abilities" => ScriptCategory::Ability,
"status" => ScriptCategory::Status,
"pokemon" => ScriptCategory::Pokemon,
"sides" => ScriptCategory::Side,
"battle" => ScriptCategory::Battle,
"weather" => ScriptCategory::Weather,
"item_battle_triggers" => ScriptCategory::ItemBattleTrigger,
_ => return Err(Ok(())),
})
}

View File

@@ -0,0 +1,26 @@
use crate::dynamic_data::ExecutingMove;
use crate::script_implementations::rune::wrappers::{impl_rune_wrapper, RuneWrapper};
use rune::runtime::{AnyObj, Shared};
use rune::Any;
use std::sync::Arc;
pub fn register(module: &mut rune::Module) -> anyhow::Result<()> {
module.ty::<RuneExecutingMove>()?;
Ok(())
}
#[derive(Debug, Any)]
pub struct RuneExecutingMove {
inner: Arc<ExecutingMove>,
}
impl RuneExecutingMove {
#[rune::function]
pub fn target_count(&self) -> usize { self.inner.target_count() }
#[rune::function]
pub fn number_of_hits(&self) -> u8 { self.inner.number_of_hits() }
#[rune::function]
pub fn user(&self) -> Shared<AnyObj> { self.inner.user().wrap() }
}
impl_rune_wrapper!(&Arc<ExecutingMove>, RuneExecutingMove);

View File

@@ -0,0 +1,67 @@
use rune::runtime::Protocol;
use rune::runtime::{AnyObj, Shared};
use rune::{Any, Value};
use std::ops::Deref;
mod executing_move;
mod pokemon;
mod turn_choice;
pub use executing_move::*;
pub use pokemon::*;
pub trait RuneWrapper {
#[inline]
fn wrap(self) -> Shared<AnyObj>;
}
pub fn module() -> anyhow::Result<rune::Module> {
let mut module = rune::Module::new();
module.ty::<RuneValueIntWrapper>()?;
turn_choice::register(&mut module)?;
pokemon::register(&mut module)?;
executing_move::register(&mut module)?;
Ok(module)
}
pub fn wrap_value_reference(value: i64) -> anyhow::Result<Value> {
Ok(Value::Any(Shared::new(AnyObj::new(RuneValueIntWrapper::new(value))?)?))
}
pub fn get_value_reference(value: Value) -> anyhow::Result<i64> {
let obj = value.into_any().into_result()?;
let obj = obj.take()?;
let obj = obj.downcast_borrow_ref::<RuneValueIntWrapper>().unwrap();
Ok(obj.value())
}
#[derive(Any, Clone)]
struct RuneValueIntWrapper {
#[rune(get, set)]
value: i64,
}
impl RuneValueIntWrapper {
pub fn new(value: i64) -> Self { Self { value } }
pub fn value(&self) -> i64 { self.value }
pub fn set_value(&mut self, value: i64) { self.value = value; }
}
impl RuneWrapper for RuneValueIntWrapper {
fn wrap(self) -> Shared<AnyObj> { Shared::new(AnyObj::new(self).unwrap()).unwrap() }
}
macro_rules! impl_rune_wrapper {
($t:ty, $wrapped_type:ident) => {
impl crate::script_implementations::rune::wrappers::RuneWrapper for $t {
fn wrap(self) -> rune::runtime::Shared<rune::runtime::AnyObj> {
rune::runtime::Shared::new(rune::runtime::AnyObj::new($wrapped_type { inner: self.clone() }).unwrap())
.unwrap()
}
}
};
}
pub(self) use impl_rune_wrapper;

View File

@@ -0,0 +1,22 @@
use crate::defines::LevelInt;
use crate::dynamic_data::Pokemon;
use crate::script_implementations::rune::wrappers::{impl_rune_wrapper, RuneWrapper};
use rune::Any;
pub fn register(module: &mut rune::Module) -> anyhow::Result<()> {
module.ty::<RunePokemon>()?;
module.function_meta(RunePokemon::level)?;
Ok(())
}
#[derive(Any)]
pub struct RunePokemon {
inner: Pokemon,
}
impl RunePokemon {
#[rune::function]
fn level(&self) -> LevelInt { self.inner.level() }
}
impl_rune_wrapper!(&Pokemon, RunePokemon);

View File

@@ -0,0 +1,26 @@
use crate::dynamic_data::TurnChoice;
use crate::script_implementations::rune::wrappers::{impl_rune_wrapper, RuneExecutingMove, RuneWrapper};
use rune::{Any, Value};
use std::sync::Arc;
pub fn register(module: &mut rune::Module) -> anyhow::Result<()> {
module.ty::<RuneTurnChoice>()?;
module.function_meta(RuneTurnChoice::speed)?;
module.function_meta(RuneTurnChoice::user)?;
Ok(())
}
#[derive(Any)]
pub struct RuneTurnChoice {
inner: Arc<TurnChoice>,
}
impl RuneTurnChoice {
#[rune::function]
fn speed(&self) -> u32 { self.inner.speed() }
#[rune::function]
fn user(&self) -> Value { Value::from(self.inner.user().wrap()) }
}
impl_rune_wrapper!(&Arc<TurnChoice>, RuneTurnChoice);