Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add #[rpc] attribute to user-defined functions #902

Merged
merged 2 commits into from
Sep 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions godot-core/src/docs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

use crate::meta::ClassName;
use crate::registry::plugin::PluginItem;
use crate::registry::plugin::{InherentImpl, PluginItem};
use std::collections::HashMap;

/// Created for documentation on
Expand Down Expand Up @@ -77,7 +77,7 @@ pub fn gather_xml_docs() -> impl Iterator<Item = String> {
let class_name = x.class_name;

match x.item {
PluginItem::InherentImpl { docs, .. } => {
PluginItem::InherentImpl(InherentImpl { docs, .. }) => {
map.entry(class_name).or_default().inherent = docs
}

Expand Down
5 changes: 5 additions & 0 deletions godot-core/src/meta/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,18 @@ mod godot_convert;
mod method_info;
mod property_info;
mod ref_arg;
// RpcConfig uses MultiplayerPeer::TransferMode and MultiplayerApi::RpcMode, which are only enabled in `codegen-full` feature.
#[cfg(feature = "codegen-full")]
mod rpc_config;
Comment on lines +44 to +45
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a brief comment that MultiplayerPeer::TransferMode and MultiplayerApi::RpcMode aren't part of the basic set of classes (also below).

mod sealed;
mod signature;
mod traits;

pub mod error;
pub use class_name::ClassName;
pub use godot_convert::{FromGodot, GodotConvert, ToGodot};
#[cfg(feature = "codegen-full")]
pub use rpc_config::RpcConfig;
pub use traits::{ArrayElement, GodotType, PackedArrayElement};

pub(crate) use crate::impl_godot_as_self;
Expand Down
52 changes: 52 additions & 0 deletions godot-core/src/meta/rpc_config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright (c) godot-rust; Bromeon and contributors.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

use crate::builtin::{Dictionary, StringName};
use crate::classes::multiplayer_api::RpcMode;
use crate::classes::multiplayer_peer::TransferMode;
use crate::classes::Node;
use crate::dict;
use crate::meta::ToGodot;

/// Configuration for a remote procedure call, typically used with `#[rpc(config = ...)]`.
///
/// See [Godot documentation](https://docs.godotengine.org/en/stable/tutorials/networking/high_level_multiplayer.html#remote-procedure-calls).
#[derive(Copy, Clone, Debug)]
pub struct RpcConfig {
pub rpc_mode: RpcMode,
pub transfer_mode: TransferMode,
pub call_local: bool,
pub channel: u32,
}

impl Default for RpcConfig {
fn default() -> Self {
Self {
rpc_mode: RpcMode::AUTHORITY,
transfer_mode: TransferMode::UNRELIABLE,
call_local: false,
channel: 0,
}
}
}

impl RpcConfig {
/// Register `method` as a remote procedure call on `node`.
pub fn configure_node(self, node: &mut Node, method_name: impl Into<StringName>) {
node.rpc_config(method_name.into(), &self.to_dictionary().to_variant());
}

/// Returns a [`Dictionary`] populated with the values required for a call to [`Node::rpc_config()`].
pub fn to_dictionary(&self) -> Dictionary {
dict! {
"rpc_mode": self.rpc_mode,
"transfer_mode": self.transfer_mode,
"call_local": self.call_local,
"channel": self.channel,
}
}
}
3 changes: 3 additions & 0 deletions godot-core/src/obj/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,7 @@ pub mod cap {
use super::*;
use crate::builtin::{StringName, Variant};
use crate::obj::{Base, Bounds, Gd};
use std::any::Any;

/// Trait for all classes that are default-constructible from the Godot engine.
///
Expand Down Expand Up @@ -558,6 +559,8 @@ pub mod cap {
fn __register_methods();
#[doc(hidden)]
fn __register_constants();
#[doc(hidden)]
fn __register_rpcs(_: &mut dyn Any) {}
}

pub trait ImplementsGodotExports: GodotClass {
Expand Down
21 changes: 19 additions & 2 deletions godot-core/src/private.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
pub use crate::gen::classes::class_macros;
pub use crate::obj::rtti::ObjectRtti;
pub use crate::registry::callbacks;
pub use crate::registry::plugin::{ClassPlugin, ErasedRegisterFn, PluginItem};
pub use crate::registry::plugin::{
ClassPlugin, ErasedRegisterFn, ErasedRegisterRpcsFn, InherentImpl, PluginItem,
};
pub use crate::storage::{as_storage, Storage};
pub use sys::out;

Expand All @@ -21,7 +23,6 @@ use crate::meta::CallContext;
use crate::sys;
use std::sync::{atomic, Arc, Mutex};
use sys::Global;

// ----------------------------------------------------------------------------------------------------------------------------------------------
// Global variables

Expand Down Expand Up @@ -128,6 +129,22 @@ pub(crate) fn iterate_plugins(mut visitor: impl FnMut(&ClassPlugin)) {
sys::plugin_foreach!(__GODOT_PLUGIN_REGISTRY; visitor);
}

#[cfg(feature = "codegen-full")] // Remove if used in other scenarios.
pub(crate) fn find_inherent_impl(class_name: crate::meta::ClassName) -> Option<InherentImpl> {
// We do this manually instead of using `iterate_plugins()` because we want to break as soon as we find a match.
let plugins = __godot_rust_plugin___GODOT_PLUGIN_REGISTRY.lock().unwrap();

plugins.iter().find_map(|elem| {
if elem.class_name == class_name {
if let PluginItem::InherentImpl(inherent_impl) = &elem.item {
return Some(inherent_impl.clone());
}
}

None
})
}

// ----------------------------------------------------------------------------------------------------------------------------------------------
// Traits and types

Expand Down
4 changes: 4 additions & 0 deletions godot-core/src/registry/callbacks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -354,3 +354,7 @@ pub fn register_user_methods_constants<T: cap::ImplementsGodotApi>(_class_builde
T::__register_methods();
T::__register_constants();
}

pub fn register_user_rpcs<T: cap::ImplementsGodotApi>(object: &mut dyn Any) {
T::__register_rpcs(object);
}
21 changes: 17 additions & 4 deletions godot-core/src/registry/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::meta::ClassName;
use crate::obj::{cap, GodotClass};
use crate::private::{ClassPlugin, PluginItem};
use crate::registry::callbacks;
use crate::registry::plugin::ErasedRegisterFn;
use crate::registry::plugin::{ErasedRegisterFn, InherentImpl};
use crate::{godot_error, sys};
use sys::{interface_fn, out, Global, GlobalGuard, GlobalLockError};

Expand Down Expand Up @@ -71,7 +71,7 @@ impl ClassRegistrationInfo {
// Note: when changing this match, make sure the array has sufficient size.
let index = match item {
PluginItem::Struct { .. } => 0,
PluginItem::InherentImpl { .. } => 1,
PluginItem::InherentImpl(_) => 1,
PluginItem::ITraitImpl { .. } => 2,
};

Expand Down Expand Up @@ -200,6 +200,18 @@ pub fn unregister_classes(init_level: InitLevel) {
}
}

#[cfg(feature = "codegen-full")]
pub fn auto_register_rpcs<T: GodotClass>(object: &mut T) {
// Find the element that matches our class, and call the closure if it exists.
if let Some(InherentImpl {
register_rpcs_fn: Some(closure),
..
}) = crate::private::find_inherent_impl(T::class_name())
{
(closure.raw)(object);
}
}

fn global_loaded_classes() -> GlobalGuard<'static, HashMap<InitLevel, Vec<LoadedClass>>> {
match LOADED_CLASSES.try_lock() {
Ok(it) => it,
Expand Down Expand Up @@ -281,11 +293,12 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) {
}
}

PluginItem::InherentImpl {
PluginItem::InherentImpl(InherentImpl {
register_methods_constants_fn,
register_rpcs_fn: _,
#[cfg(all(since_api = "4.3", feature = "docs"))]
docs: _,
} => {
}) => {
c.register_methods_constants_fn = Some(register_methods_constants_fn);
}

Expand Down
35 changes: 26 additions & 9 deletions godot-core/src/registry/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ use crate::meta::ClassName;
use crate::sys;
use std::any::Any;
use std::fmt;

// TODO(bromeon): some information coming from the proc-macro API is deferred through PluginItem, while others is directly
// translated to code. Consider moving more code to the PluginItem, which allows for more dynamic registration and will
// be easier for a future builder API.
Expand Down Expand Up @@ -45,6 +44,31 @@ impl fmt::Debug for ErasedRegisterFn {
}
}

#[derive(Copy, Clone)]
pub struct ErasedRegisterRpcsFn {
pub raw: fn(&mut dyn Any),
}

impl fmt::Debug for ErasedRegisterRpcsFn {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "0x{:0>16x}", self.raw as usize)
}
}

#[derive(Clone, Debug)]
pub struct InherentImpl {
/// Callback to library-generated function which registers functions and constants in the `impl` block.
///
/// Always present since that's the entire point of this `impl` block.
pub register_methods_constants_fn: ErasedRegisterFn,
/// Callback to library-generated function which calls [`Node::rpc_config`](crate::classes::Node::rpc_config) for each function annotated with `#[rpc]` on the `impl` block.
///
/// This function is called in [`UserClass::__before_ready()`](crate::obj::UserClass::__before_ready) definitions generated by the `#[derive(GodotClass)]` macro.
pub register_rpcs_fn: Option<ErasedRegisterRpcsFn>,
#[cfg(all(since_api = "4.3", feature = "docs"))]
pub docs: InherentImplDocs,
}

/// Represents the data part of a [`ClassPlugin`] instance.
///
/// Each enumerator represents a different item in Rust code, which is processed by an independent proc macro (for example,
Expand Down Expand Up @@ -102,14 +126,7 @@ pub enum PluginItem {
},

/// Collected from `#[godot_api] impl MyClass`.
InherentImpl {
/// Callback to library-generated function which registers functions and constants in the `impl` block.
///
/// Always present since that's the entire point of this `impl` block.
register_methods_constants_fn: ErasedRegisterFn,
#[cfg(all(since_api = "4.3", feature = "docs"))]
docs: InherentImplDocs,
},
InherentImpl(InherentImpl),

/// Collected from `#[godot_api] impl I... for MyClass`.
ITraitImpl {
Expand Down
3 changes: 2 additions & 1 deletion godot-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ homepage = "https://godot-rust.github.io"
[features]
api-custom = ["godot-bindings/api-custom"]
docs = ["dep:markdown"]
codegen-full = ["godot/__codegen-full"]

[lib]
proc-macro = true
Expand All @@ -31,7 +32,7 @@ godot-bindings = { path = "../godot-bindings", version = "=0.1.3" } # emit_godot

# Reverse dev dependencies so doctests can use `godot::` prefix.
[dev-dependencies]
godot = { path = "../godot", default-features = false }
godot = { path = "../godot", default-features = false}

# https://docs.rs/about/metadata
[package.metadata.docs.rs]
Expand Down
1 change: 1 addition & 0 deletions godot-macros/src/class/data_models/field_var.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ impl GetterSetterImpl {
external_attributes: Vec::new(),
rename: None,
is_script_virtual: false,
rpc_info: None,
},
);

Expand Down
3 changes: 3 additions & 0 deletions godot-macros/src/class/data_models/func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

use crate::class::RpcAttr;
use crate::util::{bail_fn, ident, safe_ident};
use crate::{util, ParseResult};
use proc_macro2::{Group, Ident, TokenStream, TokenTree};
Expand All @@ -19,6 +20,8 @@ pub struct FuncDefinition {
/// The name the function will be exposed as in Godot. If `None`, the Rust function name is used.
pub rename: Option<String>,
pub is_script_virtual: bool,
/// Information about the RPC configuration, if provided.
pub rpc_info: Option<RpcAttr>,
}

/// Returns a C function which acts as the callback when a virtual method of this instance is invoked.
Expand Down
Loading
Loading