Skip to content

Commit

Permalink
#[rpc] annotation for functions in #[godot_api] inherent impl blocks.
Browse files Browse the repository at this point in the history
The style is similar to GDScript's @rpc annotation, the macro can be used as follows:

#1 - Separate arguments:
```rust
#[rpc(any_peer, reliable)]
fn some_rpc(&mut self) {
    //..
}
```

Providing overlapping arguments generates a compile error.

Any omitted arguments are set to their default values.

#2 - Provide an expression:
```rust
const CONFIG: RpcArgs = RpcArgs {
    mode: RpcMode::Authority,
    ..RpcArgs::default()
};

#[rpc(config = CONFIG_EXPR)]
fn some_rpc(&mut self) {
    //..
}
```

Number #2 is useful in case you want to reuse the configuration on multiple functions.

Number #2 is mutually exclusive with number #1.
---

The generated macro code works as follows:
- Caches the configuration in a `ClassPlugin`.
- On `__before_ready()`, searches for the configuration in the plugin, registering them with Node::rpc_config().
  • Loading branch information
Houtamelo committed Sep 19, 2024
1 parent f33fe1f commit 8ae84df
Show file tree
Hide file tree
Showing 17 changed files with 493 additions and 7 deletions.
2 changes: 2 additions & 0 deletions godot-core/src/meta/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,15 @@ mod godot_convert;
mod method_info;
mod property_info;
mod ref_arg;
mod rpc_args;
mod sealed;
mod signature;
mod traits;

pub mod error;
pub use class_name::ClassName;
pub use godot_convert::{FromGodot, GodotConvert, ToGodot};
pub use rpc_args::RpcArgs;
pub use traits::{ArrayElement, GodotType, PackedArrayElement};

pub(crate) use crate::impl_godot_as_self;
Expand Down
50 changes: 50 additions & 0 deletions godot-core/src/meta/rpc_args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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;

/// See [Godot documentation](https://docs.godotengine.org/en/stable/tutorials/networking/high_level_multiplayer.html#remote-procedure-calls)
#[derive(Debug, Clone, Copy)]
pub struct RpcArgs {
pub mode: RpcMode,
pub transfer_mode: TransferMode,
pub call_local: bool,
pub transfer_channel: u32,
}

impl Default for RpcArgs {
fn default() -> Self {
Self {
mode: RpcMode::AUTHORITY,
transfer_mode: TransferMode::UNRELIABLE,
call_local: false,
transfer_channel: 0,
}
}
}

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

/// Returns a [`Dictionary`] populated with the values required for a call to [`Node::rpc_config`].
pub fn into_dictionary(self) -> Dictionary {
dict! {
"mode": self.mode,
"transfer_mode": self.transfer_mode,
"call_local": self.call_local,
"transfer_channel": self.transfer_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
4 changes: 3 additions & 1 deletion 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, PluginItem,
};
pub use crate::storage::{as_storage, Storage};
pub use sys::out;

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);
}
25 changes: 24 additions & 1 deletion godot-core/src/registry/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use std::ptr;
use crate::init::InitLevel;
use crate::meta::ClassName;
use crate::obj::{cap, GodotClass};
use crate::private::{ClassPlugin, PluginItem};
use crate::private::{ClassPlugin, PluginItem, __godot_rust_plugin___GODOT_PLUGIN_REGISTRY};
use crate::registry::callbacks;
use crate::registry::plugin::ErasedRegisterFn;
use crate::{godot_error, sys};
Expand Down Expand Up @@ -200,6 +200,28 @@ pub fn unregister_classes(init_level: InitLevel) {
}
}

pub fn auto_register_rpcs<T: GodotClass>(object: &mut T) {
let class_name = T::class_name();

// 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();

// Find the element that matches our class, and call the closure if it exists.
for elem in plugins.iter().filter(|elem| elem.class_name == class_name) {
if let PluginItem::InherentImpl {
register_rpcs_fn, ..
} = &elem.item
{
if let Some(closure) = register_rpcs_fn {
(closure.raw)(object);
}

// We found the element, break out of the loop regardless of the value of `register_rpcs_fn`.
break;
}
}
}

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

PluginItem::InherentImpl {
register_methods_constants_fn,
register_rpcs_fn: _,
#[cfg(all(since_api = "4.3", feature = "docs"))]
docs: _,
} => {
Expand Down
16 changes: 15 additions & 1 deletion 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,17 @@ 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)
}
}

/// 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 @@ -107,6 +117,10 @@ pub enum PluginItem {
///
/// Always present since that's the entire point of this `impl` block.
register_methods_constants_fn: ErasedRegisterFn,
/// Callback to library-generated function which calls [`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.
register_rpcs_fn: Option<ErasedRegisterRpcsFn>,
#[cfg(all(since_api = "4.3", feature = "docs"))]
docs: InherentImplDocs,
},
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::RpcInfo;
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<RpcInfo>,
}

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

0 comments on commit 8ae84df

Please sign in to comment.