Add optional support for teal
This commit is contained in:
parent
936ea60634
commit
4c1a1588c5
|
@ -8,3 +8,7 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
mlua-sys = { version = "0.5.0", features = ["lua54"] }
|
mlua-sys = { version = "0.5.0", features = ["lua54"] }
|
||||||
thiserror = "1.0.56"
|
thiserror = "1.0.56"
|
||||||
|
elua-derive = { path = "./elua-derive", optional = true}
|
||||||
|
|
||||||
|
[features]
|
||||||
|
teal = ["elua-derive"]
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
[package]
|
||||||
|
name = "elua-derive"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bytes = "1.5.0"
|
||||||
|
proc-macro2 = "1.0.78"
|
||||||
|
quote = "1.0.35"
|
||||||
|
reqwest = { version = "0.11.24", features = ["blocking"] }
|
||||||
|
syn = "2.0.51"
|
||||||
|
tempdir = "0.3.7"
|
||||||
|
zip = "0.6.6"
|
|
@ -0,0 +1,114 @@
|
||||||
|
use std::{fs::{self, File}, io::Read};
|
||||||
|
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use quote::quote;
|
||||||
|
|
||||||
|
use syn::{parenthesized, parse_macro_input, token, Ident, LitStr};
|
||||||
|
use tempdir::TempDir;
|
||||||
|
|
||||||
|
const DEFAULT_TEAL_VERSION: &str = "0.15.3";
|
||||||
|
|
||||||
|
enum CompilerSource {
|
||||||
|
Github,
|
||||||
|
Path,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl syn::parse::Parse for CompilerSource {
|
||||||
|
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||||
|
let ident: Ident = input.parse()?;
|
||||||
|
|
||||||
|
let ident_str = ident.to_string().to_lowercase();
|
||||||
|
let ident_str = ident_str.as_str();
|
||||||
|
|
||||||
|
match ident_str {
|
||||||
|
"github" => Ok(CompilerSource::Github),
|
||||||
|
"path" => Ok(CompilerSource::Path),
|
||||||
|
_ => Err(syn::Error::new_spanned(ident, "unknown teal download source"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct EmbedInput {
|
||||||
|
source: CompilerSource,
|
||||||
|
path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl syn::parse::Parse for EmbedInput {
|
||||||
|
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||||
|
let src: CompilerSource = input.parse()?;
|
||||||
|
|
||||||
|
let path = if input.peek(token::Paren) {
|
||||||
|
let content;
|
||||||
|
let _parens: token::Paren = parenthesized!(content in input);
|
||||||
|
let path: LitStr = content.parse()?;
|
||||||
|
path.value()
|
||||||
|
} else {
|
||||||
|
DEFAULT_TEAL_VERSION.to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(EmbedInput {
|
||||||
|
source: src,
|
||||||
|
path,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro]
|
||||||
|
pub fn embed_compiler(input: TokenStream) -> TokenStream {
|
||||||
|
let input = parse_macro_input!(input as EmbedInput);
|
||||||
|
|
||||||
|
let tl_lua = match input.source {
|
||||||
|
CompilerSource::Github => {
|
||||||
|
let path = if input.path.starts_with("v") {
|
||||||
|
input.path[1..].to_string()
|
||||||
|
} else { input.path };
|
||||||
|
|
||||||
|
download_from_github(path)
|
||||||
|
},
|
||||||
|
CompilerSource::Path => {
|
||||||
|
fs::read_to_string(input.path)
|
||||||
|
.expect("Failed to read tl.lua file from provided path")
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let tl_loader_str = format!(
|
||||||
|
"local tl = (function()\n{}\nend)()\ntl.loader()\n",
|
||||||
|
tl_lua
|
||||||
|
);
|
||||||
|
let teal_compiler = quote! {
|
||||||
|
|require: &str| {
|
||||||
|
format!("{}\n return require '{}' ", #tl_loader_str, require)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
teal_compiler.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn download_teal(url: String, inner_folder: String) -> String {
|
||||||
|
let resp = reqwest::blocking::get(url).expect("failed to download teal from url");
|
||||||
|
|
||||||
|
let bytes = resp.bytes().expect("failed to get bytes from request");
|
||||||
|
|
||||||
|
let dir = TempDir::new("tl").expect("failed to create tempdir");
|
||||||
|
let dir = dir.path().join("tl.tar.gz");
|
||||||
|
fs::write(&dir, bytes).expect("failed to write teal zip file to tempdir");
|
||||||
|
|
||||||
|
let file = File::open(dir).expect("Unable to open teal zip file!");
|
||||||
|
let mut arch = zip::ZipArchive::new(&file)
|
||||||
|
.expect("failure to read teal zip file");
|
||||||
|
|
||||||
|
let mut teal_lua = arch.by_name(&format!("{}/tl.lua", inner_folder))
|
||||||
|
.expect("failed to find tl.lua file inside zip archive");
|
||||||
|
|
||||||
|
let mut contents = Vec::with_capacity(10000);
|
||||||
|
teal_lua.read_to_end(&mut contents).expect("Failed to read zip file into buffer");
|
||||||
|
|
||||||
|
String::from_utf8(contents).expect("Failure to retrieve tl.lua as utf8 string!")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn download_from_github(version: String) -> String {
|
||||||
|
let url = format!("https://github.com/teal-language/tl/archive/v{}.zip", version);
|
||||||
|
let inner = format!("tl-{version}");
|
||||||
|
|
||||||
|
download_teal(url, inner)
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
use elua_derive::embed_compiler;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn from_github() {
|
||||||
|
let compiler = embed_compiler!(Github);
|
||||||
|
}
|
|
@ -43,6 +43,11 @@ pub use table_iter::*;
|
||||||
pub mod table_pairs;
|
pub mod table_pairs;
|
||||||
pub use table_pairs::*;
|
pub use table_pairs::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "teal")]
|
||||||
|
pub mod teal;
|
||||||
|
#[cfg(feature = "teal")]
|
||||||
|
pub use teal::*;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod tests;
|
pub mod tests;
|
||||||
|
|
||||||
|
|
|
@ -499,7 +499,7 @@ impl State {
|
||||||
|
|
||||||
pub(crate) fn create_userdata_metatable<'a, T: Userdata + 'static>(&'a self) -> Result<Table<'a>> {
|
pub(crate) fn create_userdata_metatable<'a, T: Userdata + 'static>(&'a self) -> Result<Table<'a>> {
|
||||||
let mut builder = UserdataBuilder::<T>::new();
|
let mut builder = UserdataBuilder::<T>::new();
|
||||||
T::build(self, &mut builder)?;
|
T::build(&mut builder);
|
||||||
|
|
||||||
let getters = builder.field_getters;
|
let getters = builder.field_getters;
|
||||||
let setters = builder.field_setters;
|
let setters = builder.field_setters;
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
use crate::{GenerateTealDefinition, Type};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct FuncArg {
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub ty: Type
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A struct that stores type information about a function
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct FunctionDesc {
|
||||||
|
/// The name of the function
|
||||||
|
pub name: String,
|
||||||
|
/// A boolean indicating if this is a part of userdata
|
||||||
|
pub is_field: bool,
|
||||||
|
/// The arguments of the function
|
||||||
|
pub args: Vec<FuncArg>,
|
||||||
|
/// The return type of the function, `None` if it returns nothing
|
||||||
|
pub return_ty: Option<Type>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GenerateTealDefinition for FunctionDesc {
|
||||||
|
fn generate(&self, ctx: &mut crate::GenerateContext) {
|
||||||
|
let args: String = self.args.iter()
|
||||||
|
.map(|t| match &t.name {
|
||||||
|
Some(name) => {
|
||||||
|
// include the argument name if one is set
|
||||||
|
format!("{}: {}", name, t.ty.name)
|
||||||
|
},
|
||||||
|
None => t.ty.name.clone()
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ");
|
||||||
|
|
||||||
|
let ret = match &self.return_ty {
|
||||||
|
Some(ty) => format!(": {}", ty.name),
|
||||||
|
None => String::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
let content = format!("function({args}){ret}");
|
||||||
|
if self.is_field {
|
||||||
|
ctx.write(&self.name)
|
||||||
|
.write(": ")
|
||||||
|
.write(&content)
|
||||||
|
.write_new_line();
|
||||||
|
} else {
|
||||||
|
ctx.write(&content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a Field on a Userdata.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct FieldDesc {
|
||||||
|
/// The name of the field
|
||||||
|
pub name: String,
|
||||||
|
/// The type of the field
|
||||||
|
pub ty: Type,
|
||||||
|
/// A boolean indicating if there is a setter for this field.
|
||||||
|
pub can_set: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GenerateTealDefinition for FieldDesc {
|
||||||
|
fn generate(&self, ctx: &mut crate::GenerateContext) {
|
||||||
|
ctx.write(&self.name)
|
||||||
|
.write(": ")
|
||||||
|
.writeln(&self.ty.name);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,197 @@
|
||||||
|
pub mod record;
|
||||||
|
pub use record::*;
|
||||||
|
|
||||||
|
pub mod fn_desc;
|
||||||
|
pub use fn_desc::*;
|
||||||
|
|
||||||
|
pub mod walker;
|
||||||
|
pub use walker::*;
|
||||||
|
|
||||||
|
pub mod userdata;
|
||||||
|
pub use userdata::*;
|
||||||
|
|
||||||
|
use crate::{Table, Value, Function, AnyUserdata, ValueVec};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct GenerateContext {
|
||||||
|
/// The indentation level of current context.
|
||||||
|
pub level: u32,
|
||||||
|
/// The current record that things are being generated for.
|
||||||
|
pub record: Option<Type>,
|
||||||
|
pub content: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GenerateContext {
|
||||||
|
/// Writes `s` to the end of the current generating content.
|
||||||
|
///
|
||||||
|
/// If this write is happening on a new line, it will be indented
|
||||||
|
pub fn write(&mut self, s: &str) -> &mut Self {
|
||||||
|
if self.content.ends_with("\n") {
|
||||||
|
self.write_indent();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.content = format!("{}{}", self.content, s);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes `s` directly to the end of the current generating content. There is no check for
|
||||||
|
/// indentation
|
||||||
|
pub fn write_raw(&mut self, s: &str) -> &mut Self {
|
||||||
|
if self.content.ends_with("\n") {
|
||||||
|
self.write_indent();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.content = format!("{}{}", self.content, s);
|
||||||
|
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes `s` followed by a new line directly to the end of the current generating content.
|
||||||
|
///
|
||||||
|
/// If this write is happening on a new line, it will be indented
|
||||||
|
pub fn writeln(&mut self, s: &str) -> &mut Self {
|
||||||
|
if self.content.ends_with("\n") {
|
||||||
|
self.write_indent();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.content = format!("{}{}\n", self.content, s);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes a new line to the generating content.
|
||||||
|
pub fn write_new_line(&mut self) -> &mut Self {
|
||||||
|
self.content = format!("{}\n", self.content);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes the indent to the generating content that is valid for this level.
|
||||||
|
pub fn write_indent(&mut self) -> &mut Self {
|
||||||
|
let level = self.level * 4; // 4 spaces per indentation
|
||||||
|
let indent = " ".repeat(level as _);
|
||||||
|
self.content = format!("{}{}", self.content, indent);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes `s` on its own line, will insert a new line if its *not already on its own line*.
|
||||||
|
pub fn write_on_ln(&mut self, s: &str) -> &mut Self {
|
||||||
|
if self.content.ends_with("\n") {
|
||||||
|
self.write(s);
|
||||||
|
} else {
|
||||||
|
self.write_new_line()
|
||||||
|
.write(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Increases the indentation level by 1.
|
||||||
|
pub fn inc_level(&mut self) -> &mut Self {
|
||||||
|
self.level += 1;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decreases the indentation level by 1.
|
||||||
|
pub fn dec_level(&mut self) -> &mut Self {
|
||||||
|
self.level -= 1;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait GenerateTealDefinition {
|
||||||
|
fn generate(&self, ctx: &mut GenerateContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait LuaTypeName {
|
||||||
|
fn get_lua_name() -> String;
|
||||||
|
}
|
||||||
|
|
||||||
|
// implements LuaTypeName for types that have the exact same name as the Rust type.
|
||||||
|
macro_rules! impl_type_name_simple {
|
||||||
|
($name: literal, $t: ident) => {
|
||||||
|
impl LuaTypeName for $t {
|
||||||
|
fn get_lua_name() -> String {
|
||||||
|
$name.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($name:literal, $t:ident <$( $generic: tt ),+>) => {
|
||||||
|
impl<$($generic: LuaTypeName,)+ > LuaTypeName for $t <$( $generic ),+ >{
|
||||||
|
fn get_lua_name() -> String {
|
||||||
|
let generic_name = {
|
||||||
|
let v = vec![$( $generic::get_lua_name(), )+];
|
||||||
|
v.join(", ")
|
||||||
|
};
|
||||||
|
format!("{}<{}>", $name, generic_name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($lua_name: literal, $name:tt, $($lts:lifetime),+) => {
|
||||||
|
impl<$($lts,)+ > LuaTypeName for $name <$( $lts ),+ >{
|
||||||
|
fn get_lua_name() -> String {
|
||||||
|
$lua_name.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_type_name_simple!("Boolean", bool);
|
||||||
|
impl_type_name_simple!("Number", f32);
|
||||||
|
impl_type_name_simple!("Number", f64);
|
||||||
|
impl_type_name_simple!("Number", u8);
|
||||||
|
impl_type_name_simple!("Number", u16);
|
||||||
|
impl_type_name_simple!("Number", u32);
|
||||||
|
impl_type_name_simple!("Number", u64);
|
||||||
|
impl_type_name_simple!("Number", usize);
|
||||||
|
|
||||||
|
impl_type_name_simple!("Number", i8);
|
||||||
|
impl_type_name_simple!("Number", i16);
|
||||||
|
impl_type_name_simple!("Number", i32);
|
||||||
|
impl_type_name_simple!("Number", i64);
|
||||||
|
impl_type_name_simple!("Number", isize);
|
||||||
|
|
||||||
|
impl_type_name_simple!("string", String);
|
||||||
|
|
||||||
|
impl_type_name_simple!("T | nil", Option<T>);
|
||||||
|
|
||||||
|
impl_type_name_simple!("T", Table, 'a);
|
||||||
|
impl_type_name_simple!("any", Value, 'a);
|
||||||
|
impl_type_name_simple!("any", ValueVec, 'a);
|
||||||
|
impl_type_name_simple!("function(any)", Function, 'a);
|
||||||
|
impl_type_name_simple!("T", AnyUserdata, 'a);
|
||||||
|
|
||||||
|
impl LuaTypeName for () {
|
||||||
|
fn get_lua_name() -> String {
|
||||||
|
"()".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tuple used to implement LuaTypeName for tuples that are used in function arguments
|
||||||
|
macro_rules! impl_type_name_tuples {
|
||||||
|
( $count: expr, $first: tt, $( $name: tt ),+ ) => {
|
||||||
|
impl<$first: LuaTypeName, $($name: LuaTypeName,)+> LuaTypeName for ($first, $($name),+) {
|
||||||
|
fn get_lua_name() -> String {
|
||||||
|
let tuple_names = {
|
||||||
|
let v = vec![$first::get_lua_name(), $( $name::get_lua_name(), )+];
|
||||||
|
v.join(", ")
|
||||||
|
};
|
||||||
|
format!("({})", tuple_names)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_type_name_tuples!(count - 1, $( $name ),+);
|
||||||
|
};
|
||||||
|
( $count: expr, $last: tt ) => {
|
||||||
|
impl<$last: LuaTypeName> LuaTypeName for ($last,) {
|
||||||
|
fn get_lua_name() -> String {
|
||||||
|
$last::get_lua_name()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_type_name_tuples! { 16, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16 }
|
|
@ -0,0 +1,60 @@
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
|
use crate::{FieldDesc, FunctionDesc, GenerateTealDefinition, LuaTypeName};
|
||||||
|
|
||||||
|
pub struct Record {
|
||||||
|
pub(crate) ty: Type,
|
||||||
|
pub fields: Vec<FieldDesc>,
|
||||||
|
pub functions: Vec<FunctionDesc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Record {
|
||||||
|
type Target = Type;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.ty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for Record {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.ty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GenerateTealDefinition for Record {
|
||||||
|
fn generate(&self, ctx: &mut crate::GenerateContext) {
|
||||||
|
ctx.record = Some(self.ty.clone());
|
||||||
|
ctx.write("record ")
|
||||||
|
.writeln(&self.ty.name)
|
||||||
|
.inc_level();
|
||||||
|
|
||||||
|
for field in self.fields.iter() {
|
||||||
|
field.generate(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.write_new_line();
|
||||||
|
|
||||||
|
for func in self.functions.iter() {
|
||||||
|
func.generate(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.dec_level()
|
||||||
|
.write_on_ln("end");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A struct that only stores only the type of a Record; so the name, and generics.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Type {
|
||||||
|
/// The name of the type, including generics, i.e., `LuaBuffer<T>`
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Type {
|
||||||
|
pub fn new<T: LuaTypeName>() -> Self {
|
||||||
|
Self {
|
||||||
|
name: T::get_lua_name(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,360 @@
|
||||||
|
use std::{collections::HashMap, mem, ops::{Deref, DerefMut}};
|
||||||
|
|
||||||
|
use crate::{AsLua, FieldDesc, FromLua, FromLuaVec, FuncArg, FunctionDesc, LuaTypeName, State, Type, Userdata, UserdataBuilder};
|
||||||
|
|
||||||
|
enum MutCow<'a, T> {
|
||||||
|
Borrowed(&'a mut T),
|
||||||
|
Owned(T),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Deref for MutCow<'a, T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
match self {
|
||||||
|
MutCow::Borrowed(v) => v,
|
||||||
|
MutCow::Owned(v) => &v,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> DerefMut for MutCow<'a, T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
match self {
|
||||||
|
MutCow::Borrowed(v) =>v,
|
||||||
|
MutCow::Owned(v) => v,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait TealUserdata: Sized {
|
||||||
|
fn name() -> String;
|
||||||
|
|
||||||
|
fn build<'a>(builder: &mut TealUserdataBuilder<'a, Self>);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TealUserdataBuilder<'a, T>
|
||||||
|
where
|
||||||
|
// Userdata is implemented for all types that implement TealUserdata with the
|
||||||
|
// generic impl below.
|
||||||
|
T: Userdata
|
||||||
|
{
|
||||||
|
inner: MutCow<'a, UserdataBuilder<'a, T>>,
|
||||||
|
pub(crate) fields: HashMap<String, FieldDesc>,
|
||||||
|
pub(crate) funcs: HashMap<String, FunctionDesc>,
|
||||||
|
func_arg_names: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: Userdata> From<&'a mut UserdataBuilder<'a, T>> for TealUserdataBuilder<'a, T> {
|
||||||
|
fn from(value: &'a mut UserdataBuilder<'a, T>) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: MutCow::Borrowed(value),
|
||||||
|
funcs: Default::default(),
|
||||||
|
fields: Default::default(),
|
||||||
|
func_arg_names: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn function_desc<'a, A, R>(func_arg_names: &mut Option<Vec<String>>, fn_name: &str) -> FunctionDesc
|
||||||
|
where
|
||||||
|
A: FromLuaVec<'a> + LuaTypeName,
|
||||||
|
R: AsLua<'a> + LuaTypeName,
|
||||||
|
{
|
||||||
|
let arg_ty = A::get_lua_name();
|
||||||
|
let args: Vec<FuncArg> = if A::value_num().unwrap_or(0) > 1 {
|
||||||
|
// if the Args are multiple types, the type name was created as a tuple.
|
||||||
|
// to get each type of them, the surrounding parenthesis are removed,
|
||||||
|
// and then the string is split by the commas.
|
||||||
|
let args_split = arg_ty[1..arg_ty.len()-1]
|
||||||
|
.split(",");
|
||||||
|
|
||||||
|
if let Some(names) = func_arg_names.take() {
|
||||||
|
let args_num = A::value_num().unwrap_or(0);
|
||||||
|
debug_assert_eq!(names.len(), args_num,
|
||||||
|
"Invalid number of argument names was provided! (Expected {}, received {})",
|
||||||
|
args_num, names.len());
|
||||||
|
|
||||||
|
args_split.zip(names.into_iter())
|
||||||
|
.map(|(ty, name)| FuncArg {
|
||||||
|
name: Some(name.to_string()),
|
||||||
|
ty: Type {
|
||||||
|
name: ty.trim().to_string()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
args_split
|
||||||
|
.map(|s| FuncArg { name: None, ty: Type { name: s.trim().to_string() } })
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if &arg_ty == "()" {
|
||||||
|
todo!("Dont insert any arg");
|
||||||
|
} else {
|
||||||
|
if let Some(mut names) = func_arg_names.take() {
|
||||||
|
debug_assert_eq!(names.len(), 1,
|
||||||
|
"Invalid number of argument names was provided! (Expected {}, received {})",
|
||||||
|
1, names.len());
|
||||||
|
|
||||||
|
vec![
|
||||||
|
FuncArg {
|
||||||
|
name: names.pop(),
|
||||||
|
ty: Type {
|
||||||
|
name: arg_ty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
vec![
|
||||||
|
FuncArg {
|
||||||
|
name: None,
|
||||||
|
ty: Type {
|
||||||
|
name: arg_ty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let ret_name = R::get_lua_name();
|
||||||
|
let ret_ty = match ret_name.as_str() {
|
||||||
|
"()" => None,
|
||||||
|
_ => Some(Type { name: ret_name })
|
||||||
|
};
|
||||||
|
|
||||||
|
let func_desc = FunctionDesc {
|
||||||
|
name: fn_name.to_string(),
|
||||||
|
is_field: true,
|
||||||
|
args,
|
||||||
|
return_ty: ret_ty,
|
||||||
|
};
|
||||||
|
|
||||||
|
func_desc
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: Userdata> TealUserdataBuilder<'a, T> {
|
||||||
|
pub(crate) fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
inner: MutCow::Owned(UserdataBuilder::new()),
|
||||||
|
fields: Default::default(),
|
||||||
|
funcs: Default::default(),
|
||||||
|
func_arg_names: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn field_getter<F, R>(&mut self, name: &str, f: F) -> &mut Self
|
||||||
|
where
|
||||||
|
F: Fn(&'a State, &T) -> crate::Result<R> + 'static,
|
||||||
|
R: AsLua<'a> + LuaTypeName,
|
||||||
|
T: TealUserdata + 'static
|
||||||
|
{
|
||||||
|
let tname = R::get_lua_name();
|
||||||
|
// maybe field_setter was ran first
|
||||||
|
if !self.fields.contains_key(&tname) {
|
||||||
|
let desc = FieldDesc {
|
||||||
|
name: name.to_string(),
|
||||||
|
ty: Type {
|
||||||
|
name: tname.clone(),
|
||||||
|
},
|
||||||
|
can_set: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.fields.insert(tname, desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.inner.field_getter(name, f);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn field_setter<F, V>(&mut self, name: &str, f: F) -> &mut Self
|
||||||
|
where
|
||||||
|
F: Fn(&'a State, &mut T, V) -> crate::Result<()> + 'static,
|
||||||
|
V: FromLua<'a> + LuaTypeName,
|
||||||
|
T: TealUserdata + 'static
|
||||||
|
{
|
||||||
|
let tname = V::get_lua_name();
|
||||||
|
if let Some(field) = self.fields.get_mut(&tname) {
|
||||||
|
field.can_set = true;
|
||||||
|
} else {
|
||||||
|
let desc = FieldDesc {
|
||||||
|
name: name.to_string(),
|
||||||
|
ty: Type {
|
||||||
|
name: tname.clone(),
|
||||||
|
},
|
||||||
|
can_set: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.fields.insert(tname, desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.inner.field_setter(name, f);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn function<F, R, A>(&mut self, name: &str, f: F) -> &mut Self
|
||||||
|
where
|
||||||
|
F: Fn(&'a State, A) -> crate::Result<R> + 'static,
|
||||||
|
A: FromLuaVec<'a> + LuaTypeName,
|
||||||
|
R: AsLua<'a> + LuaTypeName,
|
||||||
|
{
|
||||||
|
let func_desc = function_desc::<A, R>(&mut self.func_arg_names, name);
|
||||||
|
self.funcs.insert(name.to_string(), func_desc);
|
||||||
|
|
||||||
|
self.inner.function(name, f);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn method<F, R, A>(&mut self, name: &str, f: F) -> &mut Self
|
||||||
|
where
|
||||||
|
F: Fn(&'a State, &T, A) -> crate::Result<R> + 'static,
|
||||||
|
A: FromLuaVec<'a> + LuaTypeName,
|
||||||
|
R: AsLua<'a> + LuaTypeName,
|
||||||
|
T: TealUserdata + 'static
|
||||||
|
{
|
||||||
|
let mut func_desc = function_desc::<A, R>(&mut self.func_arg_names, name);
|
||||||
|
let mut tmp = vec![FuncArg {
|
||||||
|
name: Some("self".to_string()),
|
||||||
|
ty: Type {
|
||||||
|
name: T::get_lua_name()
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
// this is probably the fastest way to prepend to a Vec
|
||||||
|
tmp.append(&mut func_desc.args);
|
||||||
|
func_desc.args = tmp;
|
||||||
|
self.funcs.insert(name.to_string(), func_desc);
|
||||||
|
|
||||||
|
self.inner.method(name, f);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn method_mut<F, R, A>(&mut self, name: &str, f: F) -> &mut Self
|
||||||
|
where
|
||||||
|
F: Fn(&'a State, &mut T, A) -> crate::Result<R> + 'static,
|
||||||
|
A: FromLuaVec<'a> + LuaTypeName,
|
||||||
|
R: AsLua<'a> + LuaTypeName,
|
||||||
|
T: TealUserdata + 'static
|
||||||
|
{
|
||||||
|
let mut func_desc = function_desc::<A, R>(&mut self.func_arg_names, name);
|
||||||
|
let mut tmp = vec![FuncArg {
|
||||||
|
name: Some("self".to_string()),
|
||||||
|
ty: Type {
|
||||||
|
name: T::get_lua_name()
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
// this is probably the fastest way to prepend to a Vec
|
||||||
|
tmp.append(&mut func_desc.args);
|
||||||
|
func_desc.args = tmp;
|
||||||
|
self.funcs.insert(name.to_string(), func_desc);
|
||||||
|
|
||||||
|
self.inner.method_mut(name, f);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn meta_method<N, F, R, A>(&mut self, name: N, f: F) -> &mut Self
|
||||||
|
where
|
||||||
|
N: AsRef<str>,
|
||||||
|
F: Fn(&'a State, &T, A) -> crate::Result<R> + 'static,
|
||||||
|
A: FromLuaVec<'a> + LuaTypeName,
|
||||||
|
R: AsLua<'a> + LuaTypeName,
|
||||||
|
T: TealUserdata + 'static
|
||||||
|
{
|
||||||
|
let mut func_desc = function_desc::<A, R>(&mut self.func_arg_names, name.as_ref());
|
||||||
|
let mut tmp = vec![FuncArg {
|
||||||
|
name: Some("self".to_string()),
|
||||||
|
ty: Type {
|
||||||
|
name: T::get_lua_name()
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
// this is probably the fastest way to prepend to a Vec
|
||||||
|
tmp.append(&mut func_desc.args);
|
||||||
|
func_desc.args = tmp;
|
||||||
|
self.funcs.insert(name.as_ref().to_string(), func_desc);
|
||||||
|
|
||||||
|
self.inner.meta_method(name, f);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_arg_names(&mut self, names: &[&str]) -> &mut Self {
|
||||||
|
debug_assert!(self.func_arg_names.is_none(),
|
||||||
|
"Invalid attempt to set argument names for function. \
|
||||||
|
There are arguments that are currently pending to be set on a function");
|
||||||
|
|
||||||
|
self.func_arg_names = Some(names.iter().map(|n| n.to_string()).collect());
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: TealUserdata> Userdata for T {
|
||||||
|
fn name() -> String {
|
||||||
|
<T as TealUserdata>::name()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build<'a>(builder: &mut UserdataBuilder<'a, Self>) {
|
||||||
|
let builder = unsafe { mem::transmute::<_, &mut UserdataBuilder<'_, Self>>(builder) };
|
||||||
|
let mut teal_build = TealUserdataBuilder::from(builder);
|
||||||
|
<T as TealUserdata>::build(&mut teal_build);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: TealUserdata> LuaTypeName for T {
|
||||||
|
fn get_lua_name() -> String {
|
||||||
|
T::name()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) mod tests {
|
||||||
|
use crate::TealUserdata;
|
||||||
|
|
||||||
|
pub(crate) struct Vec3 {
|
||||||
|
x: f32,
|
||||||
|
y: f32,
|
||||||
|
z: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TealUserdata for Vec3 {
|
||||||
|
fn name() -> String {
|
||||||
|
"Vec3".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build<'a, 'b>(builder: &'b mut crate::TealUserdataBuilder<'a, Self>) {
|
||||||
|
builder
|
||||||
|
.field_getter("x", |_, this| Ok(this.x))
|
||||||
|
.field_setter("x", |_, this, v| {
|
||||||
|
this.x = v;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.field_getter("y", |_, this| Ok(this.y))
|
||||||
|
.field_setter("y", |_, this, v| {
|
||||||
|
this.y = v;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.field_getter("z", |_, this| Ok(this.z))
|
||||||
|
.field_setter("z", |_, this, v| {
|
||||||
|
this.z = v;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.set_arg_names(&["x", "y", "z"])
|
||||||
|
.function("new", |_, (x, y, z)| {
|
||||||
|
Ok(Vec3 {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
z
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.set_arg_names(&["scalar"])
|
||||||
|
.method("do_something", |_, this, scalar: f32| {
|
||||||
|
Ok(this.x * scalar)
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simple_teal_ud() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
use crate::{function_desc, AsLua, FieldDesc, FromLuaVec, FunctionDesc, GenerateContext, GenerateTealDefinition, LuaTypeName, Record, TealUserdata, TealUserdataBuilder, Type};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct TypeWalker {
|
||||||
|
records: Vec<Record>,
|
||||||
|
global_fields: Vec<FieldDesc>,
|
||||||
|
global_funcs: Vec<FunctionDesc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypeWalker {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn walk_type<T: TealUserdata>(&mut self) {
|
||||||
|
let mut ud = TealUserdataBuilder::<T>::new();
|
||||||
|
T::build(&mut ud);
|
||||||
|
|
||||||
|
let fields = ud.fields.values().cloned().collect();
|
||||||
|
let functions = ud.funcs.values().cloned().collect();
|
||||||
|
|
||||||
|
let record = Record {
|
||||||
|
ty: Type { name: T::get_lua_name() },
|
||||||
|
fields,
|
||||||
|
functions,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.records.push(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_global_field<T: LuaTypeName>(&mut self, name: &str) {
|
||||||
|
let field = FieldDesc {
|
||||||
|
name: name.to_string(),
|
||||||
|
ty: Type::new::<T>(),
|
||||||
|
can_set: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.global_fields.push(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_global_func<'a, Args, Ret>(&mut self, name: &str, arg_names: &[&str])
|
||||||
|
where
|
||||||
|
Args: FromLuaVec<'a> + LuaTypeName,
|
||||||
|
Ret: AsLua<'a> + LuaTypeName
|
||||||
|
{
|
||||||
|
let arg_names: Vec<_> = arg_names.iter().map(|s| s.to_string()).collect();
|
||||||
|
let mut desc = function_desc::<Args, Ret>(&mut Some(arg_names), name);
|
||||||
|
desc.is_field = true;
|
||||||
|
|
||||||
|
self.global_funcs.push(desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates the content of a `.d.tl` file.
|
||||||
|
pub fn to_type_def(&self) -> String {
|
||||||
|
let mut gen_ctx = GenerateContext::default();
|
||||||
|
|
||||||
|
for field in self.global_fields.iter() {
|
||||||
|
field.generate(&mut gen_ctx);
|
||||||
|
}
|
||||||
|
if !self.global_fields.is_empty() {
|
||||||
|
gen_ctx.write_new_line();
|
||||||
|
}
|
||||||
|
|
||||||
|
for funcs in self.global_funcs.iter() {
|
||||||
|
funcs.generate(&mut gen_ctx);
|
||||||
|
}
|
||||||
|
if !self.global_funcs.is_empty() {
|
||||||
|
gen_ctx.write_new_line();
|
||||||
|
}
|
||||||
|
|
||||||
|
for record in self.records.iter() {
|
||||||
|
record.generate(&mut gen_ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
gen_ctx.content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::TypeWalker;
|
||||||
|
use crate::teal::userdata::tests::Vec3;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vec3_type_walk() {
|
||||||
|
let mut walker = TypeWalker::new();
|
||||||
|
walker.walk_type::<Vec3>();
|
||||||
|
walker.add_global_field::<f64>("multiplier");
|
||||||
|
walker.add_global_func::<(f32, f64, usize), i32>("do_cool_math", &["a", "b", "z"]);
|
||||||
|
|
||||||
|
println!("{}", walker.to_type_def());
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,7 @@ pub struct Vec2 {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Userdata for Vec2 {
|
impl Userdata for Vec2 {
|
||||||
fn build<'a>(_state: &State, builder: &mut UserdataBuilder<'a, Vec2>) -> crate::Result<()> {
|
fn build<'a>(builder: &mut UserdataBuilder<'a, Vec2>) {
|
||||||
builder
|
builder
|
||||||
.field_getter("x", |_, this| Ok(this.x))
|
.field_getter("x", |_, this| Ok(this.x))
|
||||||
.field_getter("y", |_, this| Ok(this.y))
|
.field_getter("y", |_, this| Ok(this.y))
|
||||||
|
@ -58,8 +58,6 @@ impl Userdata for Vec2 {
|
||||||
y: ly + ry,
|
y: ly + ry,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn name() -> String {
|
fn name() -> String {
|
||||||
|
|
|
@ -47,9 +47,9 @@ impl<'a, T: Userdata> From<Ref<'a, T>> for UserdataRef<'static, T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T: Userdata + 'static> Userdata for UserdataRef<'a, T> {
|
impl<'a, T: Userdata + 'static> Userdata for UserdataRef<'a, T> {
|
||||||
fn build<'b>(state: &State, builder: &mut UserdataBuilder<'b, Self>) -> crate::Result<()> {
|
fn build<'b>(builder: &mut UserdataBuilder<'b, Self>) {
|
||||||
let mut other = UserdataBuilder::<T>::new();
|
let mut other = UserdataBuilder::<T>::new();
|
||||||
T::build(state, &mut other)?;
|
T::build(&mut other);
|
||||||
|
|
||||||
builder.expand_with(other);
|
builder.expand_with(other);
|
||||||
|
|
||||||
|
@ -75,8 +75,6 @@ impl<'a, T: Userdata + 'static> Userdata for UserdataRef<'a, T> {
|
||||||
if builder.wrapped_getter_mut.set(Box::new(mut_getter)).is_err() {
|
if builder.wrapped_getter_mut.set(Box::new(mut_getter)).is_err() {
|
||||||
panic!("Somehow the wrapped mutable getter has already been set");
|
panic!("Somehow the wrapped mutable getter has already been set");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn name() -> String {
|
fn name() -> String {
|
||||||
|
|
|
@ -56,9 +56,9 @@ impl<'a, T: Userdata> From<RefMut<'a, T>> for UserdataRefMut<'static, T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T: Userdata + 'static> Userdata for UserdataRefMut<'a, T> {
|
impl<'a, T: Userdata + 'static> Userdata for UserdataRefMut<'a, T> {
|
||||||
fn build<'b>(state: &State, builder: &mut UserdataBuilder<'b, Self>) -> crate::Result<()> {
|
fn build<'b>(builder: &mut UserdataBuilder<'b, Self>) {
|
||||||
let mut other = UserdataBuilder::<T>::new();
|
let mut other = UserdataBuilder::<T>::new();
|
||||||
T::build(state, &mut other)?;
|
T::build(&mut other);
|
||||||
|
|
||||||
builder.expand_with(other);
|
builder.expand_with(other);
|
||||||
|
|
||||||
|
@ -91,8 +91,6 @@ impl<'a, T: Userdata + 'static> Userdata for UserdataRefMut<'a, T> {
|
||||||
if builder.wrapped_getter_mut.set(Box::new(getter_mut)).is_err() {
|
if builder.wrapped_getter_mut.set(Box::new(getter_mut)).is_err() {
|
||||||
panic!("Somehow the wrapped mutable getter has already been set");
|
panic!("Somehow the wrapped mutable getter has already been set");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn name() -> String {
|
fn name() -> String {
|
||||||
|
|
|
@ -107,7 +107,7 @@ impl<'a> PushToLuaStack<'a> for MetaMethod {
|
||||||
pub trait Userdata: Sized {
|
pub trait Userdata: Sized {
|
||||||
fn name() -> String;
|
fn name() -> String;
|
||||||
|
|
||||||
fn build<'a>(state: &State, builder: &mut UserdataBuilder<'a, Self>) -> crate::Result<()>;
|
fn build<'a>(builder: &mut UserdataBuilder<'a, Self>);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T: Userdata + 'static> AsLua<'a> for T {
|
impl<'a, T: Userdata + 'static> AsLua<'a> for T {
|
||||||
|
|
|
@ -11,14 +11,12 @@ impl<T: Userdata> UserdataProxy<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Userdata> Userdata for UserdataProxy<T> {
|
impl<T: Userdata> Userdata for UserdataProxy<T> {
|
||||||
fn build<'a>(state: &State, builder: &mut UserdataBuilder<'a, Self>) -> crate::Result<()> {
|
fn build<'a>(builder: &mut UserdataBuilder<'a, Self>){
|
||||||
let mut other = UserdataBuilder::<T>::new();
|
let mut other = UserdataBuilder::<T>::new();
|
||||||
T::build(state, &mut other)?;
|
T::build(&mut other);
|
||||||
|
|
||||||
// only the functions need to be added since they're the only thing usable from a proxy
|
// only the functions need to be added since they're the only thing usable from a proxy
|
||||||
builder.functions = other.functions;
|
builder.functions = other.functions;
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn name() -> String {
|
fn name() -> String {
|
||||||
|
|
Loading…
Reference in New Issue