From ed336ebe0f2ffb7845decfd1d80d0e002e830738 Mon Sep 17 00:00:00 2001 From: Simon Kurz Date: Sat, 9 Nov 2024 08:41:24 +0100 Subject: [PATCH 01/10] Update to mlua v0.10.0 --- futuremod_engine/Cargo.toml | 4 +- .../src/plugins/library/dangerous/memory.rs | 12 ++-- .../src/plugins/library/dangerous/mod.rs | 4 +- .../src/plugins/library/dangerous/native.rs | 56 +++++++++---------- futuremod_engine/src/plugins/library/game.rs | 16 +++--- futuremod_engine/src/plugins/library/input.rs | 6 +- .../src/plugins/library/matrix.rs | 51 ++++++++--------- futuremod_engine/src/plugins/library/mod.rs | 1 + .../src/plugins/library/system.rs | 6 +- futuremod_engine/src/plugins/library/ui.rs | 6 +- futuremod_engine/src/plugins/plugin.rs | 44 +++++++-------- futuremod_hook/Cargo.toml | 4 +- futuremod_hook/src/lua.rs | 32 +++++------ futuremod_hook/src/native.rs | 2 +- futuremod_hook/src/types.rs | 6 +- 15 files changed, 126 insertions(+), 124 deletions(-) diff --git a/futuremod_engine/Cargo.toml b/futuremod_engine/Cargo.toml index 31cd29a..3264101 100644 --- a/futuremod_engine/Cargo.toml +++ b/futuremod_engine/Cargo.toml @@ -39,8 +39,8 @@ junction = "1.2.0" directories = "5.0.1" [dependencies.mlua] -version = "0.9.1" -features = ["luau", "async", "serialize", "unstable"] +version = "0.10.0" +features = ["luau", "async", "serialize"] [dependencies.windows] version = "0.*" diff --git a/futuremod_engine/src/plugins/library/dangerous/memory.rs b/futuremod_engine/src/plugins/library/dangerous/memory.rs index 2cfadce..252fd0f 100644 --- a/futuremod_engine/src/plugins/library/dangerous/memory.rs +++ b/futuremod_engine/src/plugins/library/dangerous/memory.rs @@ -1,6 +1,6 @@ use futuremod_hook::types::{Type, MAX_STRING}; use log::debug; -use mlua::{AnyUserDataExt, Lua}; +use mlua::{Lua, ObjectLike}; use crate::plugins::library::LuaResult; @@ -15,8 +15,8 @@ fn try_userdata_to_bytes(userdata: &mlua::AnyUserData) -> LuaResult> { /// **Very unsafe**. /// /// Wrong usage can easily lead to a panic. -pub fn write_memory_function<'lua>( - _: &'lua Lua, +pub fn write_memory_function( + _: &Lua, (address, data): (u32, mlua::Value), ) -> Result<(), mlua::Error> { debug!("Write memory to {}, value: {:?}", address, data); @@ -91,10 +91,10 @@ pub fn write_memory_function<'lua>( } /// Read any memory address and convert it to the given type in lua. -pub fn read_memory_function<'lua>( - lua: &'lua Lua, +pub fn read_memory_function( + lua: &Lua, (address, type_name): (u32, String), -) -> Result, mlua::Error> { +) -> Result { debug!("Read memory address {} with type {}", address, type_name); let value_type = match Type::try_from_str(type_name.as_str()) { Some(t) => t, diff --git a/futuremod_engine/src/plugins/library/dangerous/mod.rs b/futuremod_engine/src/plugins/library/dangerous/mod.rs index a8252e9..9baebbc 100644 --- a/futuremod_engine/src/plugins/library/dangerous/mod.rs +++ b/futuremod_engine/src/plugins/library/dangerous/mod.rs @@ -10,7 +10,7 @@ mod native; use futuremod_hook::lua::hook_function; use memory::*; -pub fn create_dangerous_library(lua: Arc) -> Result { +pub fn create_dangerous_library(lua: Arc) -> Result { let table = lua.create_table()?; let hook_fn = lua.create_function(hook_function)?; @@ -38,5 +38,5 @@ pub fn create_dangerous_library(lua: Arc) -> Result>(methods: &mut M) { + fn add_methods>(methods: &mut M) { methods.add_meta_function( MetaMethod::Index, |lua, (native_struct_userdata, field): (AnyUserData, String)| - -> Result, mlua::Error> { - let native_struct: Ref = + -> Result { + let native_struct: UserDataRef = native_struct_userdata.borrow().map_err(|_| { mlua::Error::RuntimeError( "Self must be a native struct definition".to_string(), @@ -73,7 +73,7 @@ impl UserData for NativeStruct { // Call the `getByteSize` method of the value to get the expected amount of byte the type allocates let byte_size = complex_type - .call_method::<_, u32>("getByteSize", ()) + .call_method::("getByteSize", ()) .map_err(|e| { mlua::Error::RuntimeError(format!( "getByteSize method errored: {}", @@ -95,13 +95,13 @@ impl UserData for NativeStruct { // Call the type's 'fromBytes' function to construct an instance of the type from the bytes let f = complex_type - .get::<_, mlua::Function>("fromBytes") + .get::("fromBytes") .map_err(|_| { mlua::Error::RuntimeError( "Type userdata is missing 'fromBytes' function".to_string(), ) })?; - let value = f.call::<_, mlua::Value>((complex_type, byte_vec))?; + let value = f.call::((complex_type, byte_vec))?; Ok(value) } @@ -114,7 +114,7 @@ impl UserData for NativeStruct { |_, (native_struct_userdata, field, value): (AnyUserData, String, mlua::Value)| -> Result<(), mlua::Error> { - let native_struct: Ref = native_struct_userdata.borrow()?; + let native_struct: UserDataRef = native_struct_userdata.borrow()?; debug!( "Set field {} of struct at 0x{:x} to {:?}", @@ -213,7 +213,7 @@ impl UserData for NativeStruct { })?; let bytes = complex_type - .call_method::>("toBytes", value) + .call_method::>("toBytes", value) .map_err(|e| { mlua::Error::RuntimeError(format!( "toBytes function of complex type errored: {}", @@ -258,12 +258,12 @@ pub struct NativeStructDefinition { fields: HashMap, } -fn native_struct_from_definition<'a>( - lua: &'a Lua, +fn native_struct_from_definition( + lua: &Lua, address: u32, - definition_userdata: AnyUserData<'a>, -) -> LuaResult> { - let definition: Ref = definition_userdata.borrow()?; + definition_userdata: AnyUserData, +) -> LuaResult { + let definition: UserDataRef = definition_userdata.borrow()?; let fields = &definition.fields; let mut struct_fields: HashMap = HashMap::new(); @@ -301,22 +301,22 @@ fn native_struct_from_definition<'a>( } impl UserData for NativeStructDefinition { - fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) { + fn add_methods>(methods: &mut M) { methods.add_function( "cast", |lua, (definition, address): (AnyUserData, u32)| - -> Result, mlua::Error> { + -> Result { native_struct_from_definition(lua, address, definition) }, ); } } -pub fn create_native_struct_definition_fn<'lua>( - lua: &'lua Lua, - fields: mlua::Table<'lua>, -) -> Result, mlua::Error> { +pub fn create_native_struct_definition_fn( + lua: &Lua, + fields: mlua::Table, +) -> Result { debug!("Creating native struct def"); let mut native_fields: HashMap = HashMap::new(); @@ -340,7 +340,7 @@ pub fn create_native_struct_definition_fn<'lua>( })?; let native_type: FieldDefinitionType = match native_type_id.type_name() { "string" => match native_type_id.as_str() { - Some(native_type_str) => match Type::try_from_str(native_type_str) { + Some(native_type_str) => match Type::try_from_str(&native_type_str) { Some(value) => FieldDefinitionType::Primitive(value), None => return Err(mlua::Error::runtime("Unsupported type")), }, @@ -348,11 +348,11 @@ pub fn create_native_struct_definition_fn<'lua>( }, "userdata" => match native_type_id.as_userdata() { Some(userdata) => { - userdata.get::<_, mlua::Function>("toBytes").map_err(|_| { + userdata.get::("toBytes").map_err(|_| { mlua::Error::runtime("Complex type is missing function 'toBytes'") })?; userdata - .get::<_, mlua::Function>("fromBytes") + .get::("fromBytes") .map_err(|_| { mlua::Error::runtime("Complex type is missing function 'fromBytes'") })?; @@ -389,7 +389,7 @@ pub fn create_native_struct_definition_fn<'lua>( Err(_) => return Err(mlua::Error::runtime("Field definition must be table")), }; - let field_definition_type = field_definition.get::<_, mlua::Value>("type")?; + let field_definition_type = field_definition.get::("type")?; let field_definition_type_type_name = field_definition_type.type_name(); if field_definition_type_type_name == "userdata" { @@ -401,10 +401,10 @@ pub fn create_native_struct_definition_fn<'lua>( Ok(definition_userdata) } -pub fn create_native_struct_fn<'lua>( - lua: &'lua Lua, - (address, definition_userdata): (u32, AnyUserData<'lua>), -) -> Result, mlua::Error> { +pub fn create_native_struct_fn( + lua: &Lua, + (address, definition_userdata): (u32, AnyUserData), +) -> Result { debug!("Create new native struct at 0x{:x}", address); native_struct_from_definition(lua, address, definition_userdata) diff --git a/futuremod_engine/src/plugins/library/game.rs b/futuremod_engine/src/plugins/library/game.rs index f55d446..7784589 100644 --- a/futuremod_engine/src/plugins/library/game.rs +++ b/futuremod_engine/src/plugins/library/game.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use log::debug; -use mlua::{FromLua, IntoLua, Lua, LuaSerdeExt, OwnedTable, UserData}; +use mlua::{FromLua, IntoLua, Lua, LuaSerdeExt, Table, UserData}; use serde::Serialize; use crate::futurecop::{self, global::GetterSetter, state::FUTURE_COP, PLAYER_ARRAY_ADDR}; @@ -44,7 +44,7 @@ struct PlayerEntity { } impl UserData for PlayerEntity { - fn add_fields<'lua, F: mlua::prelude::LuaUserDataFields<'lua, Self>>(fields: &mut F) { + fn add_fields>(fields: &mut F) { fields.add_field_method_get("health", |_, this| { Ok(unsafe { (*this.player_entity).health.health }) }); @@ -102,13 +102,13 @@ impl UserData for PlayerEntity { Ok(()) }); - fn create_getter_setter<'lua, T, F>( + fn create_getter_setter( name: &str, fields: &mut F, extractor: fn(*mut futurecop::PlayerEntity) -> *mut T, ) where - F: mlua::prelude::LuaUserDataFields<'lua, PlayerEntity>, - T: IntoLua<'lua> + FromLua<'lua> + 'static + Copy, + F: mlua::prelude::LuaUserDataFields, + T: IntoLua + FromLua + 'static + Copy, { fields.add_field_method_get(name, move |_, this| { let field: T; @@ -190,7 +190,7 @@ impl UserData for PlayerEntity { }); } - fn add_methods<'lua, M: mlua::prelude::LuaUserDataMethods<'lua, Self>>(methods: &mut M) { + fn add_methods>(methods: &mut M) { methods.add_method("getMaxHealth", |_, this, ()| { Ok(unsafe { (*this.player_entity).health.max_health }) }) @@ -213,7 +213,7 @@ impl GameState { } } -pub fn create_game_library(lua: Arc) -> Result { +pub fn create_game_library(lua: Arc) -> Result { let functions = lua.create_table()?; let get_game_state = lua.create_function(|lua, ()| { @@ -248,5 +248,5 @@ pub fn create_game_library(lua: Arc) -> Result { })?; functions.set("getPlayer", get_player)?; - Ok(functions.into_owned()) + Ok(functions) } diff --git a/futuremod_engine/src/plugins/library/input.rs b/futuremod_engine/src/plugins/library/input.rs index 0a08601..ff9f99d 100644 --- a/futuremod_engine/src/plugins/library/input.rs +++ b/futuremod_engine/src/plugins/library/input.rs @@ -2,7 +2,7 @@ use std::{str::FromStr, sync::Arc}; use device_query::Keycode; use log::*; -use mlua::{Lua, OwnedTable}; +use mlua::{Lua, Table}; use crate::input::KeyState; @@ -126,7 +126,7 @@ fn insert_keycode(table: &mlua::Table, code: Keycode) -> Result<(), mlua::Error> table.set(code.clone(), code) } -pub fn create_input_library(lua: Arc) -> Result { +pub fn create_input_library(lua: Arc) -> Result { let library = lua.create_table()?; // Insert supported key codes into library table. @@ -156,5 +156,5 @@ pub fn create_input_library(lua: Arc) -> Result { })?; library.set("isKeyPressed", is_key_pressed_function)?; - Ok(library.into_owned()) + Ok(library) } diff --git a/futuremod_engine/src/plugins/library/matrix.rs b/futuremod_engine/src/plugins/library/matrix.rs index 3607036..78559ce 100644 --- a/futuremod_engine/src/plugins/library/matrix.rs +++ b/futuremod_engine/src/plugins/library/matrix.rs @@ -8,16 +8,17 @@ use std::{ }; use log::info; -use mlua::{AnyUserData, FromLua, IntoLua, Lua, MetaMethod, OwnedTable, UserData, UserDataMethods}; +use mlua::{AnyUserData, FromLua, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, UserDataRef}; use nalgebra::{DMatrix, Matrix4, Scalar, Vector3}; use num::{ traits::{FromBytes, ToBytes}, Num, One, Zero, }; +use windows::System::User; use super::LuaResult; -pub fn create_matrix_library(lua: Arc) -> Result { +pub fn create_matrix_library(lua: Arc) -> Result { let table = lua.create_table()?; // Float-based dynamic matrix @@ -45,7 +46,7 @@ pub fn create_matrix_library(lua: Arc) -> Result { table.set("ModelMatrix", lua.create_proxy::()?)?; table.set("newModel", lua.create_function(create_model_matrix)?)?; - Ok(table.into_owned()) + Ok(table) } /// Trait for types that encapsulate their data in an arc and mutex. @@ -116,8 +117,8 @@ impl LuaMatrix { } } -impl<'a, T: 'static> FromLua<'a> for LuaMatrix { - fn from_lua(value: mlua::Value<'a>, lua: &'a Lua) -> mlua::Result { +impl FromLua for LuaMatrix { + fn from_lua(value: mlua::Value, lua: &Lua) -> mlua::Result { try_from_userdata::>(value, lua) } } @@ -126,7 +127,7 @@ impl<'a, T: 'static> FromLua<'a> for LuaMatrix { /// If value is a userdata of type T, this function returns a clone of the userdata. /// /// Errors if the given lua value is not a userdata and if the userdata is not of type T. -fn try_from_userdata<'a, T: 'static>(value: mlua::Value<'a>, _: &'a Lua) -> mlua::Result +fn try_from_userdata(value: mlua::Value, _: &Lua) -> mlua::Result where T: Clone, { @@ -139,7 +140,7 @@ where return Err(mlua::Error::RuntimeError("Not a matrix".to_string())); } - let m: Ref = userdata.borrow()?; + let m: UserDataRef = userdata.borrow()?; Ok(m.clone()) } @@ -147,8 +148,8 @@ where impl< T: Num + Copy - + for<'a> IntoLua<'a> - + for<'a> FromLua<'a> + + for<'a> IntoLua + + for<'a> FromLua + fmt::Debug + AddAssign + 'static @@ -156,7 +157,7 @@ impl< + MulAssign, > UserData for LuaMatrix { - fn add_fields<'lua, F: mlua::UserDataFields<'lua, Self>>(fields: &mut F) { + fn add_fields>(fields: &mut F) { fields.add_field_method_get("ncols", |_, matrix| { matrix.with_matrix(|matrix| Ok(matrix.ncols())) }); @@ -166,10 +167,10 @@ impl< }); } - fn add_methods<'lua, M>(methods: &mut M) + fn add_methods(methods: &mut M) where M: Sized, - M: UserDataMethods<'lua, LuaMatrix>, + M: UserDataMethods>, { methods.add_method("at", |_, matrix, (row, col): (u8, u8)| -> LuaResult { matrix.check_bounds(row, col)?; @@ -232,8 +233,8 @@ impl< } /// Create zero matrix -fn create_zero_matrix<'lua, T: Scalar + Zero>( - _: &'lua Lua, +fn create_zero_matrix( + _: &Lua, (rows, columns): (u8, u8), ) -> LuaResult> { Ok(LuaMatrix(Arc::new(Mutex::new(DMatrix::::zeros( @@ -243,8 +244,8 @@ fn create_zero_matrix<'lua, T: Scalar + Zero>( } /// Create identify matrix -fn create_identity_matrix<'lua, T: Scalar + Zero + One>( - _: &'lua Lua, +fn create_identity_matrix( + _: &Lua, size: u8, ) -> LuaResult> { let size = size as usize; @@ -269,7 +270,7 @@ fn create_identity_matrix<'lua, T: Scalar + Zero + One>( /// ``` /// /// All rows must have the same length, otherwise, this function panics. -fn create_matrix<'lua, T: Scalar>(_: &'lua Lua, data: Vec>) -> LuaResult> { +fn create_matrix(_: &Lua, data: Vec>) -> LuaResult> { let rows = data.len(); let cols = data.iter().map(|r| r.len()).fold(0, |l, r| l.max(r)); let data: Vec = data.into_iter().flatten().collect(); @@ -309,11 +310,11 @@ impl< + AddAssign + MulAssign + fmt::Debug - + for<'a> IntoLua<'a> - + for<'a> FromLua<'a>, + + for<'a> IntoLua + + for<'a> FromLua, > UserData for MatrixType { - fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + fn add_methods>(methods: &mut M) { methods.add_function("new", |_, (nrows, ncols): (u32, u32)| { Ok(MatrixType::::new(nrows, ncols)) }); @@ -465,14 +466,14 @@ impl ModelMatrix { } } -impl<'a> FromLua<'a> for ModelMatrix { - fn from_lua(value: mlua::Value<'a>, lua: &'a Lua) -> mlua::Result { +impl FromLua for ModelMatrix { + fn from_lua(value: mlua::Value, lua: &Lua) -> mlua::Result { try_from_userdata::(value, lua) } } impl UserData for ModelMatrix { - fn add_fields<'lua, F: mlua::UserDataFields<'lua, Self>>(fields: &mut F) { + fn add_fields>(fields: &mut F) { fields.add_field_method_get("ncols", |_, matrix| { matrix.with_matrix(|matrix| Ok(matrix.ncols())) }); @@ -482,7 +483,7 @@ impl UserData for ModelMatrix { }); } - fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + fn add_methods>(methods: &mut M) { methods.add_method("at", |_, matrix, (row, col): (u8, u8)| -> LuaResult { matrix.check_bounds(row, col)?; @@ -523,7 +524,7 @@ impl UserData for ModelMatrix { methods.add_function( "toBytes", |_, (_, matrix): (AnyUserData, AnyUserData)| -> LuaResult> { - let matrix: Ref = matrix.borrow()?; + let matrix: UserDataRef = matrix.borrow()?; matrix.with_matrix(|matrix| { let mut bytes = Vec::::new(); diff --git a/futuremod_engine/src/plugins/library/mod.rs b/futuremod_engine/src/plugins/library/mod.rs index 20b64db..25c97f3 100644 --- a/futuremod_engine/src/plugins/library/mod.rs +++ b/futuremod_engine/src/plugins/library/mod.rs @@ -4,5 +4,6 @@ pub mod input; pub mod matrix; pub mod system; pub mod ui; +pub mod settings; type LuaResult = Result; diff --git a/futuremod_engine/src/plugins/library/system.rs b/futuremod_engine/src/plugins/library/system.rs index 03f2e35..190333f 100644 --- a/futuremod_engine/src/plugins/library/system.rs +++ b/futuremod_engine/src/plugins/library/system.rs @@ -3,9 +3,9 @@ use std::{ time::{SystemTime, UNIX_EPOCH}, }; -use mlua::{Lua, OwnedTable}; +use mlua::{Lua, Table}; -pub fn create_system_library(lua: Arc) -> Result { +pub fn create_system_library(lua: Arc) -> Result { let library = lua.create_table()?; let get_time_fn = lua.create_function(|_, ()| { @@ -21,5 +21,5 @@ pub fn create_system_library(lua: Arc) -> Result { })?; library.set("getTime", get_time_fn)?; - Ok(library.into_owned()) + Ok(library) } diff --git a/futuremod_engine/src/plugins/library/ui.rs b/futuremod_engine/src/plugins/library/ui.rs index 5d22a66..f2b663a 100644 --- a/futuremod_engine/src/plugins/library/ui.rs +++ b/futuremod_engine/src/plugins/library/ui.rs @@ -1,13 +1,13 @@ use std::sync::Arc; -use mlua::{Lua, LuaSerdeExt, OwnedTable, Value}; +use mlua::{Lua, LuaSerdeExt, Table, Value}; use crate::api::{ self, ui::{Color, TextPalette, TEXT_PALETTES}, }; -pub fn create_ui_library(lua: Arc) -> Result { +pub fn create_ui_library(lua: Arc) -> Result { let library = lua.create_table()?; let render_text = lua.create_function( @@ -43,5 +43,5 @@ pub fn create_ui_library(lua: Arc) -> Result { library.set(format!("Palette{}", palette), Into::::into(palette))?; } - Ok(library.into_owned()) + Ok(library) } diff --git a/futuremod_engine/src/plugins/plugin.rs b/futuremod_engine/src/plugins/plugin.rs index bf4856a..4b920dc 100644 --- a/futuremod_engine/src/plugins/plugin.rs +++ b/futuremod_engine/src/plugins/plugin.rs @@ -1,7 +1,7 @@ -use super::plugin_environment::PluginEnvironment; +use super::{library::settings::{get_settings, PluginSettings}, plugin_environment::PluginEnvironment}; use futuremod_data::plugin::{PluginError, PluginInfo}; use log::*; -use mlua::{Function, Lua, OwnedFunction, Table}; +use mlua::{Function, Lua, Table}; use serde::{ser::SerializeStruct, Serialize}; use std::{fs, path::PathBuf, sync::Arc}; @@ -70,13 +70,13 @@ impl Into for PluginState { #[derive(Debug, Clone)] pub struct PluginContext { environment: PluginEnvironment, - on_load: Option, - on_unload: Option, - on_update: Option, - on_enable: Option, - on_disable: Option, - on_install: Option, - on_uninstall: Option, + on_load: Option, + on_unload: Option, + on_update: Option, + on_enable: Option, + on_disable: Option, + on_install: Option, + on_uninstall: Option, } impl Into for PluginContext { @@ -93,7 +93,7 @@ impl Into for PluginContext { } } -fn optional_lua_function_to_string(fun: &Option) -> &'static str { +fn optional_lua_function_to_string(fun: &Option) -> &'static str { if fun.is_some() { "set" } else { @@ -201,13 +201,13 @@ impl Plugin { } }; - let on_load = get_lua_function_or_none(&environment.table.to_ref(), "onLoad"); - let on_unload = get_lua_function_or_none(&environment.table.to_ref(), "onUnload"); - let on_update = get_lua_function_or_none(&environment.table.to_ref(), "onUpdate"); - let on_enable = get_lua_function_or_none(&environment.table.to_ref(), "onEnable"); - let on_disable = get_lua_function_or_none(&environment.table.to_ref(), "onDisable"); - let on_install = get_lua_function_or_none(&environment.table.to_ref(), "onInstall"); - let on_uninstall = get_lua_function_or_none(&environment.table.to_ref(), "onUninstall"); + let on_load = get_lua_function_or_none(&environment.table, "onLoad"); + let on_unload = get_lua_function_or_none(&environment.table, "onUnload"); + let on_update = get_lua_function_or_none(&environment.table, "onUpdate"); + let on_enable = get_lua_function_or_none(&environment.table, "onEnable"); + let on_disable = get_lua_function_or_none(&environment.table, "onDisable"); + let on_install = get_lua_function_or_none(&environment.table, "onInstall"); + let on_uninstall = get_lua_function_or_none(&environment.table, "onUninstall"); let context = PluginContext { environment, @@ -222,7 +222,7 @@ impl Plugin { debug!("Execute onLoad function"); match &context.on_load { - Some(main) => match main.call::<_, ()>(()) { + Some(main) => match main.call::<()>(()) { Ok(_) => debug!("Successfully called onLoad"), Err(e) => { warn!("Main function threw error: {:?}", e); @@ -375,14 +375,14 @@ impl Plugin { pub fn is_enabled(&self) -> bool { self.enabled } -} -fn get_lua_function_or_none<'lua>(module: &'lua Table, name: &str) -> Option { - match module.get::<&str, Function>(name) { + +fn get_lua_function_or_none(module: &Table, name: &str) -> Option { + match module.get::(name) { Ok(function) => { debug!("Module {:?} has attribute '{}'", module, name); - Some(function.into_owned()) + Some(function) } Err(_) => { debug!("Module {:?} has no attribute '{}'", module, name); diff --git a/futuremod_hook/Cargo.toml b/futuremod_hook/Cargo.toml index 18ff0fd..f4f4af7 100644 --- a/futuremod_hook/Cargo.toml +++ b/futuremod_hook/Cargo.toml @@ -27,5 +27,5 @@ features = [ ] [dependencies.mlua] -version = "0.9.1" -features = ["luau", "async", "serialize", "unstable"] +version = "0.10.0" +features = ["luau", "async", "serialize"] diff --git a/futuremod_hook/src/lua.rs b/futuremod_hook/src/lua.rs index 79d6b3e..ebb53e9 100644 --- a/futuremod_hook/src/lua.rs +++ b/futuremod_hook/src/lua.rs @@ -10,8 +10,8 @@ use crate::native::{memory_copy, Hook}; use crate::types::{lua_to_native, lua_to_native_implied, native_to_lua, Type}; /// Create a hook on any function with a given lua function. -pub fn hook_function<'lua>( - lua: &'lua Lua, +pub fn hook_function( + lua: &Lua, (address, arg_type_names, return_type_name, callback): (u32, Vec, String, Function), ) -> Result { debug!( @@ -72,12 +72,12 @@ pub fn hook_function<'lua>( // 1. Convert the arguments from lua values into native values // 2. Call the original function with the arguments // 3. Convert the return value back to a lua value and return it - let original_wrapper = match lua.create_function::<_, mlua::Value, _>( + let original_wrapper = match lua.create_function::<_, _, mlua::Value>( move |lua, args: MultiValue| { debug!("Lua called original function"); // Convert the arguments from lua values into actual native values. - let lua_args = args.into_vec(); + let lua_args: Vec<&mlua::Value> = args.iter().collect(); let mut converted_lua_args: Vec = Vec::new(); @@ -163,7 +163,7 @@ pub fn hook_function<'lua>( for i in 0..argument_types.len() { let arg_type = argument_types[i]; - match native_to_lua(lua, arg_type, *arg_pointer.byte_offset(i as isize * 4)) { + match native_to_lua(&lua, arg_type, *arg_pointer.byte_offset(i as isize * 4)) { Ok(value) => callback_args.push(value), Err(e) => { warn!( @@ -177,7 +177,7 @@ pub fn hook_function<'lua>( // Call the lua hook let return_value = - match callback.call::<_, mlua::Value>(mlua::MultiValue::from_vec(callback_args)) { + match callback.call::(mlua::MultiValue::from_iter(callback_args)) { Ok(value) => value, Err(e) => { warn!("Lua hook threw error: {:?}. Panicking...", e); @@ -243,12 +243,12 @@ impl NativeFunction { } } - pub fn call<'lua>( + pub fn call( &self, - lua: &'lua Lua, + lua: &Lua, args: mlua::MultiValue, - ) -> Result, mlua::Error> { - let args = args.into_vec(); + ) -> Result { + let args = args.iter().collect::>(); debug!( "Calling function at address {:x} with ({:?}), expecting return type {:?}", @@ -312,7 +312,7 @@ impl NativeFunction { } impl UserData for NativeFunction { - fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) { + fn add_methods>(methods: &mut M) { methods.add_method("getAddress", |_, native_function, ()| { return Ok(native_function.address); }); @@ -324,8 +324,8 @@ impl UserData for NativeFunction { } } -pub fn create_native_function_function<'lua>( - lua: &'lua Lua, +pub fn create_native_function_function( + lua: &Lua, (arg_types, return_type, lua_fn): (Vec, String, mlua::Function), ) -> Result { debug!( @@ -385,7 +385,7 @@ pub fn create_native_function_function<'lua>( } } - let return_value = match lua_fn.call::<_, mlua::Value>(mlua::MultiValue::from_vec(lua_args)) + let return_value = match lua_fn.call::(mlua::MultiValue::from_iter(lua_args)) { Ok(value) => value, Err(e) => { @@ -536,8 +536,8 @@ pub fn create_native_function_function<'lua>( } } -pub fn get_native_function<'lua>( - _: &'lua Lua, +pub fn get_native_function( + _: &Lua, (address, arg_types, return_type): (u32, Vec, String), ) -> Result { let mut lua_arg_types: Vec = Vec::new(); diff --git a/futuremod_hook/src/native.rs b/futuremod_hook/src/native.rs index c433a3e..30dc48f 100644 --- a/futuremod_hook/src/native.rs +++ b/futuremod_hook/src/native.rs @@ -723,7 +723,7 @@ impl Hook { } impl UserData for Hook { - fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) { + fn add_methods>(methods: &mut M) { methods.add_method_mut("unhook", |_, this, ()| { unsafe { this.unhook() diff --git a/futuremod_hook/src/types.rs b/futuremod_hook/src/types.rs index deba5c5..b44d81b 100644 --- a/futuremod_hook/src/types.rs +++ b/futuremod_hook/src/types.rs @@ -37,11 +37,11 @@ impl Type { pub const MAX_STRING: u16 = 1024; /// Convert a native value into its lua value given the type name. -pub unsafe fn native_to_lua<'a>( - lua: &'a Lua, +pub unsafe fn native_to_lua( + lua: &Lua, lua_type: Type, raw_value: u32, -) -> Result, mlua::Error> { +) -> Result { let value = match lua_type { Type::String => { let mut string_bytes: Vec = Vec::new(); From 816086062f1ff35a1fa6f0ff0d09e4d8f4bec461 Mon Sep 17 00:00:00 2001 From: Simon Kurz Date: Sat, 9 Nov 2024 08:41:39 +0100 Subject: [PATCH 02/10] Implement initial settings library --- examples/settings/info.toml | 10 + examples/settings/main.lua | 17 ++ futuremod_data/src/plugin.rs | 2 + .../src/plugins/library/settings/mod.rs | 268 ++++++++++++++++++ futuremod_engine/src/plugins/plugin.rs | 14 + .../src/plugins/plugin_environment.rs | 151 +++++----- .../src/plugins/plugin_manager.rs | 14 + futuremod_engine/src/server.rs | 32 ++- 8 files changed, 433 insertions(+), 75 deletions(-) create mode 100644 examples/settings/info.toml create mode 100644 examples/settings/main.lua create mode 100644 futuremod_engine/src/plugins/library/settings/mod.rs diff --git a/examples/settings/info.toml b/examples/settings/info.toml new file mode 100644 index 0000000..dbf01ec --- /dev/null +++ b/examples/settings/info.toml @@ -0,0 +1,10 @@ +name = "Settings Test" +version = "0.0.1" +authors = ["Simon Kurz"] +dependencies = ["settings", "system"] +description = """\ +# Settings Test +This plugin tests the settings function. + +It does nothing more than registering some settings and logging changes to them. +""" diff --git a/examples/settings/main.lua b/examples/settings/main.lua new file mode 100644 index 0000000..0dbcfc7 --- /dev/null +++ b/examples/settings/main.lua @@ -0,0 +1,17 @@ +local settings = require("settings") + +function onLoad() + createSettings() + + print(`Creation took {diff} ms`) +end + +function createSettings() + settings:create({ + settings.Text("Example Settings"), + settings.Text("By clicking the button below, you should be able to trigger some code of the plugin. How neat!!!"), + settings.Button("Click Me"):onClick(function() + print("Clicked") + end) + }) +end \ No newline at end of file diff --git a/futuremod_data/src/plugin.rs b/futuremod_data/src/plugin.rs index 777f647..401c7c4 100644 --- a/futuremod_data/src/plugin.rs +++ b/futuremod_data/src/plugin.rs @@ -12,6 +12,7 @@ pub enum PluginDependency { UI, System, Matrix, + Settings, // The following libraries are from the standard library Math, @@ -35,6 +36,7 @@ impl Display for PluginDependency { PluginDependency::String => f.write_str("String"), PluginDependency::Utf8 => f.write_str("Utf8"), PluginDependency::Matrix => f.write_str("Matrix"), + PluginDependency::Settings => f.write_str("Settings"), } } } diff --git a/futuremod_engine/src/plugins/library/settings/mod.rs b/futuremod_engine/src/plugins/library/settings/mod.rs new file mode 100644 index 0000000..a1ee6d4 --- /dev/null +++ b/futuremod_engine/src/plugins/library/settings/mod.rs @@ -0,0 +1,268 @@ +use std::{collections::{HashMap, HashSet}, sync::Arc}; + +use log::{debug, info}; +use mlua::{AnyUserData, Lua, Table, UserData}; +use serde::{Deserialize, Serialize}; +use anyhow::anyhow; + +use super::LuaResult; + +#[derive(Debug, Clone)] +pub struct PluginSettingsLibrary { + settings: Option, +} + +impl PluginSettingsLibrary { + pub fn new() -> Self { + PluginSettingsLibrary{settings: None} + } +} + +impl UserData for PluginSettingsLibrary { + fn add_methods>(methods: &mut M) { + methods.add_method_mut("create", |_lua, settings_library, components: Table| { + let mut setting_components = Vec::::new(); + + for pair in components.pairs::() { + let (_key, value) = pair?; + let component = value.as_userdata().ok_or(mlua::Error::RuntimeError("invalid component".into()))?; + + if component.is::() { + debug!("Got button builder component"); + + let builder = component.borrow::()?; + setting_components.push(SettingsComponent::Button(builder.clone())); + } else if component.is::() { + debug!("Got text compoment"); + + let builder = component.borrow::()?; + setting_components.push(SettingsComponent::Text(builder.clone())); + } else { + debug!("Got unknown compomnent: {:?}", value); + } + } + + let settings = PluginSettings{components: setting_components}; + + settings_library.settings = Some(settings); + + Ok(()) + }); + + methods.add_function("Button", create_button); + methods.add_function("Text", create_text); + } +} + +pub fn create_settings_library_new(_lua: Arc) -> Result { + Ok(PluginSettingsLibrary::new()) +} + +pub fn create_settings_library(lua: Arc) -> Result { + let library = lua.create_table()?; + library.set("create", lua.create_function(create_settings)?)?; + library.set("Button", lua.create_function(create_button)?)?; + library.set("Text", lua.create_function(create_text)?)?; + + Ok(library) +} + +pub fn create_settings_table(lua: &Lua) -> Result { + let library = lua.create_table()?; + library.set("create", lua.create_function(create_settings)?)?; + library.set("Button", lua.create_function(create_button)?)?; + library.set("Text", lua.create_function(create_text)?)?; + + Ok(mlua::Value::Table(library)) +} + +#[derive(Debug, Clone, Serialize)] +#[serde(tag = "type", rename_all = "camelCase")] +pub enum SettingsComponent { + Button(ButtonComponent), + Section(SectionComponent), + Text(TextComponent), +} + +#[derive(Debug, Clone, Serialize)] +pub struct PluginSettings { + pub components: Vec, +} + +impl UserData for PluginSettings {} + +fn create_settings(lua: &Lua, components: Table) -> LuaResult<()> { + debug!("Create settings from: {:?}", components); + + let mut setting_components = Vec::::new(); + + for pair in components.pairs::() { + let (_key, value) = pair?; + let component = value.as_userdata().ok_or(mlua::Error::RuntimeError("invalid component".into()))?; + + if component.is::() { + debug!("Got button builder component"); + + let builder = component.borrow::()?; + setting_components.push(SettingsComponent::Button(builder.clone())); + } else if component.is::() { + debug!("Got text compoment"); + + let builder = component.borrow::()?; + setting_components.push(SettingsComponent::Text(builder.clone())); + } else if component.is::() { + debug!("Got section compoment"); + + let builder = component.borrow::()?; + setting_components.push(SettingsComponent::Section(builder.clone())); + } else { + debug!("Got unknown compomnent: {:?}", value); + } + } + + let settings = PluginSettings{components: setting_components}; + + debug!("Created setting: {:#?}", settings); + + let globals = lua.globals(); + globals.set("_settings", settings)?; + debug!("Stored settings in plugin globals"); + + let other_globals = lua.globals(); + debug!("Stored settings: {:#?}", other_globals.get::("_settings")); + + lua.globals().for_each(|key: String, value: mlua::Value| { + debug!("Setting Global: {key} -> {value:#?}"); + Ok(()) + })?; + + Ok(()) +} + +pub fn get_settings(context: &HashMap<&'static str, mlua::Value>) -> Result, anyhow::Error> { + let has_settings = context.contains_key("settings"); + + if !has_settings { + debug!("Plugin has no settings in globals"); + return Ok(None); + } + + debug!("Settings key found in plugin's globals"); + + let settings_global_value: &mlua::Value = context.get("settings") + .ok_or(anyhow!("Could not access settings global in plugin context"))?; + + let settings_global = settings_global_value.as_userdata() + .ok_or(anyhow!("Global settings has invalid format"))?; + + debug!("Got settings as userdata from globals"); + + let settings = settings_global.borrow::() + .map_err(|e| anyhow!("Plugin context does not contain valid setting: {e}"))?; + + debug!("Could convert settings userdata to settings struct"); + + Ok(settings.settings.clone()) +} + +#[derive(Debug, Clone, Serialize)] +pub struct ButtonComponent { + text: String, + disabled: bool, + #[serde(skip)] + on_click: Option, + id: Option, +} + +fn create_button(_: &Lua, text: String) -> LuaResult { + Ok(ButtonComponent::new(text)) +} + +impl ButtonComponent { + pub fn new(text: String) -> ButtonComponent { + debug!("Create button with text '{}'", text); + + ButtonComponent { + text, + disabled: false, + on_click: None, + id: None, + } + } +} + +impl UserData for ButtonComponent { + fn add_methods>(methods: &mut M) { + methods.add_function("withText", |_, (builder, text): (AnyUserData, String)| { + debug!("Change button text to '{}'", text); + let builder = builder.borrow::()?; + let mut new_buider = builder.clone(); + new_buider.text = text; + Ok(new_buider) + }); + + methods.add_function("withDisabled", |_, (builder, value): (AnyUserData, bool)| { + debug!("Change button disabled to '{}'", value); + let builder = builder.borrow::()?; + let mut new_buider = builder.clone(); + new_buider.disabled = value; + Ok(new_buider) + }); + + methods.add_function("withID", |_, (builder, id): (AnyUserData, String)| { + debug!("Change button ID to {}", id); + let builder = builder.borrow::()?; + let mut new_buider = builder.clone(); + new_buider.id = Some(id); + Ok(new_buider) + }); + + methods.add_function("onClick", |_, (builder, cb): (AnyUserData, mlua::Function)| { + debug!("Change button on click listener to '{:?}'", cb); + let builder = builder.borrow::()?; + let mut new_buider = builder.clone(); + new_buider.on_click = Some(cb); + Ok(new_buider) + }); + } +} + +#[derive(Debug, Clone, Serialize)] +pub struct SectionComponent { + content: Vec +} + +impl SectionComponent { + pub fn new(content: Vec) -> SectionComponent { + SectionComponent{content} + } +} + +impl UserData for SectionComponent {} + + +#[derive(Debug, Clone, Serialize)] +pub struct TextComponent { + pub text: String +} + +fn create_text(_lua: &Lua, text: String) -> LuaResult { + Ok(TextComponent::new(text)) +} + +impl TextComponent { + pub fn new(text: String) -> TextComponent { + TextComponent{text} + } +} + +impl UserData for TextComponent { + fn add_methods>(methods: &mut M) { + methods.add_function("withText", |_, (text_component, text): (AnyUserData, String)| { + let original = text_component.borrow::()?; + let mut new_component = original.clone(); + new_component.text = text; + Ok(new_component) + }); + } +} \ No newline at end of file diff --git a/futuremod_engine/src/plugins/plugin.rs b/futuremod_engine/src/plugins/plugin.rs index 4b920dc..faf3985 100644 --- a/futuremod_engine/src/plugins/plugin.rs +++ b/futuremod_engine/src/plugins/plugin.rs @@ -376,6 +376,20 @@ impl Plugin { self.enabled } + pub fn get_settings(&self) -> Result, PluginError> { + match &self.state { + PluginState::Loaded(context) => { + debug!("{:#?}", context.environment.libraries); + get_settings(&context.environment.libraries) + .map_err(|e| PluginError::Error(format!("{}", e))) + }, + _ => { + debug!("Requested plugin settings of not loaded plugin"); + Ok(None) + }, + } + } +} fn get_lua_function_or_none(module: &Table, name: &str) -> Option { match module.get::(name) { diff --git a/futuremod_engine/src/plugins/plugin_environment.rs b/futuremod_engine/src/plugins/plugin_environment.rs index 44345b6..7f65f4f 100644 --- a/futuremod_engine/src/plugins/plugin_environment.rs +++ b/futuremod_engine/src/plugins/plugin_environment.rs @@ -7,13 +7,12 @@ use std::{ }; use super::library::{ - dangerous::create_dangerous_library, game::create_game_library, input::create_input_library, - matrix::create_matrix_library, system::create_system_library, ui::create_ui_library, + dangerous::create_dangerous_library, game::create_game_library, input::create_input_library, matrix::create_matrix_library, settings::{create_settings_library, create_settings_library_new}, system::create_system_library, ui::create_ui_library }; use anyhow::bail; use futuremod_data::plugin::{PluginDependency, PluginInfo}; use log::*; -use mlua::{Lua, OwnedTable}; +use mlua::{IntoLua, Lua, Table}; /// Holds the entire plugin environment. /// @@ -24,9 +23,11 @@ use mlua::{Lua, OwnedTable}; #[derive(Clone)] pub struct PluginEnvironment { /// Plugin globals. - pub table: OwnedTable, + pub table: Table, /// Plugin package cache - package_cache: Arc>>, + package_cache: Arc>>, + + pub libraries: Arc>, } #[derive(Debug, Clone, Copy)] @@ -57,7 +58,7 @@ unsafe fn raw_to_lua<'a>( lua: &'a Lua, lua_type: Type, raw_value: u32, -) -> Result, mlua::Error> { +) -> Result { let value = match lua_type { Type::Integer => mlua::Value::Integer(raw_value as i32), Type::String => { @@ -115,27 +116,28 @@ unsafe fn lua_to_raw<'a>( fn prepare_libraries( lua: Arc, info: &PluginInfo, -) -> Result, mlua::Error> { - let mut libraries = HashMap::new(); +) -> Result, mlua::Error> { + let mut libraries: HashMap<&'static str, mlua::Value> = HashMap::new(); let globals = lua.globals(); for library in info.dependencies.iter() { match library { PluginDependency::Dangerous => { - libraries.insert("dangerous", create_dangerous_library(lua.clone())?) + libraries.insert("dangerous", mlua::Value::Table(create_dangerous_library(lua.clone())?)) } - PluginDependency::Game => libraries.insert("game", create_game_library(lua.clone())?), + PluginDependency::Game => libraries.insert("game", mlua::Value::Table(create_game_library(lua.clone())?)), PluginDependency::Input => { - libraries.insert("input", create_input_library(lua.clone())?) + libraries.insert("input", mlua::Value::Table(create_input_library(lua.clone())?)) } - PluginDependency::UI => libraries.insert("ui", create_ui_library(lua.clone())?), + PluginDependency::UI => libraries.insert("ui", mlua::Value::Table(create_ui_library(lua.clone())?)), PluginDependency::System => { - libraries.insert("system", create_system_library(lua.clone())?) + libraries.insert("system", mlua::Value::Table(create_system_library(lua.clone())?)) } PluginDependency::Matrix => { - libraries.insert("matrix", create_matrix_library(lua.clone())?) + libraries.insert("matrix", mlua::Value::Table(create_matrix_library(lua.clone())?)) } + PluginDependency::Settings => libraries.insert("settings", create_settings_library_new(lua.clone())?.into_lua(&lua)?), PluginDependency::Math => libraries.insert("math", globals.get("math").to_owned()?), PluginDependency::Bit32 => libraries.insert("bit32", globals.get("bit32").to_owned()?), PluginDependency::String => { @@ -143,6 +145,7 @@ fn prepare_libraries( } PluginDependency::Table => libraries.insert("table", globals.get("table").to_owned()?), PluginDependency::Utf8 => libraries.insert("utf8", globals.get("utf8").to_owned()?), + }; } @@ -154,7 +157,7 @@ fn link_global_by_name( src: &mlua::Table, dst: &mlua::Table, ) -> Result<(), mlua::Error> { - dst.set(name, src.get::<_, mlua::Value>(name)?) + dst.set(name, src.get::(name)?) } const DEFAULT_GLOBALS: [&str; 17] = [ @@ -196,21 +199,23 @@ impl PluginEnvironment { // Create and set functions let print_target = plugin_info.name.to_string(); let print_fn = lua.create_function(move |_, msg: mlua::Value| { - // Convert the message into a string. - // If the value cannot be converted to string, use it's debug representation - let msg = match msg.to_string() { - Ok(msg) => msg, - Err(_) => format!("{:?}", msg), - }; - let plugin_name = print_target.clone(); + // Convert the message into a string. + // If the value cannot be converted to string, use it's debug representation + let msg = match msg.to_string() { + Ok(msg) => msg, + Err(_) => format!("{:?}", msg), + }; + let plugin_name = print_target.clone(); + + info!(target: format!("plugin::{}", print_target).as_str(), plugin:% = plugin_name; "{}", msg); - info!(target: format!("plugin::{}", print_target).as_str(), plugin:% = plugin_name; "{}", msg); + Ok(()) + })?; - Ok(()) - })?; + let libraries = Arc::new(prepare_libraries(lua.clone(), &plugin_info)?); + let libraires_weak = Arc::downgrade(&libraries); - let libraries = prepare_libraries(lua.clone(), &plugin_info)?; - let package_cache: Arc>> = + let package_cache: Arc>> = Arc::new(Mutex::new(HashMap::new())); let require_fn_package_cache = Arc::downgrade(&package_cache); let plugin_info_clone = plugin_info.clone(); @@ -219,65 +224,68 @@ impl PluginEnvironment { let lua_ref = lua.clone(); let require_fn = lua.create_function(move |lua, name: String| { - debug!("Plugin '{}' required {}", plugin_name, name); + debug!("Plugin '{}' required {}", plugin_name, name); - // Check if a library with the given name exists - if let Some(library) = libraries.get(name.as_str()) { - debug!("Required name is a library"); - return Ok(library.clone()); - } + // Check if a library with the given name exists + let libraries = libraires_weak.upgrade() + .ok_or(mlua::Error::RuntimeError("Require is forbidden".into()))?; - debug!("Library doesn't exist, treating require statement as requiring a local file"); + if let Some(library) = libraries.get(name.as_str()) { + debug!("Required name is a library"); + return Ok(library.clone()); + } - // Check if the require statement should load another lua file - // Normalize the require path such that referencing the same file with a slightly different path - // will not load the same file multiple times. - // We enforce here that every require statement of a lua file is the relative path to that file - // starting from the root of the plugin. - let require_path = Path::new(&name).to_path_buf().with_extension("lua"); + debug!("Library doesn't exist, treating require statement as requiring a local file"); - debug!("Requiring file '{:?}'", require_path); + // Check if the require statement should load another lua file + // Normalize the require path such that referencing the same file with a slightly different path + // will not load the same file multiple times. + // We enforce here that every require statement of a lua file is the relative path to that file + // starting from the root of the plugin. + let require_path = Path::new(&name).to_path_buf().with_extension("lua"); - let absolute_require_path = Path::join(&plugin_path, require_path.clone()).canonicalize().map_err(|e| mlua::Error::RuntimeError(format!("Could not load library: {:?}", e)))?; + debug!("Requiring file '{:?}'", require_path); - let require_package_cache = match require_fn_package_cache.upgrade() { - Some(c) => c, - None => return Err(mlua::Error::RuntimeError("Require is forbidden: Plugin is destroyed".into())), - }; + let absolute_require_path = Path::join(&plugin_path, require_path.clone()).canonicalize().map_err(|e| mlua::Error::RuntimeError(format!("Could not load library: {:?}", e)))?; - let mut require_package_cache = require_package_cache.lock().map_err(|e| mlua::Error::RuntimeError(format!("Couldn't get lock to cache: {:?}", e)))?; + let require_package_cache = match require_fn_package_cache.upgrade() { + Some(c) => c, + None => return Err(mlua::Error::RuntimeError("Require is forbidden: Plugin is destroyed".into())), + }; - if let Some(cached_file) = require_package_cache.get(&require_path) { - debug!("Found required file in cache"); - return Ok(cached_file.clone()); - } + let mut require_package_cache = require_package_cache.lock().map_err(|e| mlua::Error::RuntimeError(format!("Couldn't get lock to cache: {:?}", e)))?; - if !absolute_require_path.starts_with(&plugin_path) { - warn!("Plugin {} required {:?} which is outside it's plugin folder", plugin_name, absolute_require_path); - return Err(mlua::Error::RuntimeError("Permission denied: Requiring a file outside of the plugin folder is not allowed".into())); - } + if let Some(cached_file) = require_package_cache.get(&require_path) { + debug!("Found required file in cache"); + return Ok(mlua::Value::Table(cached_file.clone())); + } - if !absolute_require_path.exists() { - warn!("Plugin {} required non-existing file {:?}", plugin_name, absolute_require_path); - return Err(mlua::Error::RuntimeError("Required file doesn't exist".into())); - } + if !absolute_require_path.starts_with(&plugin_path) { + warn!("Plugin {} required {:?} which is outside it's plugin folder", plugin_name, absolute_require_path); + return Err(mlua::Error::RuntimeError("Permission denied: Requiring a file outside of the plugin folder is not allowed".into())); + } + + if !absolute_require_path.exists() { + warn!("Plugin {} required non-existing file {:?}", plugin_name, absolute_require_path); + return Err(mlua::Error::RuntimeError("Required file doesn't exist".into())); + } - debug!("Preparing plugin environment for required file"); - let file_environment = PluginEnvironment::new(lua_ref.clone(), &plugin_info_clone)?; + debug!("Preparing plugin environment for required file"); + let file_environment = PluginEnvironment::new(lua_ref.clone(), &plugin_info_clone)?; - // Read the file content - let content = fs::read_to_string(&absolute_require_path).map_err(|e| mlua::Error::RuntimeError(format!("Could not require file: {:?}", e)))?; - let file_chunk = lua.load(content).set_environment(file_environment.table.clone()); + // Read the file content + let content = fs::read_to_string(&absolute_require_path).map_err(|e| mlua::Error::RuntimeError(format!("Could not require file: {:?}", e)))?; + let file_chunk = lua.load(content).set_environment(file_environment.table.clone()); - debug!("Executing required file"); - file_chunk.exec()?; + debug!("Executing required file"); + file_chunk.exec()?; - let file_globals = file_environment.table.clone(); + let file_globals = file_environment.table.clone(); - let _ = require_package_cache.insert(absolute_require_path, file_globals.clone()); + let _ = require_package_cache.insert(absolute_require_path, file_globals.clone()); - Ok(file_globals) - })?; + Ok(mlua::Value::Table(file_globals)) + })?; table.set("print", print_fn)?; table.set("require", require_fn)?; @@ -285,7 +293,8 @@ impl PluginEnvironment { add_default_globals(&table, &lua.globals())?; Ok(PluginEnvironment { - table: table.into_owned(), + table, + libraries, package_cache, }) } diff --git a/futuremod_engine/src/plugins/plugin_manager.rs b/futuremod_engine/src/plugins/plugin_manager.rs index c51d785..644bf2e 100644 --- a/futuremod_engine/src/plugins/plugin_manager.rs +++ b/futuremod_engine/src/plugins/plugin_manager.rs @@ -9,6 +9,7 @@ use std::sync::{Arc, Mutex, OnceLock}; use std::{collections::HashMap, fs}; use walkdir::WalkDir; +use super::library::settings::PluginSettings; use super::plugin::*; use super::plugin_info::PluginInfoError; use super::plugin_persistence::{PersistedPlugin, PersistedPlugins, PersistentPluginState}; @@ -421,6 +422,19 @@ impl PluginManager { return &self.plugins; } + pub fn get_plugin_settings(&self, name: &str) -> Result, PluginManagerError> { + info!("Getting settings of plugin '{}'", name); + match self.plugins.get(name) { + Some(game_plugin) => { + game_plugin.get_settings().map_err(PluginManagerError::Plugin) + } + None => { + warn!("Plugin doesn't exist"); + Err(PluginManagerError::PluginNotFound) + } + } + } + /// Install a plugin from a folder. /// /// This method will install the plugin stored at the specified `folder`. diff --git a/futuremod_engine/src/server.rs b/futuremod_engine/src/server.rs index 6f9c25e..43a8ebb 100644 --- a/futuremod_engine/src/server.rs +++ b/futuremod_engine/src/server.rs @@ -76,6 +76,7 @@ fn serve(config: Config) -> Result<(), Error> { .route("/plugin/install-dev", post(install_plugin_in_dev_mode)) .route("/plugin/uninstall", post(uninstall_plugin)) .route("/plugin/info", put(get_plugin_info)) + .route("/plugin/settings", put(get_plugin_settings)) .route("/log", get(log_handler)); axum::Server::bind( @@ -240,13 +241,13 @@ where } } -fn with_plugin_manager(f: F) -> Result +fn with_plugin_manager(f: F) -> Result where - F: Fn(&PluginManager) -> Result, + F: Fn(&PluginManager) -> R, { match GlobalPluginManager::get().lock() { - Ok(mut plugin_manager) => Ok(f(&mut plugin_manager)?), - Err(e) => Err(anyhow!("Could not get lock to plugin manager: {:?}", e)), + Ok(plugin_manager) => Ok(f(&plugin_manager)), + Err(e) => Err(AppError(anyhow!("Could not get lock to plugin manager: {:?}", e))), } } @@ -330,6 +331,29 @@ async fn reload_plugin(Json(payload): Json) -> impl IntoResponse { }) } +async fn get_plugin_settings(Json(payload): Json) -> impl IntoResponse { + debug!("Getting settings of '{}'", payload.name); + with_plugin_manager(|plugin_manager| -> Response { + match plugin_manager.get_plugin_settings(&payload.name) { + Ok(optional_result) => match optional_result { + None => { + debug!("Plugin has not settings"); + StatusCode::NO_CONTENT.into_response() + }, + Some(settings) => { + debug!("Plugin has settings, returning"); + Json(settings).into_response() + }, + }, + Err(e) => match e { + PluginManagerError::PluginNotFound => + StatusCode::NOT_FOUND.into_response(), + e => (StatusCode::INTERNAL_SERVER_ERROR, AppError(anyhow!("{:?}", e))).into_response(), + } + } + }) +} + const TEMPORARY_DIRECTORY: &str = "fcop"; enum InstallError { From 8e56249915d358340d0ae69003ae27331ac5f224 Mon Sep 17 00:00:00 2001 From: Simon Kurz Date: Sat, 9 Nov 2024 08:47:06 +0100 Subject: [PATCH 03/10] Fix code formatting --- .../src/plugins/library/dangerous/native.rs | 29 ++--- .../src/plugins/library/matrix.rs | 9 +- futuremod_engine/src/plugins/library/mod.rs | 2 +- .../src/plugins/library/settings/mod.rs | 119 +++++++++++------- futuremod_engine/src/plugins/plugin.rs | 9 +- .../src/plugins/plugin_environment.rs | 49 +++++--- .../src/plugins/plugin_manager.rs | 11 +- futuremod_engine/src/server.rs | 20 +-- futuremod_hook/src/lua.rs | 9 +- 9 files changed, 151 insertions(+), 106 deletions(-) diff --git a/futuremod_engine/src/plugins/library/dangerous/native.rs b/futuremod_engine/src/plugins/library/dangerous/native.rs index c431a70..907839c 100644 --- a/futuremod_engine/src/plugins/library/dangerous/native.rs +++ b/futuremod_engine/src/plugins/library/dangerous/native.rs @@ -1,7 +1,7 @@ use std::{cell::Ref, collections::HashMap}; use log::debug; -use mlua::{AnyUserData, ObjectLike, Lua, MetaMethod, UserData, UserDataRef}; +use mlua::{AnyUserData, Lua, MetaMethod, ObjectLike, UserData, UserDataRef}; use futuremod_hook::types::{lua_to_native, native_to_lua, Type}; @@ -93,14 +93,13 @@ impl UserData for NativeStruct { } // Call the type's 'fromBytes' function to construct an instance of the type from the bytes - let f = - complex_type - .get::("fromBytes") - .map_err(|_| { - mlua::Error::RuntimeError( - "Type userdata is missing 'fromBytes' function".to_string(), - ) - })?; + let f = complex_type + .get::("fromBytes") + .map_err(|_| { + mlua::Error::RuntimeError( + "Type userdata is missing 'fromBytes' function".to_string(), + ) + })?; let value = f.call::((complex_type, byte_vec))?; Ok(value) @@ -304,9 +303,7 @@ impl UserData for NativeStructDefinition { fn add_methods>(methods: &mut M) { methods.add_function( "cast", - |lua, - (definition, address): (AnyUserData, u32)| - -> Result { + |lua, (definition, address): (AnyUserData, u32)| -> Result { native_struct_from_definition(lua, address, definition) }, ); @@ -351,11 +348,9 @@ pub fn create_native_struct_definition_fn( userdata.get::("toBytes").map_err(|_| { mlua::Error::runtime("Complex type is missing function 'toBytes'") })?; - userdata - .get::("fromBytes") - .map_err(|_| { - mlua::Error::runtime("Complex type is missing function 'fromBytes'") - })?; + userdata.get::("fromBytes").map_err(|_| { + mlua::Error::runtime("Complex type is missing function 'fromBytes'") + })?; FieldDefinitionType::Complex(key.clone()) } diff --git a/futuremod_engine/src/plugins/library/matrix.rs b/futuremod_engine/src/plugins/library/matrix.rs index 78559ce..9de28c8 100644 --- a/futuremod_engine/src/plugins/library/matrix.rs +++ b/futuremod_engine/src/plugins/library/matrix.rs @@ -8,7 +8,9 @@ use std::{ }; use log::info; -use mlua::{AnyUserData, FromLua, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, UserDataRef}; +use mlua::{ + AnyUserData, FromLua, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, UserDataRef, +}; use nalgebra::{DMatrix, Matrix4, Scalar, Vector3}; use num::{ traits::{FromBytes, ToBytes}, @@ -244,10 +246,7 @@ fn create_zero_matrix( } /// Create identify matrix -fn create_identity_matrix( - _: &Lua, - size: u8, -) -> LuaResult> { +fn create_identity_matrix(_: &Lua, size: u8) -> LuaResult> { let size = size as usize; Ok(LuaMatrix(Arc::new(Mutex::new(DMatrix::::identity( diff --git a/futuremod_engine/src/plugins/library/mod.rs b/futuremod_engine/src/plugins/library/mod.rs index 25c97f3..12f9520 100644 --- a/futuremod_engine/src/plugins/library/mod.rs +++ b/futuremod_engine/src/plugins/library/mod.rs @@ -2,8 +2,8 @@ pub mod dangerous; pub mod game; pub mod input; pub mod matrix; +pub mod settings; pub mod system; pub mod ui; -pub mod settings; type LuaResult = Result; diff --git a/futuremod_engine/src/plugins/library/settings/mod.rs b/futuremod_engine/src/plugins/library/settings/mod.rs index a1ee6d4..319ec97 100644 --- a/futuremod_engine/src/plugins/library/settings/mod.rs +++ b/futuremod_engine/src/plugins/library/settings/mod.rs @@ -1,9 +1,12 @@ -use std::{collections::{HashMap, HashSet}, sync::Arc}; +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, +}; +use anyhow::anyhow; use log::{debug, info}; use mlua::{AnyUserData, Lua, Table, UserData}; use serde::{Deserialize, Serialize}; -use anyhow::anyhow; use super::LuaResult; @@ -14,7 +17,7 @@ pub struct PluginSettingsLibrary { impl PluginSettingsLibrary { pub fn new() -> Self { - PluginSettingsLibrary{settings: None} + PluginSettingsLibrary { settings: None } } } @@ -25,27 +28,31 @@ impl UserData for PluginSettingsLibrary { for pair in components.pairs::() { let (_key, value) = pair?; - let component = value.as_userdata().ok_or(mlua::Error::RuntimeError("invalid component".into()))?; - + let component = value + .as_userdata() + .ok_or(mlua::Error::RuntimeError("invalid component".into()))?; + if component.is::() { debug!("Got button builder component"); - + let builder = component.borrow::()?; setting_components.push(SettingsComponent::Button(builder.clone())); } else if component.is::() { debug!("Got text compoment"); - + let builder = component.borrow::()?; setting_components.push(SettingsComponent::Text(builder.clone())); } else { debug!("Got unknown compomnent: {:?}", value); } } - - let settings = PluginSettings{components: setting_components}; - + + let settings = PluginSettings { + components: setting_components, + }; + settings_library.settings = Some(settings); - + Ok(()) }); @@ -98,7 +105,9 @@ fn create_settings(lua: &Lua, components: Table) -> LuaResult<()> { for pair in components.pairs::() { let (_key, value) = pair?; - let component = value.as_userdata().ok_or(mlua::Error::RuntimeError("invalid component".into()))?; + let component = value + .as_userdata() + .ok_or(mlua::Error::RuntimeError("invalid component".into()))?; if component.is::() { debug!("Got button builder component"); @@ -120,7 +129,9 @@ fn create_settings(lua: &Lua, components: Table) -> LuaResult<()> { } } - let settings = PluginSettings{components: setting_components}; + let settings = PluginSettings { + components: setting_components, + }; debug!("Created setting: {:#?}", settings); @@ -129,7 +140,10 @@ fn create_settings(lua: &Lua, components: Table) -> LuaResult<()> { debug!("Stored settings in plugin globals"); let other_globals = lua.globals(); - debug!("Stored settings: {:#?}", other_globals.get::("_settings")); + debug!( + "Stored settings: {:#?}", + other_globals.get::("_settings") + ); lua.globals().for_each(|key: String, value: mlua::Value| { debug!("Setting Global: {key} -> {value:#?}"); @@ -139,7 +153,9 @@ fn create_settings(lua: &Lua, components: Table) -> LuaResult<()> { Ok(()) } -pub fn get_settings(context: &HashMap<&'static str, mlua::Value>) -> Result, anyhow::Error> { +pub fn get_settings( + context: &HashMap<&'static str, mlua::Value>, +) -> Result, anyhow::Error> { let has_settings = context.contains_key("settings"); if !has_settings { @@ -149,15 +165,18 @@ pub fn get_settings(context: &HashMap<&'static str, mlua::Value>) -> Result() + let settings = settings_global + .borrow::() .map_err(|e| anyhow!("Plugin context does not contain valid setting: {e}"))?; debug!("Could convert settings userdata to settings struct"); @@ -181,7 +200,7 @@ fn create_button(_: &Lua, text: String) -> LuaResult { impl ButtonComponent { pub fn new(text: String) -> ButtonComponent { debug!("Create button with text '{}'", text); - + ButtonComponent { text, disabled: false, @@ -201,13 +220,16 @@ impl UserData for ButtonComponent { Ok(new_buider) }); - methods.add_function("withDisabled", |_, (builder, value): (AnyUserData, bool)| { - debug!("Change button disabled to '{}'", value); - let builder = builder.borrow::()?; - let mut new_buider = builder.clone(); - new_buider.disabled = value; - Ok(new_buider) - }); + methods.add_function( + "withDisabled", + |_, (builder, value): (AnyUserData, bool)| { + debug!("Change button disabled to '{}'", value); + let builder = builder.borrow::()?; + let mut new_buider = builder.clone(); + new_buider.disabled = value; + Ok(new_buider) + }, + ); methods.add_function("withID", |_, (builder, id): (AnyUserData, String)| { debug!("Change button ID to {}", id); @@ -217,33 +239,35 @@ impl UserData for ButtonComponent { Ok(new_buider) }); - methods.add_function("onClick", |_, (builder, cb): (AnyUserData, mlua::Function)| { - debug!("Change button on click listener to '{:?}'", cb); - let builder = builder.borrow::()?; - let mut new_buider = builder.clone(); - new_buider.on_click = Some(cb); - Ok(new_buider) - }); + methods.add_function( + "onClick", + |_, (builder, cb): (AnyUserData, mlua::Function)| { + debug!("Change button on click listener to '{:?}'", cb); + let builder = builder.borrow::()?; + let mut new_buider = builder.clone(); + new_buider.on_click = Some(cb); + Ok(new_buider) + }, + ); } } #[derive(Debug, Clone, Serialize)] pub struct SectionComponent { - content: Vec + content: Vec, } impl SectionComponent { pub fn new(content: Vec) -> SectionComponent { - SectionComponent{content} + SectionComponent { content } } } impl UserData for SectionComponent {} - #[derive(Debug, Clone, Serialize)] pub struct TextComponent { - pub text: String + pub text: String, } fn create_text(_lua: &Lua, text: String) -> LuaResult { @@ -252,17 +276,20 @@ fn create_text(_lua: &Lua, text: String) -> LuaResult { impl TextComponent { pub fn new(text: String) -> TextComponent { - TextComponent{text} + TextComponent { text } } } impl UserData for TextComponent { fn add_methods>(methods: &mut M) { - methods.add_function("withText", |_, (text_component, text): (AnyUserData, String)| { - let original = text_component.borrow::()?; - let mut new_component = original.clone(); - new_component.text = text; - Ok(new_component) - }); + methods.add_function( + "withText", + |_, (text_component, text): (AnyUserData, String)| { + let original = text_component.borrow::()?; + let mut new_component = original.clone(); + new_component.text = text; + Ok(new_component) + }, + ); } -} \ No newline at end of file +} diff --git a/futuremod_engine/src/plugins/plugin.rs b/futuremod_engine/src/plugins/plugin.rs index faf3985..2532305 100644 --- a/futuremod_engine/src/plugins/plugin.rs +++ b/futuremod_engine/src/plugins/plugin.rs @@ -1,4 +1,7 @@ -use super::{library::settings::{get_settings, PluginSettings}, plugin_environment::PluginEnvironment}; +use super::{ + library::settings::{get_settings, PluginSettings}, + plugin_environment::PluginEnvironment, +}; use futuremod_data::plugin::{PluginError, PluginInfo}; use log::*; use mlua::{Function, Lua, Table}; @@ -382,11 +385,11 @@ impl Plugin { debug!("{:#?}", context.environment.libraries); get_settings(&context.environment.libraries) .map_err(|e| PluginError::Error(format!("{}", e))) - }, + } _ => { debug!("Requested plugin settings of not loaded plugin"); Ok(None) - }, + } } } } diff --git a/futuremod_engine/src/plugins/plugin_environment.rs b/futuremod_engine/src/plugins/plugin_environment.rs index 7f65f4f..cce8a41 100644 --- a/futuremod_engine/src/plugins/plugin_environment.rs +++ b/futuremod_engine/src/plugins/plugin_environment.rs @@ -7,7 +7,13 @@ use std::{ }; use super::library::{ - dangerous::create_dangerous_library, game::create_game_library, input::create_input_library, matrix::create_matrix_library, settings::{create_settings_library, create_settings_library_new}, system::create_system_library, ui::create_ui_library + dangerous::create_dangerous_library, + game::create_game_library, + input::create_input_library, + matrix::create_matrix_library, + settings::{create_settings_library, create_settings_library_new}, + system::create_system_library, + ui::create_ui_library, }; use anyhow::bail; use futuremod_data::plugin::{PluginDependency, PluginInfo}; @@ -123,21 +129,33 @@ fn prepare_libraries( for library in info.dependencies.iter() { match library { - PluginDependency::Dangerous => { - libraries.insert("dangerous", mlua::Value::Table(create_dangerous_library(lua.clone())?)) + PluginDependency::Dangerous => libraries.insert( + "dangerous", + mlua::Value::Table(create_dangerous_library(lua.clone())?), + ), + PluginDependency::Game => libraries.insert( + "game", + mlua::Value::Table(create_game_library(lua.clone())?), + ), + PluginDependency::Input => libraries.insert( + "input", + mlua::Value::Table(create_input_library(lua.clone())?), + ), + PluginDependency::UI => { + libraries.insert("ui", mlua::Value::Table(create_ui_library(lua.clone())?)) } - PluginDependency::Game => libraries.insert("game", mlua::Value::Table(create_game_library(lua.clone())?)), - PluginDependency::Input => { - libraries.insert("input", mlua::Value::Table(create_input_library(lua.clone())?)) - } - PluginDependency::UI => libraries.insert("ui", mlua::Value::Table(create_ui_library(lua.clone())?)), - PluginDependency::System => { - libraries.insert("system", mlua::Value::Table(create_system_library(lua.clone())?)) - } - PluginDependency::Matrix => { - libraries.insert("matrix", mlua::Value::Table(create_matrix_library(lua.clone())?)) - } - PluginDependency::Settings => libraries.insert("settings", create_settings_library_new(lua.clone())?.into_lua(&lua)?), + PluginDependency::System => libraries.insert( + "system", + mlua::Value::Table(create_system_library(lua.clone())?), + ), + PluginDependency::Matrix => libraries.insert( + "matrix", + mlua::Value::Table(create_matrix_library(lua.clone())?), + ), + PluginDependency::Settings => libraries.insert( + "settings", + create_settings_library_new(lua.clone())?.into_lua(&lua)?, + ), PluginDependency::Math => libraries.insert("math", globals.get("math").to_owned()?), PluginDependency::Bit32 => libraries.insert("bit32", globals.get("bit32").to_owned()?), PluginDependency::String => { @@ -145,7 +163,6 @@ fn prepare_libraries( } PluginDependency::Table => libraries.insert("table", globals.get("table").to_owned()?), PluginDependency::Utf8 => libraries.insert("utf8", globals.get("utf8").to_owned()?), - }; } diff --git a/futuremod_engine/src/plugins/plugin_manager.rs b/futuremod_engine/src/plugins/plugin_manager.rs index 644bf2e..7466a1e 100644 --- a/futuremod_engine/src/plugins/plugin_manager.rs +++ b/futuremod_engine/src/plugins/plugin_manager.rs @@ -422,12 +422,15 @@ impl PluginManager { return &self.plugins; } - pub fn get_plugin_settings(&self, name: &str) -> Result, PluginManagerError> { + pub fn get_plugin_settings( + &self, + name: &str, + ) -> Result, PluginManagerError> { info!("Getting settings of plugin '{}'", name); match self.plugins.get(name) { - Some(game_plugin) => { - game_plugin.get_settings().map_err(PluginManagerError::Plugin) - } + Some(game_plugin) => game_plugin + .get_settings() + .map_err(PluginManagerError::Plugin), None => { warn!("Plugin doesn't exist"); Err(PluginManagerError::PluginNotFound) diff --git a/futuremod_engine/src/server.rs b/futuremod_engine/src/server.rs index 43a8ebb..31909da 100644 --- a/futuremod_engine/src/server.rs +++ b/futuremod_engine/src/server.rs @@ -247,7 +247,10 @@ where { match GlobalPluginManager::get().lock() { Ok(plugin_manager) => Ok(f(&plugin_manager)), - Err(e) => Err(AppError(anyhow!("Could not get lock to plugin manager: {:?}", e))), + Err(e) => Err(AppError(anyhow!( + "Could not get lock to plugin manager: {:?}", + e + ))), } } @@ -339,17 +342,20 @@ async fn get_plugin_settings(Json(payload): Json) -> impl IntoResp None => { debug!("Plugin has not settings"); StatusCode::NO_CONTENT.into_response() - }, + } Some(settings) => { debug!("Plugin has settings, returning"); Json(settings).into_response() - }, + } }, Err(e) => match e { - PluginManagerError::PluginNotFound => - StatusCode::NOT_FOUND.into_response(), - e => (StatusCode::INTERNAL_SERVER_ERROR, AppError(anyhow!("{:?}", e))).into_response(), - } + PluginManagerError::PluginNotFound => StatusCode::NOT_FOUND.into_response(), + e => ( + StatusCode::INTERNAL_SERVER_ERROR, + AppError(anyhow!("{:?}", e)), + ) + .into_response(), + }, } }) } diff --git a/futuremod_hook/src/lua.rs b/futuremod_hook/src/lua.rs index ebb53e9..497919d 100644 --- a/futuremod_hook/src/lua.rs +++ b/futuremod_hook/src/lua.rs @@ -243,11 +243,7 @@ impl NativeFunction { } } - pub fn call( - &self, - lua: &Lua, - args: mlua::MultiValue, - ) -> Result { + pub fn call(&self, lua: &Lua, args: mlua::MultiValue) -> Result { let args = args.iter().collect::>(); debug!( @@ -385,8 +381,7 @@ pub fn create_native_function_function( } } - let return_value = match lua_fn.call::(mlua::MultiValue::from_iter(lua_args)) - { + let return_value = match lua_fn.call::(mlua::MultiValue::from_iter(lua_args)) { Ok(value) => value, Err(e) => { warn!("Lua function threw unexpected error: {:?}. Panicking...", e); From 47f4becae27b53fd8c8574f7b6bef3bc1fe300ca Mon Sep 17 00:00:00 2001 From: Simon Kurz Date: Sat, 9 Nov 2024 09:40:52 +0100 Subject: [PATCH 04/10] Fix some compiler warnings --- futuremod_engine/src/plugins/library/dangerous/native.rs | 2 +- futuremod_engine/src/plugins/library/matrix.rs | 2 -- futuremod_engine/src/plugins/library/settings/mod.rs | 6 +++--- futuremod_engine/src/plugins/plugin_environment.rs | 2 +- futuremod_engine/src/plugins/plugin_manager.rs | 2 +- 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/futuremod_engine/src/plugins/library/dangerous/native.rs b/futuremod_engine/src/plugins/library/dangerous/native.rs index 907839c..7e817b8 100644 --- a/futuremod_engine/src/plugins/library/dangerous/native.rs +++ b/futuremod_engine/src/plugins/library/dangerous/native.rs @@ -1,4 +1,4 @@ -use std::{cell::Ref, collections::HashMap}; +use std::collections::HashMap; use log::debug; use mlua::{AnyUserData, Lua, MetaMethod, ObjectLike, UserData, UserDataRef}; diff --git a/futuremod_engine/src/plugins/library/matrix.rs b/futuremod_engine/src/plugins/library/matrix.rs index 9de28c8..85e02be 100644 --- a/futuremod_engine/src/plugins/library/matrix.rs +++ b/futuremod_engine/src/plugins/library/matrix.rs @@ -1,5 +1,4 @@ use std::{ - cell::Ref, fmt, marker::PhantomData, mem::size_of, @@ -16,7 +15,6 @@ use num::{ traits::{FromBytes, ToBytes}, Num, One, Zero, }; -use windows::System::User; use super::LuaResult; diff --git a/futuremod_engine/src/plugins/library/settings/mod.rs b/futuremod_engine/src/plugins/library/settings/mod.rs index 319ec97..0e4e5ac 100644 --- a/futuremod_engine/src/plugins/library/settings/mod.rs +++ b/futuremod_engine/src/plugins/library/settings/mod.rs @@ -1,12 +1,12 @@ use std::{ - collections::{HashMap, HashSet}, + collections::HashMap, sync::Arc, }; use anyhow::anyhow; -use log::{debug, info}; +use log::debug; use mlua::{AnyUserData, Lua, Table, UserData}; -use serde::{Deserialize, Serialize}; +use serde::Serialize; use super::LuaResult; diff --git a/futuremod_engine/src/plugins/plugin_environment.rs b/futuremod_engine/src/plugins/plugin_environment.rs index cce8a41..ba5c173 100644 --- a/futuremod_engine/src/plugins/plugin_environment.rs +++ b/futuremod_engine/src/plugins/plugin_environment.rs @@ -11,7 +11,7 @@ use super::library::{ game::create_game_library, input::create_input_library, matrix::create_matrix_library, - settings::{create_settings_library, create_settings_library_new}, + settings::create_settings_library_new, system::create_system_library, ui::create_ui_library, }; diff --git a/futuremod_engine/src/plugins/plugin_manager.rs b/futuremod_engine/src/plugins/plugin_manager.rs index 7466a1e..2b72a37 100644 --- a/futuremod_engine/src/plugins/plugin_manager.rs +++ b/futuremod_engine/src/plugins/plugin_manager.rs @@ -168,7 +168,7 @@ impl PluginManager { pub fn new(plugins_directory: PathBuf) -> Result { let lua = Arc::new(Lua::new()); if let Err(e) = - lua.load_from_std_lib(StdLib::STRING | StdLib::BIT | StdLib::MATH | StdLib::TABLE) + lua.load_std_libs(StdLib::STRING | StdLib::BIT | StdLib::MATH | StdLib::TABLE) { error!("Could not load subset of standard library: {}", e); return Err(PluginManagerError::Other(format!( From 7c31940f21fb8ba854bbebbca02591e3261ef854 Mon Sep 17 00:00:00 2001 From: Simon Kurz Date: Sat, 9 Nov 2024 09:41:03 +0100 Subject: [PATCH 05/10] Add forgotten cargo lock --- Cargo.lock | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7938f4a..b34aae6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3069,17 +3069,18 @@ dependencies = [ [[package]] name = "mlua" -version = "0.9.9" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d111deb18a9c9bd33e1541309f4742523bfab01d276bfa9a27519f6de9c11dc7" +checksum = "0f6ddbd668297c46be4bdea6c599dcc1f001a129586272d53170b7ac0a62961e" dependencies = [ "bstr", + "either", "erased-serde", "futures-util", "libloading 0.8.5", "mlua-sys", "num-traits", - "once_cell", + "parking_lot 0.12.3", "rustc-hash 2.0.0", "serde", "serde-value", From 9d7694250fdff5a487cbeda30e45f34b149ed2dd Mon Sep 17 00:00:00 2001 From: Simon Kurz Date: Sat, 9 Nov 2024 09:54:21 +0100 Subject: [PATCH 06/10] Split settings library into multiple files --- .../src/plugins/library/settings/button.rs | 71 +++++++++ .../src/plugins/library/settings/mod.rs | 137 ++---------------- .../src/plugins/library/settings/section.rs | 19 +++ .../src/plugins/library/settings/settings.rs | 19 +++ .../src/plugins/library/settings/text.rs | 31 ++++ 5 files changed, 153 insertions(+), 124 deletions(-) create mode 100644 futuremod_engine/src/plugins/library/settings/button.rs create mode 100644 futuremod_engine/src/plugins/library/settings/section.rs create mode 100644 futuremod_engine/src/plugins/library/settings/settings.rs create mode 100644 futuremod_engine/src/plugins/library/settings/text.rs diff --git a/futuremod_engine/src/plugins/library/settings/button.rs b/futuremod_engine/src/plugins/library/settings/button.rs new file mode 100644 index 0000000..d78410e --- /dev/null +++ b/futuremod_engine/src/plugins/library/settings/button.rs @@ -0,0 +1,71 @@ +use log::debug; +use mlua::{AnyUserData, Lua, UserData}; +use serde::Serialize; + +#[derive(Debug, Clone, Serialize)] +pub struct ButtonComponent { + text: String, + disabled: bool, + #[serde(skip)] + on_click: Option, + id: Option, +} + +pub fn create_button(_: &Lua, text: String) -> mlua::Result { + Ok(ButtonComponent::new(text)) +} + +impl ButtonComponent { + pub fn new(text: String) -> ButtonComponent { + debug!("Create button with text '{}'", text); + + ButtonComponent { + text, + disabled: false, + on_click: None, + id: None, + } + } +} + +impl UserData for ButtonComponent { + fn add_methods>(methods: &mut M) { + methods.add_function("withText", |_, (builder, text): (AnyUserData, String)| { + debug!("Change button text to '{}'", text); + let builder = builder.borrow::()?; + let mut new_buider = builder.clone(); + new_buider.text = text; + Ok(new_buider) + }); + + methods.add_function( + "withDisabled", + |_, (builder, value): (AnyUserData, bool)| { + debug!("Change button disabled to '{}'", value); + let builder = builder.borrow::()?; + let mut new_buider = builder.clone(); + new_buider.disabled = value; + Ok(new_buider) + }, + ); + + methods.add_function("withID", |_, (builder, id): (AnyUserData, String)| { + debug!("Change button ID to {}", id); + let builder = builder.borrow::()?; + let mut new_buider = builder.clone(); + new_buider.id = Some(id); + Ok(new_buider) + }); + + methods.add_function( + "onClick", + |_, (builder, cb): (AnyUserData, mlua::Function)| { + debug!("Change button on click listener to '{:?}'", cb); + let builder = builder.borrow::()?; + let mut new_buider = builder.clone(); + new_buider.on_click = Some(cb); + Ok(new_buider) + }, + ); + } +} \ No newline at end of file diff --git a/futuremod_engine/src/plugins/library/settings/mod.rs b/futuremod_engine/src/plugins/library/settings/mod.rs index 0e4e5ac..e298af8 100644 --- a/futuremod_engine/src/plugins/library/settings/mod.rs +++ b/futuremod_engine/src/plugins/library/settings/mod.rs @@ -5,8 +5,19 @@ use std::{ use anyhow::anyhow; use log::debug; -use mlua::{AnyUserData, Lua, Table, UserData}; -use serde::Serialize; +use mlua::{Lua, Table, UserData}; + +mod settings; +mod button; +mod section; +mod text; + +pub use settings::PluginSettings; + +use settings::SettingsComponent; +use button::{create_button, ButtonComponent}; +use text::{create_text, TextComponent}; +use section::SectionComponent; use super::LuaResult; @@ -83,20 +94,6 @@ pub fn create_settings_table(lua: &Lua) -> Result { Ok(mlua::Value::Table(library)) } -#[derive(Debug, Clone, Serialize)] -#[serde(tag = "type", rename_all = "camelCase")] -pub enum SettingsComponent { - Button(ButtonComponent), - Section(SectionComponent), - Text(TextComponent), -} - -#[derive(Debug, Clone, Serialize)] -pub struct PluginSettings { - pub components: Vec, -} - -impl UserData for PluginSettings {} fn create_settings(lua: &Lua, components: Table) -> LuaResult<()> { debug!("Create settings from: {:?}", components); @@ -184,112 +181,4 @@ pub fn get_settings( Ok(settings.settings.clone()) } -#[derive(Debug, Clone, Serialize)] -pub struct ButtonComponent { - text: String, - disabled: bool, - #[serde(skip)] - on_click: Option, - id: Option, -} - -fn create_button(_: &Lua, text: String) -> LuaResult { - Ok(ButtonComponent::new(text)) -} - -impl ButtonComponent { - pub fn new(text: String) -> ButtonComponent { - debug!("Create button with text '{}'", text); - - ButtonComponent { - text, - disabled: false, - on_click: None, - id: None, - } - } -} - -impl UserData for ButtonComponent { - fn add_methods>(methods: &mut M) { - methods.add_function("withText", |_, (builder, text): (AnyUserData, String)| { - debug!("Change button text to '{}'", text); - let builder = builder.borrow::()?; - let mut new_buider = builder.clone(); - new_buider.text = text; - Ok(new_buider) - }); - - methods.add_function( - "withDisabled", - |_, (builder, value): (AnyUserData, bool)| { - debug!("Change button disabled to '{}'", value); - let builder = builder.borrow::()?; - let mut new_buider = builder.clone(); - new_buider.disabled = value; - Ok(new_buider) - }, - ); - - methods.add_function("withID", |_, (builder, id): (AnyUserData, String)| { - debug!("Change button ID to {}", id); - let builder = builder.borrow::()?; - let mut new_buider = builder.clone(); - new_buider.id = Some(id); - Ok(new_buider) - }); - methods.add_function( - "onClick", - |_, (builder, cb): (AnyUserData, mlua::Function)| { - debug!("Change button on click listener to '{:?}'", cb); - let builder = builder.borrow::()?; - let mut new_buider = builder.clone(); - new_buider.on_click = Some(cb); - Ok(new_buider) - }, - ); - } -} - -#[derive(Debug, Clone, Serialize)] -pub struct SectionComponent { - content: Vec, -} - -impl SectionComponent { - pub fn new(content: Vec) -> SectionComponent { - SectionComponent { content } - } -} - -impl UserData for SectionComponent {} - -#[derive(Debug, Clone, Serialize)] -pub struct TextComponent { - pub text: String, -} - -fn create_text(_lua: &Lua, text: String) -> LuaResult { - Ok(TextComponent::new(text)) -} - -impl TextComponent { - pub fn new(text: String) -> TextComponent { - TextComponent { text } - } -} - -impl UserData for TextComponent { - fn add_methods>(methods: &mut M) { - methods.add_function( - "withText", - |_, (text_component, text): (AnyUserData, String)| { - let original = text_component.borrow::()?; - let mut new_component = original.clone(); - new_component.text = text; - Ok(new_component) - }, - ); - } -} diff --git a/futuremod_engine/src/plugins/library/settings/section.rs b/futuremod_engine/src/plugins/library/settings/section.rs new file mode 100644 index 0000000..ae48046 --- /dev/null +++ b/futuremod_engine/src/plugins/library/settings/section.rs @@ -0,0 +1,19 @@ +use mlua::UserData; +use serde::Serialize; + +use super::SettingsComponent; + + +#[derive(Debug, Clone, Serialize)] +pub struct SectionComponent { + content: Vec, +} + +impl SectionComponent { + pub fn new(content: Vec) -> SectionComponent { + SectionComponent { content } + } +} + +impl UserData for SectionComponent {} + diff --git a/futuremod_engine/src/plugins/library/settings/settings.rs b/futuremod_engine/src/plugins/library/settings/settings.rs new file mode 100644 index 0000000..c67a393 --- /dev/null +++ b/futuremod_engine/src/plugins/library/settings/settings.rs @@ -0,0 +1,19 @@ +use mlua::UserData; +use serde::Serialize; + +use super::{button::ButtonComponent, section::SectionComponent, text::TextComponent}; + +#[derive(Debug, Clone, Serialize)] +#[serde(tag = "type", rename_all = "camelCase")] +pub enum SettingsComponent { + Button(ButtonComponent), + Section(SectionComponent), + Text(TextComponent), +} + +#[derive(Debug, Clone, Serialize)] +pub struct PluginSettings { + pub components: Vec, +} + +impl UserData for PluginSettings {} \ No newline at end of file diff --git a/futuremod_engine/src/plugins/library/settings/text.rs b/futuremod_engine/src/plugins/library/settings/text.rs new file mode 100644 index 0000000..87e6e66 --- /dev/null +++ b/futuremod_engine/src/plugins/library/settings/text.rs @@ -0,0 +1,31 @@ +use mlua::{AnyUserData, Lua, UserData}; +use serde::Serialize; + +#[derive(Debug, Clone, Serialize)] +pub struct TextComponent { + pub text: String, +} + +pub fn create_text(_lua: &Lua, text: String) -> mlua::Result { + Ok(TextComponent::new(text)) +} + +impl TextComponent { + pub fn new(text: String) -> TextComponent { + TextComponent { text } + } +} + +impl UserData for TextComponent { + fn add_methods>(methods: &mut M) { + methods.add_function( + "withText", + |_, (text_component, text): (AnyUserData, String)| { + let original = text_component.borrow::()?; + let mut new_component = original.clone(); + new_component.text = text; + Ok(new_component) + }, + ); + } +} From a927b573146298d8181b9b553380fe6d7c36495a Mon Sep 17 00:00:00 2001 From: Simon Kurz Date: Sun, 10 Nov 2024 10:44:54 +0100 Subject: [PATCH 07/10] Change settings storage structure and conversion from builder to compoment --- Cargo.lock | 11 + Cargo.toml | 2 +- examples/settings/main.lua | 17 +- futuremod_engine/Cargo.toml | 1 + .../src/plugins/library/settings/button.rs | 47 +++-- .../src/plugins/library/settings/mod.rs | 134 ++---------- .../src/plugins/library/settings/section.rs | 87 +++++++- .../src/plugins/library/settings/settings.rs | 191 +++++++++++++++++- .../src/plugins/library/settings/text.rs | 22 +- .../src/plugins/plugin_environment.rs | 10 +- 10 files changed, 348 insertions(+), 174 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b34aae6..bad4420 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1706,6 +1706,7 @@ dependencies = [ "tokio", "tokio-util", "toml", + "uuid", "walkdir", "windows 0.58.0", "zip", @@ -5427,6 +5428,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +dependencies = [ + "getrandom", + "serde", +] + [[package]] name = "vcpkg" version = "0.2.15" diff --git a/Cargo.toml b/Cargo.toml index 1d777d4..35167e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,4 +8,4 @@ members = [ resolver = "2" [profile.release.package.futuremod_hook] -opt-level = 0 # Completely disable optimizations for dll due to issues with raw memory modification \ No newline at end of file +opt-level = 0 # Completely disable optimizations for dll due to issues with raw memory modification diff --git a/examples/settings/main.lua b/examples/settings/main.lua index 0dbcfc7..cb9a7f7 100644 --- a/examples/settings/main.lua +++ b/examples/settings/main.lua @@ -8,10 +8,17 @@ end function createSettings() settings:create({ - settings.Text("Example Settings"), - settings.Text("By clicking the button below, you should be able to trigger some code of the plugin. How neat!!!"), - settings.Button("Click Me"):onClick(function() - print("Clicked") - end) + settings.Section({ + settings.Text("Example Settings"), + settings.Text("By clicking the button below, you should be able to trigger some code of the plugin. How neat!!!"), + settings.Button("Click Me"):onClick(function() + print("Clicked") + end) + :withID("button") + }), + settings.Section({ + settings.Text("Next Section"), + settings.Button("Other butotn"):withDisabled(true) + }) }) end \ No newline at end of file diff --git a/futuremod_engine/Cargo.toml b/futuremod_engine/Cargo.toml index 3264101..009e5df 100644 --- a/futuremod_engine/Cargo.toml +++ b/futuremod_engine/Cargo.toml @@ -37,6 +37,7 @@ walkdir = "2.4.0" zip = "0.6.6" junction = "1.2.0" directories = "5.0.1" +uuid = { version = "1.11.0", features = ["serde", "v4"] } [dependencies.mlua] version = "0.10.0" diff --git a/futuremod_engine/src/plugins/library/settings/button.rs b/futuremod_engine/src/plugins/library/settings/button.rs index d78410e..225966b 100644 --- a/futuremod_engine/src/plugins/library/settings/button.rs +++ b/futuremod_engine/src/plugins/library/settings/button.rs @@ -1,38 +1,49 @@ + use log::debug; use mlua::{AnyUserData, Lua, UserData}; use serde::Serialize; #[derive(Debug, Clone, Serialize)] -pub struct ButtonComponent { - text: String, - disabled: bool, +pub struct ButtonBuilder { + pub(super) text: String, + pub(super) disabled: bool, + #[serde(skip)] + pub(super) on_click: Option, #[serde(skip)] - on_click: Option, - id: Option, + pub(super) manual_id: Option, } -pub fn create_button(_: &Lua, text: String) -> mlua::Result { - Ok(ButtonComponent::new(text)) +#[derive(Debug, Clone, Serialize)] +pub struct Button { + pub(super) text: String, + pub(super) disabled: bool, + #[serde(skip)] + pub(super) on_click: Option, + pub(super) id: String, } -impl ButtonComponent { - pub fn new(text: String) -> ButtonComponent { +pub fn create_button(_: &Lua, text: String) -> mlua::Result { + Ok(ButtonBuilder::new(text)) +} + +impl ButtonBuilder { + pub fn new(text: String) -> ButtonBuilder { debug!("Create button with text '{}'", text); - ButtonComponent { + ButtonBuilder { text, disabled: false, on_click: None, - id: None, + manual_id: None, } } } -impl UserData for ButtonComponent { +impl UserData for ButtonBuilder { fn add_methods>(methods: &mut M) { methods.add_function("withText", |_, (builder, text): (AnyUserData, String)| { debug!("Change button text to '{}'", text); - let builder = builder.borrow::()?; + let builder = builder.borrow::()?; let mut new_buider = builder.clone(); new_buider.text = text; Ok(new_buider) @@ -42,7 +53,7 @@ impl UserData for ButtonComponent { "withDisabled", |_, (builder, value): (AnyUserData, bool)| { debug!("Change button disabled to '{}'", value); - let builder = builder.borrow::()?; + let builder = builder.borrow::()?; let mut new_buider = builder.clone(); new_buider.disabled = value; Ok(new_buider) @@ -51,9 +62,9 @@ impl UserData for ButtonComponent { methods.add_function("withID", |_, (builder, id): (AnyUserData, String)| { debug!("Change button ID to {}", id); - let builder = builder.borrow::()?; + let builder = builder.borrow::()?; let mut new_buider = builder.clone(); - new_buider.id = Some(id); + new_buider.manual_id = Some(id); Ok(new_buider) }); @@ -61,11 +72,11 @@ impl UserData for ButtonComponent { "onClick", |_, (builder, cb): (AnyUserData, mlua::Function)| { debug!("Change button on click listener to '{:?}'", cb); - let builder = builder.borrow::()?; + let builder = builder.borrow::()?; let mut new_buider = builder.clone(); new_buider.on_click = Some(cb); Ok(new_buider) }, ); } -} \ No newline at end of file +} diff --git a/futuremod_engine/src/plugins/library/settings/mod.rs b/futuremod_engine/src/plugins/library/settings/mod.rs index e298af8..d8b8a33 100644 --- a/futuremod_engine/src/plugins/library/settings/mod.rs +++ b/futuremod_engine/src/plugins/library/settings/mod.rs @@ -1,25 +1,20 @@ -use std::{ - collections::HashMap, - sync::Arc, -}; +use std::{collections::HashMap, sync::Arc}; use anyhow::anyhow; use log::debug; -use mlua::{Lua, Table, UserData}; +use mlua::{Lua, UserData}; -mod settings; mod button; mod section; +mod settings; mod text; pub use settings::PluginSettings; -use settings::SettingsComponent; -use button::{create_button, ButtonComponent}; -use text::{create_text, TextComponent}; -use section::SectionComponent; - -use super::LuaResult; +use button::create_button; +use section::create_section; +use settings::{create_settings, ComponentBuilder}; +use text::create_text; #[derive(Debug, Clone)] pub struct PluginSettingsLibrary { @@ -34,41 +29,20 @@ impl PluginSettingsLibrary { impl UserData for PluginSettingsLibrary { fn add_methods>(methods: &mut M) { - methods.add_method_mut("create", |_lua, settings_library, components: Table| { - let mut setting_components = Vec::::new(); - - for pair in components.pairs::() { - let (_key, value) = pair?; - let component = value - .as_userdata() - .ok_or(mlua::Error::RuntimeError("invalid component".into()))?; - - if component.is::() { - debug!("Got button builder component"); - - let builder = component.borrow::()?; - setting_components.push(SettingsComponent::Button(builder.clone())); - } else if component.is::() { - debug!("Got text compoment"); - - let builder = component.borrow::()?; - setting_components.push(SettingsComponent::Text(builder.clone())); - } else { - debug!("Got unknown compomnent: {:?}", value); - } - } - - let settings = PluginSettings { - components: setting_components, - }; + methods.add_method_mut( + "create", + |_lua, settings_library, components: Vec| { + let settings = create_settings(components)?; - settings_library.settings = Some(settings); + settings_library.settings = Some(settings); - Ok(()) - }); + Ok(()) + }, + ); methods.add_function("Button", create_button); methods.add_function("Text", create_text); + methods.add_function("Section", create_section); } } @@ -76,80 +50,6 @@ pub fn create_settings_library_new(_lua: Arc) -> Result) -> Result { - let library = lua.create_table()?; - library.set("create", lua.create_function(create_settings)?)?; - library.set("Button", lua.create_function(create_button)?)?; - library.set("Text", lua.create_function(create_text)?)?; - - Ok(library) -} - -pub fn create_settings_table(lua: &Lua) -> Result { - let library = lua.create_table()?; - library.set("create", lua.create_function(create_settings)?)?; - library.set("Button", lua.create_function(create_button)?)?; - library.set("Text", lua.create_function(create_text)?)?; - - Ok(mlua::Value::Table(library)) -} - - -fn create_settings(lua: &Lua, components: Table) -> LuaResult<()> { - debug!("Create settings from: {:?}", components); - - let mut setting_components = Vec::::new(); - - for pair in components.pairs::() { - let (_key, value) = pair?; - let component = value - .as_userdata() - .ok_or(mlua::Error::RuntimeError("invalid component".into()))?; - - if component.is::() { - debug!("Got button builder component"); - - let builder = component.borrow::()?; - setting_components.push(SettingsComponent::Button(builder.clone())); - } else if component.is::() { - debug!("Got text compoment"); - - let builder = component.borrow::()?; - setting_components.push(SettingsComponent::Text(builder.clone())); - } else if component.is::() { - debug!("Got section compoment"); - - let builder = component.borrow::()?; - setting_components.push(SettingsComponent::Section(builder.clone())); - } else { - debug!("Got unknown compomnent: {:?}", value); - } - } - - let settings = PluginSettings { - components: setting_components, - }; - - debug!("Created setting: {:#?}", settings); - - let globals = lua.globals(); - globals.set("_settings", settings)?; - debug!("Stored settings in plugin globals"); - - let other_globals = lua.globals(); - debug!( - "Stored settings: {:#?}", - other_globals.get::("_settings") - ); - - lua.globals().for_each(|key: String, value: mlua::Value| { - debug!("Setting Global: {key} -> {value:#?}"); - Ok(()) - })?; - - Ok(()) -} - pub fn get_settings( context: &HashMap<&'static str, mlua::Value>, ) -> Result, anyhow::Error> { @@ -180,5 +80,3 @@ pub fn get_settings( Ok(settings.settings.clone()) } - - diff --git a/futuremod_engine/src/plugins/library/settings/section.rs b/futuremod_engine/src/plugins/library/settings/section.rs index ae48046..d232ef0 100644 --- a/futuremod_engine/src/plugins/library/settings/section.rs +++ b/futuremod_engine/src/plugins/library/settings/section.rs @@ -1,19 +1,88 @@ -use mlua::UserData; -use serde::Serialize; +use std::sync::Weak; -use super::SettingsComponent; +use mlua::{Lua, UserData}; +use serde::{ + ser::{SerializeSeq, SerializeStruct}, + Serialize, +}; +use super::{button::ButtonBuilder, settings::Component, text::TextBuilder, ComponentBuilder}; #[derive(Debug, Clone, Serialize)] -pub struct SectionComponent { - content: Vec, +pub struct SectionBuilder { + pub(super) content: Vec, } -impl SectionComponent { - pub fn new(content: Vec) -> SectionComponent { - SectionComponent { content } +#[derive(Debug, Clone)] +pub struct Section { + pub(super) content: Vec>, + pub(super) id: String, +} + +pub struct SectionContent<'a>(&'a Vec>); + +impl<'a> Serialize for SectionContent<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut content_state = serializer.serialize_seq(Some(self.0.len()))?; + + for child in self.0.iter() { + let strong_child = child + .upgrade() + .ok_or(serde::ser::Error::custom("Child dropped"))?; + + content_state.serialize_element(&*strong_child)?; + } + + content_state.end() + } +} + +impl Serialize for Section { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("section", 2)?; + state.serialize_field("id", &self.id)?; + state.serialize_field("content", &SectionContent(&self.content))?; + + state.end() } } -impl UserData for SectionComponent {} +impl SectionBuilder { + pub fn new(content: Vec) -> SectionBuilder { + SectionBuilder { content } + } +} + +pub fn create_section(_lua: &Lua, children: Vec) -> mlua::Result { + let mut components = Vec::::new(); + + for child in children { + let child_userdata = child + .as_userdata() + .ok_or(mlua::Error::RuntimeError("Component not userdata".into()))?; + + let compoment = if let Ok(v) = child_userdata.borrow::() { + ComponentBuilder::Button(v.clone()) + } else if let Ok(v) = child_userdata.borrow::() { + ComponentBuilder::Text(v.clone()) + } else if let Ok(v) = child_userdata.borrow::() { + ComponentBuilder::Section(v.clone()) + } else { + return Err(mlua::Error::RuntimeError("Unknown compoment".into())); + }; + + components.push(compoment) + } + + Ok(SectionBuilder { + content: components, + }) +} +impl UserData for SectionBuilder {} diff --git a/futuremod_engine/src/plugins/library/settings/settings.rs b/futuremod_engine/src/plugins/library/settings/settings.rs index c67a393..726caf9 100644 --- a/futuremod_engine/src/plugins/library/settings/settings.rs +++ b/futuremod_engine/src/plugins/library/settings/settings.rs @@ -1,19 +1,194 @@ +use std::{ + collections::HashMap, + sync::{Arc, Weak}, +}; + +use anyhow::{anyhow, bail}; +use log::debug; use mlua::UserData; -use serde::Serialize; +use serde::{ser::SerializeStruct, Serialize}; +use uuid::Uuid; -use super::{button::ButtonComponent, section::SectionComponent, text::TextComponent}; +use super::{ + button::{Button, ButtonBuilder}, + section::{Section, SectionBuilder}, + text::{Text, TextBuilder}, +}; #[derive(Debug, Clone, Serialize)] #[serde(tag = "type", rename_all = "camelCase")] -pub enum SettingsComponent { - Button(ButtonComponent), - Section(SectionComponent), - Text(TextComponent), +pub enum ComponentBuilder { + Button(ButtonBuilder), + Section(SectionBuilder), + Text(TextBuilder), } #[derive(Debug, Clone, Serialize)] +#[serde(tag = "type")] +pub enum Component { + Button(Button), + Section(Section), + Text(Text), +} + +#[derive(Debug, Clone)] pub struct PluginSettings { - pub components: Vec, + components: HashMap>, + root: Weak, +} + +impl Serialize for PluginSettings { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("settings", 1)?; + let strong_root = self + .root + .upgrade() + .ok_or(serde::ser::Error::custom("Root dropped"))?; + state.serialize_field("root", &*strong_root)?; + state.end() + } +} + +impl UserData for PluginSettings {} + +pub(super) fn generate_new_id(map: &HashMap) -> String { + let mut key = Uuid::new_v4().to_string(); + + while map.contains_key(&key) { + key = Uuid::new_v4().to_string(); + } + + key +} + +pub(super) fn create_settings(components: Vec) -> mlua::Result { + debug!("Create settings from: {:?}", components); + + let mut setting_components = HashMap::>::new(); + + let mut root_components = Vec::>::new(); + + for unknown_component in components.into_iter() { + let component = build_component(&mut setting_components, unknown_component) + .map_err(|e| mlua::Error::RuntimeError(format!("{:?}", e)))?; + + root_components.push(component); + } + + let id = generate_new_id(&setting_components); + let root_section = Section { + content: root_components, + id: id.clone(), + }; + + let shared_root = Arc::new(Component::Section(root_section)); + + setting_components.insert(id, shared_root.clone()); + + let settings = PluginSettings { + components: setting_components, + root: Arc::downgrade(&shared_root), + }; + + Ok(settings) +} + +fn build_component( + components: &mut HashMap>, + component: mlua::Value, +) -> Result, anyhow::Error> { + let userdata = component + .as_userdata() + .ok_or(anyhow!("Component not a userdata"))?; + + if let Ok(button_builder) = userdata.borrow::() { + build_button(components, button_builder.clone()) + } else if let Ok(section_builder) = userdata.borrow::() { + build_section(components, section_builder.clone()) + } else { + bail!("Unkown component: {:?}", userdata); + } +} + +fn build_button( + components: &mut HashMap>, + button_builder: ButtonBuilder, +) -> Result, anyhow::Error> { + let id = match button_builder.manual_id { + Some(v) => { + if components.contains_key(&v) { + bail!("Duplicate id"); + } + + v + } + None => generate_new_id(&components), + }; + + let button = Button { + text: button_builder.text, + disabled: button_builder.disabled, + on_click: button_builder.on_click, + id: id.clone(), + }; + + let shared_component = Arc::new(Component::Button(button)); + + components.insert(id, shared_component.clone()); + + Ok(Arc::downgrade(&shared_component)) +} + +fn build_known_compoment( + components: &mut HashMap>, + builder: ComponentBuilder, +) -> Result, anyhow::Error> { + match builder { + ComponentBuilder::Button(button_builder) => build_button(components, button_builder), + ComponentBuilder::Section(section_builder) => build_section(components, section_builder), + ComponentBuilder::Text(text_builder) => build_text(components, text_builder), + } } -impl UserData for PluginSettings {} \ No newline at end of file +fn build_section( + components: &mut HashMap>, + section_builder: SectionBuilder, +) -> Result, anyhow::Error> { + let mut content = Vec::>::new(); + + for component_builder in section_builder.content { + let component = build_known_compoment(components, component_builder)?; + + content.push(component); + } + + let id = generate_new_id(&components); + let section = Section { + id: id.clone(), + content, + }; + + let shared_component = Arc::new(Component::Section(section)); + + components.insert(id, shared_component.clone()); + + Ok(Arc::downgrade(&shared_component)) +} + +fn build_text( + components: &mut HashMap>, + text_builder: TextBuilder, +) -> Result, anyhow::Error> { + let id = generate_new_id(components); + let text = Arc::new(Component::Text(Text { + id: id.clone(), + text: text_builder.text, + })); + + components.insert(id, text.clone()); + + Ok(Arc::downgrade(&text)) +} diff --git a/futuremod_engine/src/plugins/library/settings/text.rs b/futuremod_engine/src/plugins/library/settings/text.rs index 87e6e66..8ac6279 100644 --- a/futuremod_engine/src/plugins/library/settings/text.rs +++ b/futuremod_engine/src/plugins/library/settings/text.rs @@ -2,26 +2,32 @@ use mlua::{AnyUserData, Lua, UserData}; use serde::Serialize; #[derive(Debug, Clone, Serialize)] -pub struct TextComponent { +pub struct TextBuilder { pub text: String, } -pub fn create_text(_lua: &Lua, text: String) -> mlua::Result { - Ok(TextComponent::new(text)) +#[derive(Debug, Clone, Serialize)] +pub struct Text { + pub(super) id: String, + pub(super) text: String, +} + +pub fn create_text(_lua: &Lua, text: String) -> mlua::Result { + Ok(TextBuilder::new(text)) } -impl TextComponent { - pub fn new(text: String) -> TextComponent { - TextComponent { text } +impl TextBuilder { + pub fn new(text: String) -> TextBuilder { + TextBuilder { text } } } -impl UserData for TextComponent { +impl UserData for TextBuilder { fn add_methods>(methods: &mut M) { methods.add_function( "withText", |_, (text_component, text): (AnyUserData, String)| { - let original = text_component.borrow::()?; + let original = text_component.borrow::()?; let mut new_component = original.clone(); new_component.text = text; Ok(new_component) diff --git a/futuremod_engine/src/plugins/plugin_environment.rs b/futuremod_engine/src/plugins/plugin_environment.rs index ba5c173..5464121 100644 --- a/futuremod_engine/src/plugins/plugin_environment.rs +++ b/futuremod_engine/src/plugins/plugin_environment.rs @@ -7,13 +7,9 @@ use std::{ }; use super::library::{ - dangerous::create_dangerous_library, - game::create_game_library, - input::create_input_library, - matrix::create_matrix_library, - settings::create_settings_library_new, - system::create_system_library, - ui::create_ui_library, + dangerous::create_dangerous_library, game::create_game_library, input::create_input_library, + matrix::create_matrix_library, settings::create_settings_library_new, + system::create_system_library, ui::create_ui_library, }; use anyhow::bail; use futuremod_data::plugin::{PluginDependency, PluginInfo}; From 90eeb5b19901255f5b89c433c6b3b7148b57ff48 Mon Sep 17 00:00:00 2001 From: Simon Kurz Date: Thu, 14 Nov 2024 20:26:22 +0100 Subject: [PATCH 08/10] Add shared components for settings --- futuremod_data/src/plugin/mod.rs | 4 ++ futuremod_data/src/{ => plugin}/plugin.rs | 0 futuremod_data/src/plugin/settings.rs | 52 +++++++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 futuremod_data/src/plugin/mod.rs rename futuremod_data/src/{ => plugin}/plugin.rs (100%) create mode 100644 futuremod_data/src/plugin/settings.rs diff --git a/futuremod_data/src/plugin/mod.rs b/futuremod_data/src/plugin/mod.rs new file mode 100644 index 0000000..3be63d8 --- /dev/null +++ b/futuremod_data/src/plugin/mod.rs @@ -0,0 +1,4 @@ +pub mod plugin; +pub mod settings; + +pub use plugin::*; \ No newline at end of file diff --git a/futuremod_data/src/plugin.rs b/futuremod_data/src/plugin/plugin.rs similarity index 100% rename from futuremod_data/src/plugin.rs rename to futuremod_data/src/plugin/plugin.rs diff --git a/futuremod_data/src/plugin/settings.rs b/futuremod_data/src/plugin/settings.rs new file mode 100644 index 0000000..5b58b9e --- /dev/null +++ b/futuremod_data/src/plugin/settings.rs @@ -0,0 +1,52 @@ +use serde::{Deserialize, Serialize}; + + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PluginSettings { + pub root: Component +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "camelCase")] +pub enum Component { + Button(Button), + Section(Section), + Text(Text), +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Button { + pub text: String, + pub disabled: bool, + pub id: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Section { + pub content: Vec, + pub id: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Text { + pub text: String, + pub id: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", tag = "type")] +pub enum Event { + ClickButton, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SettingsEvent { + pub name: String, + pub target_id: String, + pub event: Event, +} \ No newline at end of file From f1ad9175844978596776be91e0efc1f9e80efb4f Mon Sep 17 00:00:00 2001 From: Simon Kurz Date: Thu, 14 Nov 2024 20:27:58 +0100 Subject: [PATCH 09/10] Add endpoint to engine for handling settings events --- .../src/plugins/library/settings/button.rs | 1 + .../src/plugins/library/settings/settings.rs | 26 +++++++- futuremod_engine/src/plugins/mod.rs | 2 +- futuremod_engine/src/server.rs | 63 +++++++++++++------ 4 files changed, 70 insertions(+), 22 deletions(-) diff --git a/futuremod_engine/src/plugins/library/settings/button.rs b/futuremod_engine/src/plugins/library/settings/button.rs index 225966b..3285a14 100644 --- a/futuremod_engine/src/plugins/library/settings/button.rs +++ b/futuremod_engine/src/plugins/library/settings/button.rs @@ -14,6 +14,7 @@ pub struct ButtonBuilder { } #[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] pub struct Button { pub(super) text: String, pub(super) disabled: bool, diff --git a/futuremod_engine/src/plugins/library/settings/settings.rs b/futuremod_engine/src/plugins/library/settings/settings.rs index 726caf9..b79da55 100644 --- a/futuremod_engine/src/plugins/library/settings/settings.rs +++ b/futuremod_engine/src/plugins/library/settings/settings.rs @@ -4,6 +4,7 @@ use std::{ }; use anyhow::{anyhow, bail}; +use futuremod_data::plugin::settings::Event; use log::debug; use mlua::UserData; use serde::{ser::SerializeStruct, Serialize}; @@ -24,7 +25,7 @@ pub enum ComponentBuilder { } #[derive(Debug, Clone, Serialize)] -#[serde(tag = "type")] +#[serde(tag = "type", rename_all = "camelCase")] pub enum Component { Button(Button), Section(Section), @@ -52,6 +53,29 @@ impl Serialize for PluginSettings { } } +impl PluginSettings { + pub fn handle_event(&mut self, id: String, event: Event) -> Result<(), anyhow::Error> { + match event { + Event::ClickButton => { + let component = self.components.get(&id) + .ok_or(anyhow!("No component with id '{}' exists", id))?; + + if let Component::Button(button) = component.as_ref() { + match &button.on_click { + Some(button_callback) => { + button_callback.call::<()>(()) + .map_err(|e| anyhow!("onClick function errored: {}", e))?; + }, + None => bail!("Button '{}' has no onClick callback", id), + } + } + + return Ok(()) + }, + } + } +} + impl UserData for PluginSettings {} pub(super) fn generate_new_id(map: &HashMap) -> String { diff --git a/futuremod_engine/src/plugins/mod.rs b/futuremod_engine/src/plugins/mod.rs index 46ac7c4..c786449 100644 --- a/futuremod_engine/src/plugins/mod.rs +++ b/futuremod_engine/src/plugins/mod.rs @@ -1,4 +1,4 @@ -mod library; +pub mod library; pub mod plugin; mod plugin_environment; pub mod plugin_info; diff --git a/futuremod_engine/src/server.rs b/futuremod_engine/src/server.rs index 31909da..06f66b4 100644 --- a/futuremod_engine/src/server.rs +++ b/futuremod_engine/src/server.rs @@ -10,7 +10,7 @@ use axum::{ routing::{get, post, put}, BoxError, Json, Router, }; -use futuremod_data::plugin::PluginInfo; +use futuremod_data::plugin::{settings::SettingsEvent, PluginInfo}; use futures::Stream; use futures::TryStreamExt; use kv::Key; @@ -77,6 +77,7 @@ fn serve(config: Config) -> Result<(), Error> { .route("/plugin/uninstall", post(uninstall_plugin)) .route("/plugin/info", put(get_plugin_info)) .route("/plugin/settings", put(get_plugin_settings)) + .route("/plugin/settings/event", put(handle_settings_event)) .route("/log", get(log_handler)); axum::Server::bind( @@ -334,28 +335,50 @@ async fn reload_plugin(Json(payload): Json) -> impl IntoResponse { }) } +fn get_plugin_settings_from_manager(name: &str, plugin_manager: &PluginManager) -> Result { + match plugin_manager.get_plugin_settings(name) { + Ok(optional_result) => match optional_result { + None => { + debug!("Plugin has not settings"); + Err(StatusCode::NOT_FOUND.into_response()) + } + Some(settings) => { + debug!("Plugin has settings, returning"); + Ok(settings) + } + }, + Err(e) => match e { + PluginManagerError::PluginNotFound => Err(StatusCode::NOT_FOUND.into_response()), + e => Err(( + StatusCode::INTERNAL_SERVER_ERROR, + AppError(anyhow!("{:?}", e)), + ) + .into_response()), + }, + } +} + async fn get_plugin_settings(Json(payload): Json) -> impl IntoResponse { debug!("Getting settings of '{}'", payload.name); with_plugin_manager(|plugin_manager| -> Response { - match plugin_manager.get_plugin_settings(&payload.name) { - Ok(optional_result) => match optional_result { - None => { - debug!("Plugin has not settings"); - StatusCode::NO_CONTENT.into_response() - } - Some(settings) => { - debug!("Plugin has settings, returning"); - Json(settings).into_response() - } - }, - Err(e) => match e { - PluginManagerError::PluginNotFound => StatusCode::NOT_FOUND.into_response(), - e => ( - StatusCode::INTERNAL_SERVER_ERROR, - AppError(anyhow!("{:?}", e)), - ) - .into_response(), - }, + match get_plugin_settings_from_manager(&payload.name, plugin_manager) { + Ok(settings) => Json(settings).into_response(), + Err(e) => e.into_response(), + } + }) +} + + +async fn handle_settings_event(Json(payload): Json) -> impl IntoResponse { + debug!("Handling settings event: {:?}", payload); + + with_plugin_manager(|plugin_manager| { + match get_plugin_settings_from_manager(&payload.name, plugin_manager) { + Err(e) => e.into_response(), + Ok(mut settings) => match settings.handle_event(payload.target_id.clone(), payload.event.clone()) { + Ok(_) => StatusCode::NO_CONTENT.into_response(), + Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("{}", e)).into_response(), + } } }) } From 9566570e6a5857ee5d4ea42a0e1467155725ccb9 Mon Sep 17 00:00:00 2001 From: Simon Kurz Date: Thu, 14 Nov 2024 20:30:10 +0100 Subject: [PATCH 10/10] Basic support for plugin settings --- futuremod/src/api.rs | 64 ++++++++++++++++++++++++- futuremod/src/view/dashboard/state.rs | 17 +++++-- futuremod/src/view/plugin/components.rs | 55 +++++++++++++++++++-- futuremod/src/view/plugin/mod.rs | 1 + futuremod/src/view/plugin/state.rs | 25 ++++++++++ futuremod/src/view/plugin/view.rs | 37 ++++++++++---- 6 files changed, 181 insertions(+), 18 deletions(-) create mode 100644 futuremod/src/view/plugin/state.rs diff --git a/futuremod/src/api.rs b/futuremod/src/api.rs index 51925fd..5047bde 100644 --- a/futuremod/src/api.rs +++ b/futuremod/src/api.rs @@ -4,11 +4,11 @@ use crate::config; use anyhow::{anyhow, bail}; use log::info; use reqwest::Body; -use serde::de::DeserializeOwned; +use serde::{de::DeserializeOwned, Serialize}; use tokio::fs; use tokio_util::codec::{BytesCodec, FramedRead}; -use futuremod_data::plugin::{Plugin, PluginInfo}; +use futuremod_data::plugin::{settings::{Event, PluginSettings, SettingsEvent}, Plugin, PluginInfo}; pub fn build_url(path: &str) -> String { let config = config::get(); @@ -236,3 +236,63 @@ pub async fn disable_plugin(name: String) -> Result<(), anyhow::Error> { Ok(()) } + +#[derive(Debug, Clone, Serialize)] +pub struct PluginByName { + pub name: String, +} + + +pub async fn get_plugin_settings(name: String) -> Result { + let body = PluginByName { + name, + }; + + let response = reqwest::Client::new() + .put(build_url("/plugin/settings")) + .json(&body) + .send() + .await + .map_err(|e| anyhow!("Could not send request to get settings: {}", e))?; + + if !response.status().is_success() { + let response_text = response + .text() + .await + .map_err(|e| anyhow!("Could nto get response content: {}", e))?; + + bail!("{}", response_text); + } + + response + .json() + .await + .map_err(|e| anyhow!("Plugin settings has unexpected format: {}", e)) +} + +pub async fn send_settings_event(name: String, id: String, event: Event) -> Result<(), anyhow::Error> { + let body = SettingsEvent { + name, + event, + target_id: id, + }; + + let response = reqwest::Client::new() + .put(build_url("/plugin/settings/event")) + .json(&body) + .send() + .await + .map_err(|e| anyhow!("Could not send request for settings event: {}", e))?; + + if !response.status().is_success() { + let response_text = response + .text() + .await + + .map_err(|e| anyhow!("Could not get response content: {}", e))?; + + bail!("{}", response_text); + } + + Ok(()) +} \ No newline at end of file diff --git a/futuremod/src/view/dashboard/state.rs b/futuremod/src/view/dashboard/state.rs index ffabae3..aa68ed5 100644 --- a/futuremod/src/view/dashboard/state.rs +++ b/futuremod/src/view/dashboard/state.rs @@ -258,7 +258,9 @@ pub fn update(dashboard: &mut Dashboard, message: Message) -> Task { let plugin = dashboard.plugins.get(&name); match plugin { Some(plugin) => { - dashboard.view = View::Plugin(view::plugin::Plugin::new(plugin)); + let (view, message) = view::plugin::Plugin::new(plugin); + dashboard.view = View::Plugin(view); + return message.map(Message::Plugin); } None => {} } @@ -290,12 +292,17 @@ pub fn update(dashboard: &mut Dashboard, message: Message) -> Task { }, _ => (), }, - View::Plugin(_) => match message { + View::Plugin(plugin_view) => match message { Message::Plugin(plugin_message) => match plugin_message { - view::plugin::Message::GoBack => { - return Task::done(Message::ToPluginList); + view::plugin::Message::GoBack => return Task::done(Message::ToPluginList), + plugin_message => { + if let Some(plugin) = dashboard.plugins.get(&plugin_view.name) { + return plugin_view.update(plugin, plugin_message) + .map(Message::Plugin); + } else { + warn!("Plugin view active but plugin '{}' not found", plugin_view.name); + } } - _ => (), }, _ => (), }, diff --git a/futuremod/src/view/plugin/components.rs b/futuremod/src/view/plugin/components.rs index 606c61d..cf5925c 100644 --- a/futuremod/src/view/plugin/components.rs +++ b/futuremod/src/view/plugin/components.rs @@ -1,6 +1,6 @@ -use futuremod_data::plugin::{Plugin, PluginDependency, PluginState}; +use futuremod_data::plugin::{settings::{self, Component, PluginSettings}, Plugin, PluginDependency, PluginState}; use iced::{ - widget::{column, container, markdown, row, text, Scrollable, Toggler}, + widget::{column, container, markdown, row, scrollable, text, Scrollable, Toggler}, Alignment, Length, Padding, }; use iced_fonts::Bootstrap; @@ -14,7 +14,7 @@ use crate::{ }, }; -use super::Message; +use super::{view::FutureResult, Message}; fn plugin_reload_button<'a>(plugin: &Plugin) -> Element<'a, Message> { icon_text_button(Bootstrap::ArrowClockwise, "Reload") @@ -111,6 +111,7 @@ pub fn plugin_details_view<'a>( ]) .padding(8), plugin_details_content(&plugin_view.description, plugin), + plugin_info_box(loading_settings(&plugin_view.settings)), ] .into() } @@ -207,3 +208,51 @@ fn plugin_toggle_button<'a>(plugin: &Plugin) -> Option> { .into(), ) } + +fn loading_settings<'a>(loading_settings: &'a FutureResult) -> Element<'a, Message> { + match loading_settings { + FutureResult::Loading => text("Loading...").into(), + FutureResult::Finished(plugin_settings) => settings(plugin_settings), + FutureResult::Error(e) => text(format!("Could not load plugin settings: {}", e)).into(), + } +} + +fn settings<'a>(settings: &'a PluginSettings) -> Element<'a, Message> { + scrollable(render_component(&settings.root)).into() +} + +fn render_component<'a>(component: &'a settings::Component) -> Element<'a, Message> { + match component { + Component::Button(button) => render_button(button), + Component::Section(section) => render_section(section), + Component::Text(text) => render_text(text), + } +} + +fn render_button<'a>(settings_button: &'a settings::Button) -> Element<'a, Message> { + button(text(&settings_button.text)) + .on_press_maybe(if settings_button.disabled { + None + } else { + Some(Message::SettingsEvent(settings_button.id.clone(), settings::Event::ClickButton)) + }) + .into() +} + +fn render_section<'a>(settings_section: &'a settings::Section) -> Element<'a, Message> { + let mut section = Column::new(); + + for component in settings_section.content.iter() { + section = section.push(render_component(&component)); + } + + section + .spacing(8) + .padding(Padding{top: 16.0, right: 0.0, bottom: 16.0, left: 0.0}) + .into() +} + +fn render_text<'a>(settings_text: &'a settings::Text) -> Element<'a, Message> { + text(&settings_text.text) + .into() +} \ No newline at end of file diff --git a/futuremod/src/view/plugin/mod.rs b/futuremod/src/view/plugin/mod.rs index e4e14b8..4ba2f11 100644 --- a/futuremod/src/view/plugin/mod.rs +++ b/futuremod/src/view/plugin/mod.rs @@ -1,4 +1,5 @@ mod components; mod view; +mod state; pub use view::{Message, Plugin}; diff --git a/futuremod/src/view/plugin/state.rs b/futuremod/src/view/plugin/state.rs new file mode 100644 index 0000000..c05f625 --- /dev/null +++ b/futuremod/src/view/plugin/state.rs @@ -0,0 +1,25 @@ +use futures::TryFutureExt; +use iced::Task; +use log::info; + +use crate::api::send_settings_event; + +use super::{view::FutureResult, Message}; + +pub(super) fn update(view: &mut super::Plugin, plugin: &futuremod_data::plugin::Plugin, message: Message) -> Task { + match message { + Message::HandlePluginSettingsResponse(result) => match result { + Ok(settings) => view.settings = FutureResult::Finished(settings), + Err(e) => view.settings = FutureResult::Error(e), + }, + Message::SettingsEvent(id, event) => { + return Task::perform(send_settings_event(plugin.info.name.clone(), id, event).map_err(|e| e.to_string()), Message::EventResponse); + }, + Message::EventResponse(response) => { + info!("Event response: {:?}", response); + } + _ => (), + } + + Task::none() +} \ No newline at end of file diff --git a/futuremod/src/view/plugin/view.rs b/futuremod/src/view/plugin/view.rs index 79d0257..2902424 100644 --- a/futuremod/src/view/plugin/view.rs +++ b/futuremod/src/view/plugin/view.rs @@ -1,13 +1,23 @@ -use iced::widget::markdown; +use futuremod_data::plugin::settings::{Event, PluginSettings}; +use futures::TryFutureExt; +use iced::{widget::markdown, Task}; -use crate::widget::Element; +use crate::{api::get_plugin_settings, widget::Element}; -use super::components::plugin_details_view; +use super::{components::plugin_details_view, state::update}; + +#[derive(Debug, Clone)] +pub enum FutureResult { + Loading, + Finished(T), + Error(E), +} #[derive(Debug, Clone)] pub struct Plugin { pub name: String, pub description: Vec, + pub settings: FutureResult, } #[derive(Debug, Clone)] @@ -18,16 +28,27 @@ pub enum Message { Reload(String), UninstallPrompt(String), OpenUrl(reqwest::Url), + HandlePluginSettingsResponse(Result), + SettingsEvent(String, Event), + EventResponse(Result<(), String>), } impl Plugin { - pub fn new(plugin: &futuremod_data::plugin::Plugin) -> Self { + pub fn new(plugin: &futuremod_data::plugin::Plugin) -> (Self, Task) { let description = markdown::parse(&plugin.info.description).collect(); - Plugin { - name: plugin.info.name.clone(), - description, - } + ( + Plugin { + name: plugin.info.name.clone(), + description, + settings: FutureResult::Loading, + }, + Task::perform(get_plugin_settings(plugin.info.name.clone()).map_err(|e| e.to_string()), Message::HandlePluginSettingsResponse), + ) + } + + pub fn update(&mut self, plugin: &futuremod_data::plugin::Plugin, message: Message) -> Task { + update(self, plugin, message) } pub fn view<'a>(&'a self, plugin: &futuremod_data::plugin::Plugin) -> Element<'a, Message> {