Implement async support in metatype

This commit is contained in:
2025-06-25 01:36:43 +10:00
parent cbf786206d
commit 2352cb0225
4 changed files with 169 additions and 85 deletions

View File

@@ -1,7 +1,7 @@
use crate::{
__internal::{display, type_id},
Cdef, CdefBuilder, IntoFfi, Metatype, MetatypeBuilder, Type, TypeBuilder, TypeType,
UnsafeExternCFn,
Cdef, CdefBuilder, FfiReturnConvention, IntoFfi, Metatype, MetatypeBuilder, Type, TypeBuilder,
TypeType, UnsafeExternCFn,
};
use luaify::luaify;
use std::{
@@ -168,6 +168,11 @@ unsafe impl<F: Future<Output: IntoFfi> + 'static> Metatype for lua_future<F> {
unsafe impl<F: Future<Output: IntoFfi> + 'static> IntoFfi for lua_future<F> {
type Into = lua_future<F>;
fn convention() -> FfiReturnConvention {
// futures are always returned by-value due to rust type inference limitations
FfiReturnConvention::ByValue
}
fn convert(self) -> Self::Into {
self
}

View File

@@ -387,6 +387,12 @@ impl<'r> Drop for MetatypeBuilder<'r> {
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FfiReturnConvention {
ByValue,
ByOutParam,
}
pub unsafe trait FromFfi: Sized {
type From: Type + Sized;
@@ -404,6 +410,13 @@ pub unsafe trait FromFfi: Sized {
pub unsafe trait IntoFfi: Sized {
type Into: Type + Sized;
fn convention() -> FfiReturnConvention {
match Self::Into::ty() {
TypeType::Void | TypeType::Primitive => FfiReturnConvention::ByValue,
TypeType::Aggregate => FfiReturnConvention::ByOutParam,
}
}
fn postlude(_ret: &str) -> impl Display {
""
}
@@ -414,8 +427,9 @@ pub unsafe trait IntoFfi: Sized {
#[derive(Debug)]
pub struct MetatypeMethodBuilder<'r, 'm> {
metatype: &'m mut MetatypeBuilder<'r>,
params: String, // parameters to the lua function
args: String, // arguments to the C call
lparams: String, // parameters to the lua function
cparams: String, // parameters to the lua function
cargs: String, // arguments to the C call
prelude: String, // function body prelude
postlude: String, // function body postlude
}
@@ -424,8 +438,9 @@ impl<'r, 'm> MetatypeMethodBuilder<'r, 'm> {
pub fn new(metatype: &'m mut MetatypeBuilder<'r>) -> Self {
Self {
metatype,
params: String::new(),
args: String::new(),
lparams: String::new(),
cparams: String::new(),
cargs: String::new(),
prelude: String::new(),
postlude: String::new(),
}
@@ -437,18 +452,32 @@ impl<'r, 'm> MetatypeMethodBuilder<'r, 'm> {
"cannot declare void parameter"
);
(!self.params.is_empty()).then(|| self.params.push_str(", "));
(!self.args.is_empty()).then(|| self.args.push_str(", "));
write!(self.params, "{name}").unwrap();
write!(self.args, "{name}").unwrap();
let Self {
metatype: MetatypeBuilder { registry, .. },
lparams,
cparams,
cargs,
prelude,
postlude,
..
} = self;
registry.include::<T::From>();
(!lparams.is_empty()).then(|| lparams.push_str(", "));
(!cparams.is_empty()).then(|| cparams.push_str(", "));
(!cargs.is_empty()).then(|| cargs.push_str(", "));
write!(lparams, "{name}").unwrap();
write!(cparams, "{}", T::From::cdecl(&name)).unwrap();
write!(cargs, "{name}").unwrap();
if T::require_keepalive() {
write!(self.prelude, "local __keep_{name} = {name}; ").unwrap();
write!(self.postlude, "__C.{KEEP_FN}(__keep_{name}); ").unwrap();
write!(prelude, "local __keep_{name} = {name}; ").unwrap();
write!(postlude, "__C.{KEEP_FN}(__keep_{name}); ").unwrap();
}
let name = name.to_string();
write!(self.prelude, "{}", T::prelude(&name)).unwrap();
write!(prelude, "{}", T::prelude(&name.to_string())).unwrap();
self
}
@@ -458,13 +487,27 @@ impl<'r, 'm> MetatypeMethodBuilder<'r, 'm> {
// this passes one lua `string` argument as two C `const uint8_t *ptr` and `uintptr_t len`
// arguments, bypassing the slower generic `&[u8]: FromFfi` path which constructs a
// temporary cdata to pass the string and its length in one argument
(!self.params.is_empty()).then(|| self.params.push_str(", "));
(!self.args.is_empty()).then(|| self.args.push_str(", "));
write!(self.params, "{name}").unwrap();
write!(self.args, "{name}, __{name}_len").unwrap();
write!(self.prelude, "local __{name}_len = 0; ").unwrap();
let Self {
lparams,
cparams,
cargs,
prelude,
..
} = self;
let param_ptr = <*const u8>::cdecl("ptr");
let param_len = usize::cdecl("len");
(!lparams.is_empty()).then(|| lparams.push_str(", "));
(!cparams.is_empty()).then(|| cparams.push_str(", "));
(!cargs.is_empty()).then(|| cargs.push_str(", "));
write!(lparams, "{name}").unwrap();
write!(cparams, "{param_ptr}, {param_len}",).unwrap();
write!(cargs, "{name}, __{name}_len").unwrap();
write!(prelude, "local __{name}_len = 0; ").unwrap();
write!(
self.prelude,
prelude,
r#"if {name} ~= nil then assert(type({name}) == "string", "string expected in argument '{name}', got " .. type({name})); __{name}_len = #{name}; end; "#
)
.unwrap();
@@ -472,46 +515,63 @@ impl<'r, 'm> MetatypeMethodBuilder<'r, 'm> {
}
pub fn param_ignored(&mut self) -> &mut Self {
(!self.params.is_empty()).then(|| self.params.push_str(", "));
write!(self.params, "_").unwrap();
(!self.lparams.is_empty()).then(|| self.lparams.push_str(", "));
write!(self.lparams, "_").unwrap();
self
}
pub fn call<T: IntoFfi>(&mut self, func: impl Display) {
let Self {
metatype,
params,
args,
metatype:
MetatypeBuilder {
registry,
cdef,
lua,
..
},
lparams,
cparams,
cargs,
prelude,
postlude,
..
} = self;
let lua = &mut metatype.lua;
write!(lua, "function({params}) {prelude}").unwrap();
registry.include::<T::Into>();
write!(lua, "function({lparams}) {prelude}").unwrap();
match T::Into::ty() {
TypeType::Void => {
write!(lua, "__C.{func}({args}); {postlude}end").unwrap();
match T::convention() {
FfiReturnConvention::ByValue => {
if T::Into::ty() == TypeType::Void {
write!(lua, "__C.{func}({cargs}); {postlude}end").unwrap();
} else {
let check = T::postlude("__res");
write!(lua, "local __res = __C.{func}({cargs}); ").unwrap();
write!(lua, "{check}{postlude}return __res; end").unwrap();
}
writeln!(cdef, "{};", T::Into::cdecl(display!("{func}({cparams})"))).unwrap();
}
TypeType::Primitive => {
let check = T::postlude("__res");
write!(
lua,
"local __res = __C.{func}({args}); {check}{postlude}return __res; end"
)
.unwrap();
}
TypeType::Aggregate => {
FfiReturnConvention::ByOutParam => {
let ct = T::Into::name();
let check = T::postlude("__res");
write!(lua, "local __res = __new(__ct.{ct}); __C.{func}(__res").unwrap();
if !args.is_empty() {
write!(lua, ", {args}").unwrap();
if !cargs.is_empty() {
write!(lua, ", {cargs}").unwrap();
}
write!(lua, "); {check}{postlude}return __res; end").unwrap()
write!(lua, "); {check}{postlude}return __res; end").unwrap();
write!(cdef, "void {func}({}", <*mut T::Into>::cdecl("out")).unwrap();
if !cparams.is_empty() {
write!(cdef, ", {cparams}").unwrap();
}
writeln!(cdef, ");").unwrap();
}
}
}
pub fn call_inferred<T: IntoFfi>(&mut self, func: impl Display, _infer: impl FnOnce() -> T) {
self.call::<T>(func)
}
}
//