Skip to content

Commit

Permalink
Cast from Gd<T> to a script
Browse files Browse the repository at this point in the history
  • Loading branch information
TitanNano committed Aug 14, 2024
1 parent 6b38c9e commit d917942
Show file tree
Hide file tree
Showing 11 changed files with 329 additions and 10 deletions.
29 changes: 25 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ proc-macro2 = "1.0.68"
quote = "1.0.33"
syn = "2.0.38"
const-str = "0.5.6"
thiserror = "1"

godot-rust-script-derive = { path = "derive" }
tests-scripts-lib = { path = "tests-scripts-lib" }
161 changes: 159 additions & 2 deletions derive/src/impl_attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@

use proc_macro2::TokenStream;
use quote::{quote, quote_spanned, ToTokens};
use syn::{parse_macro_input, spanned::Spanned, FnArg, ImplItem, ItemImpl, ReturnType, Type};
use syn::{
parse2, parse_macro_input, spanned::Spanned, FnArg, Ident, ImplItem, ImplItemFn, ItemImpl,
PatIdent, PatType, ReturnType, Token, Type, Visibility,
};

use crate::{
is_context_type, rust_to_variant_type,
compile_error, is_context_type, rust_to_variant_type,
type_paths::{godot_types, property_hints, string_name_ty, variant_ty},
};

Expand Down Expand Up @@ -173,12 +176,166 @@ pub fn godot_script_impl(
);
};

let pub_interface = generate_public_interface(&body);

quote! {
#body

#trait_impl

#pub_interface

#metadata
}
.into()
}

fn extract_script_name_from_type(impl_target: &syn::Type) -> Result<Ident, TokenStream> {
match impl_target {
Type::Array(_) => Err(compile_error("Arrays are not supported!", impl_target)),
Type::BareFn(_) => Err(compile_error(
"Bare functions are not supported!",
impl_target,
)),
Type::Group(_) => Err(compile_error("Groups are not supported!", impl_target)),
Type::ImplTrait(_) => Err(compile_error("Impl traits are not suppored!", impl_target)),
Type::Infer(_) => Err(compile_error("Infer is not supported!", impl_target)),
Type::Macro(_) => Err(compile_error("Macro types are not supported!", impl_target)),
Type::Never(_) => Err(compile_error("Never type is not supported!", impl_target)),
Type::Paren(_) => Err(compile_error("Unsupported type!", impl_target)),
Type::Path(ref path) => Ok(path.path.segments.last().unwrap().ident.clone()),
Type::Ptr(_) => Err(compile_error(
"Pointer types are not supported!",
impl_target,
)),
Type::Reference(_) => Err(compile_error("References are not supported!", impl_target)),
Type::Slice(_) => Err(compile_error("Slices are not supported!", impl_target)),
Type::TraitObject(_) => Err(compile_error(
"Trait objects are not supported!",
impl_target,
)),
Type::Tuple(_) => Err(compile_error("Tuples are not supported!", impl_target)),
Type::Verbatim(_) => Err(compile_error("Verbatim is not supported!", impl_target)),
_ => Err(compile_error("Unsupported type!", impl_target)),
}
}

fn sanitize_trait_fn_arg(arg: FnArg) -> FnArg {
match arg {
FnArg::Receiver(mut rec) => {
rec.mutability = Some(Token![mut](rec.span()));
rec.ty = parse2(quote!(&mut Self)).unwrap();

FnArg::Receiver(rec)
}
FnArg::Typed(ty) => FnArg::Typed(PatType {
attrs: ty.attrs,
pat: match *ty.pat {
syn::Pat::Const(_)
| syn::Pat::Lit(_)
| syn::Pat::Macro(_)
| syn::Pat::Or(_)
| syn::Pat::Paren(_)
| syn::Pat::Path(_)
| syn::Pat::Range(_)
| syn::Pat::Reference(_)
| syn::Pat::Rest(_)
| syn::Pat::Slice(_)
| syn::Pat::Struct(_)
| syn::Pat::Tuple(_)
| syn::Pat::TupleStruct(_)
| syn::Pat::Type(_)
| syn::Pat::Verbatim(_)
| syn::Pat::Wild(_) => ty.pat,
syn::Pat::Ident(ident_pat) => Box::new(syn::Pat::Ident(PatIdent {
attrs: ident_pat.attrs,
by_ref: None,
mutability: None,
ident: ident_pat.ident,
subpat: None,
})),
_ => ty.pat,
},
colon_token: ty.colon_token,
ty: ty.ty,
}),
}
}

fn generate_public_interface(impl_body: &ItemImpl) -> TokenStream {
let impl_target = impl_body.self_ty.as_ref();
let script_name = match extract_script_name_from_type(impl_target) {
Ok(target) => target,
Err(err) => return err,
};

let trait_name = Ident::new(&format!("I{}", script_name), script_name.span());

let functions: Vec<_> = impl_body
.items
.iter()
.filter_map(|func| match func {
ImplItem::Fn(func @ ImplItemFn{ vis: Visibility::Public(_), .. }) => Some(func),
_ => None,
})
.map(|func| {
let mut sig = func.sig.clone();

sig.inputs = sig
.inputs
.into_iter()
.filter(|arg| {
!matches!(arg, FnArg::Typed(PatType { attrs: _, pat: _, colon_token: _, ty }) if matches!(ty.as_ref(), Type::Path(path) if path.path.segments.last().unwrap().ident == "Context"))
})
.map(sanitize_trait_fn_arg)
.collect();
sig
})
.collect();

let function_defs: TokenStream = functions
.iter()
.map(|func| quote_spanned! { func.span() => #func; })
.collect();
let function_impls: TokenStream = functions
.iter()
.map(|func| {
let func_name = func.ident.to_string();
let args: TokenStream = func
.inputs
.iter()
.filter_map(|arg| match arg {
FnArg::Receiver(_) => None,
FnArg::Typed(arg) => Some(arg),
})
.map(|arg| {
let pat = arg.pat.clone();

quote_spanned! { pat.span() =>
::godot::meta::ToGodot::to_variant(&#pat),
}
})
.collect();

quote_spanned! { func.span() =>
#func {
(*self).call(#func_name.into(), &[#args]).to()
}
}
})
.collect();

quote! {
#[automatically_derived]
#[allow(dead_code)]
pub trait #trait_name {
#function_defs
}

#[automatically_derived]
#[allow(dead_code)]
impl #trait_name for ::godot_rust_script::RsRef<#impl_target> {
#function_impls
}
}
}
7 changes: 7 additions & 0 deletions derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
.unwrap_or_else(|| quote!(::godot_rust_script::godot::prelude::RefCounted));

let script_type_ident = opts.ident;
let class_name = script_type_ident.to_string();
let fields = opts.data.take_struct().unwrap().fields;

let public_fields = fields.iter().filter(|field| {
Expand Down Expand Up @@ -129,6 +130,8 @@ pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let output = quote! {
impl ::godot_rust_script::GodotScript for #script_type_ident {
type Base = #base_class;

const CLASS_NAME: &'static str = #class_name;

#get_fields_impl

Expand Down Expand Up @@ -383,3 +386,7 @@ pub fn godot_script_impl(
) -> proc_macro::TokenStream {
impl_attribute::godot_script_impl(args, body)
}

fn compile_error(message: &str, tokens: impl ToTokens) -> TokenStream {
syn::Error::new_spanned(tokens, message).into_compile_error()
}
1 change: 1 addition & 0 deletions rust-script/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ rand = { workspace = true, optional = true }
godot-rust-script-derive = { workspace = true, optional = true }
once_cell = "1.19.0"
const-str.workspace = true
thiserror.workspace = true

[dev-dependencies]
tests-scripts-lib = { path = "../tests-scripts-lib" }
Expand Down
1 change: 1 addition & 0 deletions rust-script/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub use godot_rust_script_derive::{godot_script_impl, GodotScript};

pub mod private_export {
pub use super::shared::__godot_rust_plugin_SCRIPT_REGISTRY;
pub use crate::script_registry::RsRefOwner;
pub use const_str::{concat, replace, strip_prefix, unwrap};
pub use godot::sys::{plugin_add, plugin_registry};
}
Expand Down
6 changes: 3 additions & 3 deletions rust-script/src/library.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ use godot::{
sys::VariantType,
};

pub use crate::script_registry::{
CastToScript, GodotScript, GodotScriptImpl, RsRef, RustScriptMetaData, RustScriptMethodInfo,
};
use crate::script_registry::{
CreateScriptInstanceData, GodotScriptObject, RustScriptPropertyInfo, RustScriptSignalInfo,
};
pub use crate::script_registry::{
GodotScript, GodotScriptImpl, RustScriptMetaData, RustScriptMethodInfo,
};
pub use signals::{ScriptSignal, Signal, SignalArguments};

mod signals;
Expand Down
1 change: 1 addition & 0 deletions rust-script/src/runtime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ use crate::{

use self::rust_script_language::RustScriptLanguage;

pub(crate) use rust_script::RustScript;
pub use rust_script_instance::{Context, GenericContext};

#[macro_export]
Expand Down
2 changes: 1 addition & 1 deletion rust-script/src/runtime/rust_script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const NOTIFICATION_EXTENSION_RELOADED: i32 = 2;

#[derive(GodotClass)]
#[class(base = ScriptExtension, tool)]
pub(super) struct RustScript {
pub(crate) struct RustScript {
#[var(get = get_class_name, set = set_class_name, usage_flags = [STORAGE])]
class_name: GString,

Expand Down
Loading

0 comments on commit d917942

Please sign in to comment.