diff --git a/turing/benches/lua_api_bench.rs b/turing/benches/lua_api_bench.rs index 2531f87..0473b94 100644 --- a/turing/benches/lua_api_bench.rs +++ b/turing/benches/lua_api_bench.rs @@ -42,11 +42,11 @@ extern "C" fn fetch_string( fn setup_turing_for_lua() -> Turing { let mut turing = Turing::new(); - let mut meta = ScriptFnMetadata::new(Some("test".to_owned()), log_info_wasm, None); + let mut meta = ScriptFnMetadata::new("test".to_owned(), log_info_wasm, None); let _ = meta.add_param_type(DataType::RustString, "msg"); turing.add_function("Log.info", meta).unwrap(); - let mut meta = ScriptFnMetadata::new(Some("test".to_owned()), fetch_string, None); + let mut meta = ScriptFnMetadata::new("test".to_owned(), fetch_string, None); let _ = meta.add_return_type(DataType::ExtString); turing.add_function("fetch_string", meta).unwrap(); diff --git a/turing/benches/wasm_api_bench.rs b/turing/benches/wasm_api_bench.rs index 912fca0..8b035f3 100644 --- a/turing/benches/wasm_api_bench.rs +++ b/turing/benches/wasm_api_bench.rs @@ -44,11 +44,11 @@ extern "C" fn fetch_string(_params: FfiParamArray) -> FfiParam { fn setup_turing_with_callbacks() -> Turing { let mut turing = Turing::new(); - let mut meta = ScriptFnMetadata::new(Some("test".to_owned()), log_info_wasm, None); + let mut meta = ScriptFnMetadata::new("test".to_owned(), log_info_wasm, None); let _ = meta.add_param_type(DataType::RustString, "msg"); turing.add_function("log_info", meta).unwrap(); - let mut meta = ScriptFnMetadata::new(Some("test".to_owned()), fetch_string, None); + let mut meta = ScriptFnMetadata::new("test".to_owned(), fetch_string, None); let _ = meta.add_return_type(DataType::ExtString); turing.add_function("fetch_string", meta).unwrap(); diff --git a/turing/src/engine/lua_engine.rs b/turing/src/engine/lua_engine.rs index c93e36f..ddd735f 100644 --- a/turing/src/engine/lua_engine.rs +++ b/turing/src/engine/lua_engine.rs @@ -212,13 +212,13 @@ impl LuaInterpreter { fn generate_function(&self, lua: &Lua, table: &Table, name: &str, metadata: &ScriptFnMetadata) -> Result<()> { let cap = metadata.capability.clone(); let callback = metadata.callback; - let pts = metadata.param_types.clone(); + let pts = metadata.param_types.iter().map(|d| d.data_type).collect::>(); let data = Arc::clone(&self.data); let func = lua.create_function(move |lua, args: LuaVariadic| -> mlua::Result { lua_bind_env::( - &data, lua, cap.as_deref(), &args, pts.as_slice(), &callback + &data, lua, &cap, &args, &pts, &callback ) }).map_err(|e| anyhow!("Failed to create function: {e}"))?; @@ -446,13 +446,13 @@ impl LuaInterpreter { fn lua_bind_env( data: &Arc>, lua: &Lua, - cap: Option<&str>, + cap: &str, ps: &LuaVariadic, p: &[DataType], func: &ScriptCallback, ) -> mlua::Result { - if let Some(cap) = cap && !data.read().active_capabilities.contains(cap) { + if !data.read().active_capabilities.contains(cap) { return Err(mlua::Error::RuntimeError(format!("Mod capability '{cap}' is not currently loaded"))) } diff --git a/turing/src/engine/types.rs b/turing/src/engine/types.rs index da60f9d..3b431f3 100644 --- a/turing/src/engine/types.rs +++ b/turing/src/engine/types.rs @@ -1,51 +1,72 @@ -use anyhow::anyhow; use crate::interop::params::{DataType, FfiParam, FfiParamArray}; +use anyhow::anyhow; pub type ScriptCallback = extern "C" fn(FfiParamArray) -> FfiParam; +#[derive(Clone)] +pub struct ScriptFnParameter { + pub name: String, + pub data_type: DataType, + pub data_type_name: String, +} #[derive(Clone)] pub struct ScriptFnMetadata { - pub capability: Option, + pub capability: String, pub callback: ScriptCallback, - pub param_types: Vec, - pub param_type_names: Vec<(String, String)>, - pub return_type: Vec, - pub return_type_names: Vec, + pub param_types: Vec, + pub return_type: Vec<(DataType, String)>, pub doc_comment: Option, } impl ScriptFnMetadata { - pub fn new(capability: Option, callback: ScriptCallback, doc_comment: Option) -> Self { + pub fn new( + capability: String, + callback: ScriptCallback, + doc_comment: Option, + ) -> Self { Self { capability, callback, param_types: Vec::new(), - param_type_names: Vec::new(), return_type: Vec::new(), - return_type_names: Vec::new(), doc_comment, } } /// May error if DataType is not a valid parameter type - pub fn add_param_type(&mut self, p: DataType, param_name: impl ToString) -> anyhow::Result<&mut Self> { - if !p.is_valid_param_type() { - return Err(anyhow!("DataType '{}' is not a valid parameter type", p)) - } - self.param_types.push(p); - self.param_type_names.push((param_name.to_string(), p.as_spec_param_type()?.to_string())); + pub fn add_param_type( + &mut self, + p: DataType, + param_name: impl ToString, + ) -> anyhow::Result<&mut Self> { + if !p.is_valid_param_type() { + return Err(anyhow!("DataType '{}' is not a valid parameter type", p)); + } + self.param_types.push(ScriptFnParameter { + name: param_name.to_string(), + data_type: p, + data_type_name: p.as_spec_param_type()?.to_string(), + }); Ok(self) } /// May error if DataType is not a valid parameter type - pub fn add_param_type_named(&mut self, p: DataType, param_name: String, type_name: String) -> anyhow::Result<&mut Self> { + pub fn add_param_type_named( + &mut self, + p: DataType, + param_name: String, + type_name: String, + ) -> anyhow::Result<&mut Self> { if !p.is_valid_param_type() { - return Err(anyhow!("DataType '{}' is not a valid parameter type", p)) + return Err(anyhow!("DataType '{}' is not a valid parameter type", p)); } - self.param_types.push(p); - self.param_type_names.push((param_name, type_name)); + self.param_types.push(ScriptFnParameter { + name: param_name, + data_type: p, + data_type_name: type_name, + }); Ok(self) } @@ -53,24 +74,24 @@ impl ScriptFnMetadata { /// May error if DataType is not a valid return type pub fn add_return_type(&mut self, r: DataType) -> anyhow::Result<&mut Self> { if !r.is_valid_return_type() { - return Err(anyhow!("DataType '{}' is not a valid return type", r)) + return Err(anyhow!("DataType '{}' is not a valid return type", r)); } - self.return_type.push(r); - self.return_type_names.push(r.as_spec_return_type()?.to_string()); + self.return_type.push((r, r.as_spec_return_type()?.to_string())); Ok(self) } /// May error if DataType is not a valid return type - pub fn add_return_type_named(&mut self, r: DataType, type_name: String) -> anyhow::Result<&mut Self> { + pub fn add_return_type_named( + &mut self, + r: DataType, + type_name: String, + ) -> anyhow::Result<&mut Self> { if !r.is_valid_return_type() { - return Err(anyhow!("DataType '{}' is not a valid return type", r)) + return Err(anyhow!("DataType '{}' is not a valid return type", r)); } - self.return_type.push(r); - self.return_type_names.push(type_name); + self.return_type.push((r, type_name)); Ok(self) } - - } impl DataType { @@ -89,7 +110,9 @@ impl DataType { DataType::Bool => "bool", DataType::RustString | DataType::ExtString => "&str", DataType::Object => return Err(anyhow!("Cannot derive type name from 'Object'")), - DataType::RustError | DataType::ExtError => return Err(anyhow!("Error is not a valid param type")), + DataType::RustError | DataType::ExtError => { + return Err(anyhow!("Error is not a valid param type")); + } DataType::Void => return Err(anyhow!("Void is not a valid param type")), DataType::Vec2 => "Vec2", DataType::Vec3 => "Vec3", @@ -114,7 +137,9 @@ impl DataType { DataType::Bool => "bool", DataType::RustString | DataType::ExtString => "String", DataType::Object => return Err(anyhow!("Cannot derive type name from 'Object'")), - DataType::RustError | DataType::ExtError => return Err(anyhow!("Error is not a valid param type")), + DataType::RustError | DataType::ExtError => { + return Err(anyhow!("Error is not a valid param type")); + } DataType::Void => "void", DataType::Vec2 => "Vec2", DataType::Vec3 => "Vec3", @@ -124,4 +149,3 @@ impl DataType { }) } } - diff --git a/turing/src/engine/wasm_engine.rs b/turing/src/engine/wasm_engine.rs index d0e5e1d..c4e3c59 100644 --- a/turing/src/engine/wasm_engine.rs +++ b/turing/src/engine/wasm_engine.rs @@ -615,18 +615,18 @@ impl WasmInterpreter { let mut name = name.replace(":", "_").replace(".", "_").to_case(Case::Snake); name.insert(0, '_'); - let p_types = metadata.param_types.iter().map(|d| d.to_val_type()).collect::>>()?; + let p_types = metadata.param_types.iter().map(|d| d.data_type.to_val_type()).collect::>>()?; // if the only return type is void, we treat it as no return types - let r_types = if metadata.return_type.len() == 1 && metadata.return_type.first().cloned() == Some(DataType::Void) { + let r_types = if metadata.return_type.len() == 1 && metadata.return_type.first().cloned().map(|r| r.0) == Some(DataType::Void) { Vec::new() } else { - metadata.return_type.iter().map(|d| d.to_val_type()).collect::>>()? + metadata.return_type.iter().map(|d| d.0.to_val_type()).collect::>>()? }; let ft = FuncType::new(engine, p_types, r_types); let cap = metadata.capability.clone(); let callback = metadata.callback; - let pts = metadata.param_types.clone(); + let pts = metadata.param_types.iter().map(|d| d.data_type).collect::>(); let data2 = Arc::clone(&data); linker.func_new( @@ -634,7 +634,7 @@ impl WasmInterpreter { name.as_str(), ft, move |caller, ps, rs| { - wasm_bind_env::(&data2, caller, cap.as_deref(), ps, rs, pts.as_slice(), &callback) + wasm_bind_env::(&data2, caller, &cap, ps, rs, pts.as_slice(), &callback) } )?; @@ -709,10 +709,6 @@ impl WasmInterpreter { ret_type: DataType, data: &Arc>, ) -> Param { - let Some(instance) = &mut self.script_instance else { - return Param::Error("No script is loaded or reentry was attempted".to_string()); - }; - // Fast-path: typed cache (common signatures). Falls back to dynamic call below. if let Some(entry) = self.typed_cache.get(&cache_key) { return entry.invoke(&mut self.store, params).unwrap_or_else(Param::Error) @@ -787,14 +783,14 @@ impl WasmInterpreter { fn wasm_bind_env( data: &Arc>, mut caller: Caller<'_, WasiP1Ctx>, - cap: Option<&str>, + cap: &str, ps: &[Val], rs: &mut [Val], p: &[DataType], func: &ScriptCallback, ) -> Result<()> { - if let Some(cap) = cap && !data.read().active_capabilities.contains(cap) { + if !data.read().active_capabilities.contains(cap) { return Err(anyhow!("Mod capability '{}' is not currently loaded", cap)) } diff --git a/turing/src/global_ffi/ffi.rs b/turing/src/global_ffi/ffi.rs index 7b2995d..38817e7 100644 --- a/turing/src/global_ffi/ffi.rs +++ b/turing/src/global_ffi/ffi.rs @@ -121,12 +121,12 @@ unsafe extern "C" fn turing_create_script_data( callback: ScriptCallback, doc_comment: *const c_char, ) -> *mut ScriptFnMetadata { + if capability.is_null() { + panic!("turing_create_script_data(): capability must be a valid string pointer, null is not allowed"); + } + let cap = unsafe { - capability - .as_ref() - .map(|s| CStr::from_ptr(s)) - .map(|s| s.to_string_lossy()) - .map(|s| s.to_string()) + CStr::from_ptr(capability).to_string_lossy().to_string() }; let doc = if doc_comment.is_null() { @@ -150,30 +150,29 @@ unsafe extern "C" fn turing_create_script_data( unsafe extern "C" fn turing_script_data_add_param_type(data: *mut ScriptFnMetadata, params: *mut DataType, param_names: *mut *const c_char, param_type_names: *mut *const c_char, params_count: u32) -> *const c_char { let data = unsafe { &mut *data }; let array = unsafe { slice::from_raw_parts(params, params_count as usize) }; - let param_names = unsafe { slice::from_raw_parts(param_names, params_count as usize) } - .iter() - .map( - |ptr| unsafe { CStr::from_ptr(*ptr) }.to_string_lossy().into_owned() - ) - .collect::>(); - let param_type_names = unsafe { slice::from_raw_parts(param_type_names, params_count as usize) } - .iter() - .map( - |ptr| if ptr.is_null() { None } else { Some(unsafe { CStr::from_ptr(*ptr) }.to_string_lossy().into_owned()) } - ) - .collect::>>(); - - for ((ty, name), ty_name) in array.iter().zip(param_names).zip(param_type_names) { - if let Some(ty_name) = ty_name { - if let Err(e) = data.add_param_type_named(*ty, name, ty_name) { - return CString::new(format!("{}", e)).unwrap().into_raw() - } - } else { - if let Err(e) = data.add_param_type(*ty, name) { - return CString::new(format!("{}", e)).unwrap().into_raw() - } - } + let names = unsafe { slice::from_raw_parts(param_names, params_count as usize) }; + let type_names = unsafe { slice::from_raw_parts(param_type_names, params_count as usize) }; + + for i in 0..(params_count as usize) { + let ty = array[i]; + let name = unsafe { CStr::from_ptr(names[i]) }.to_string_lossy().into_owned(); + + let ty_ptr = type_names[i]; + match ty_ptr.is_null() { + true => { + if let Err(e) = data.add_param_type(ty, name) { + return CString::new(format!("{}", e)).unwrap().into_raw() + } + }, + false => { + let ty_name = unsafe { CStr::from_ptr(ty_ptr) }.to_string_lossy().into_owned(); + if let Err(e) = data.add_param_type_named(ty, name, ty_name) { + return CString::new(format!("{}", e)).unwrap().into_raw() + } + }, + }; } + ptr::null() } diff --git a/turing/src/lib.rs b/turing/src/lib.rs index d5321ee..0c5f3f1 100644 --- a/turing/src/lib.rs +++ b/turing/src/lib.rs @@ -43,6 +43,16 @@ new_key_type! { #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] pub struct ScriptFnKey(u32); +impl ScriptFnKey { + pub fn new(id: u32) -> Self { + Self(id) + } + + pub fn is_valid(&self) -> bool { + self.0 != u32::MAX + } +} + impl From for ScriptFnKey { fn from(value: u32) -> Self { ScriptFnKey(value) @@ -231,6 +241,10 @@ impl Turing { return Param::Error("No code engine is active".to_string()) }; + if !cache_key.is_valid() { + return Param::Error("Invalid function key".to_string()); + } + engine.call_fn( cache_key, params, diff --git a/turing/src/spec_gen/generator.rs b/turing/src/spec_gen/generator.rs index ef23f41..1f612ce 100644 --- a/turing/src/spec_gen/generator.rs +++ b/turing/src/spec_gen/generator.rs @@ -24,9 +24,11 @@ pub fn generate_specs( let mut output = Vec::new(); + // always generate core spec for (api, ver) in api_versions { output.push((api.clone(), generate_spec(api, *ver, metadata)?)) } + for (name, contents) in output { let path = output_directory.join(format!("{}.txt", name)); @@ -52,7 +54,8 @@ fn generate_spec(api: &str, ver: Semver, metadata: &FxHashMap>(); let class_name = names[0].to_case(Case::Pascal); @@ -125,10 +128,10 @@ impl ScriptFnMetadata { let invalid_patterns = ["`", "'", "\"", "<", ">", ":", ".", ",", "/", "?", "!", "%", "$", "#", "-", "+", "=", "|", "[", "]", "{", "}"]; - out += &self.param_type_names + out += &self.param_types .iter() - .map(|(n, tn)| { - let mut tn = tn.clone(); + .map(|info| { + let mut tn = info.data_type_name.clone(); if invalid_patterns.iter().any(|p| tn.contains(p)) { let tn_old = tn.clone(); for p in invalid_patterns { @@ -136,18 +139,14 @@ impl ScriptFnMetadata { } eprintln!("[API Generator Warning]: type name \"{tn_old}\" contains invalid characters and should be manually sanitized to avoid potential naming conflicts, using name \"{tn}\" instead"); } - n.to_string() + ": " + &tn + format!("{}: {}", info.name, tn) }) .collect::>() .join(", "); out += ") -> "; - if let Some(rtn) = self.return_type_names.get(0) { - out += rtn; - } else { - out += "void"; - } + out += self.return_type.first().map_or("void", |v| &v.1); out += " : "; out += &binding; diff --git a/turing/src/tests.rs b/turing/src/tests.rs index dc4cafc..8619cda 100644 --- a/turing/src/tests.rs +++ b/turing/src/tests.rs @@ -65,7 +65,7 @@ fn common_setup_direct(source: &str) -> Result> { let mut turing = Turing::new(); let mut metadata = ScriptFnMetadata::new( - Some("test".to_owned()), + "test".to_owned(), log_info_wasm, None, ); @@ -73,7 +73,7 @@ fn common_setup_direct(source: &str) -> Result> { turing.add_function("log.info", metadata)?; let mut metadata = ScriptFnMetadata::new( - Some("test".to_owned()), + "test".to_owned(), fetch_string, None, );