Skip to content

Commit c4dd6b7

Browse files
committed
#[rpc] annotation for functions in #[godot_api] inherent impl blocks.
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().
1 parent f33fe1f commit c4dd6b7

File tree

21 files changed

+613
-102
lines changed

21 files changed

+613
-102
lines changed

godot-core/src/docs.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
*/
77

88
use crate::meta::ClassName;
9-
use crate::registry::plugin::PluginItem;
9+
use crate::registry::plugin::{InherentImpl, PluginItem};
1010
use std::collections::HashMap;
1111

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

7979
match x.item {
80-
PluginItem::InherentImpl { docs, .. } => {
80+
PluginItem::InherentImpl(InherentImpl { docs, .. }) => {
8181
map.entry(class_name).or_default().inherent = docs
8282
}
8383

godot-core/src/meta/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,19 @@ mod godot_convert;
4040
mod method_info;
4141
mod property_info;
4242
mod ref_arg;
43+
// RpcConfig uses `MultiplayerPeer::TransferMode` and `MultiplayerApi::RpcMode`,
44+
// which are only available when `codegen-full` is enabled.
45+
#[cfg(feature = "codegen-full")]
46+
mod rpc_config;
4347
mod sealed;
4448
mod signature;
4549
mod traits;
4650

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

5258
pub(crate) use crate::impl_godot_as_self;

godot-core/src/meta/rpc_config.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright (c) godot-rust; Bromeon and contributors.
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
6+
*/
7+
8+
use crate::builtin::{Dictionary, StringName};
9+
use crate::classes::multiplayer_api::RpcMode;
10+
use crate::classes::multiplayer_peer::TransferMode;
11+
use crate::classes::Node;
12+
use crate::dict;
13+
use crate::meta::ToGodot;
14+
15+
/// See [Godot documentation](https://docs.godotengine.org/en/stable/tutorials/networking/high_level_multiplayer.html#remote-procedure-calls)
16+
#[derive(Copy, Clone, Debug)]
17+
pub struct RpcConfig {
18+
pub rpc_mode: RpcMode,
19+
pub transfer_mode: TransferMode,
20+
pub call_local: bool,
21+
pub channel: u32,
22+
}
23+
24+
impl Default for RpcConfig {
25+
fn default() -> Self {
26+
Self {
27+
rpc_mode: RpcMode::AUTHORITY,
28+
transfer_mode: TransferMode::UNRELIABLE,
29+
call_local: false,
30+
channel: 0,
31+
}
32+
}
33+
}
34+
35+
impl RpcConfig {
36+
/// Register `method` as a remote procedure call on `node`.
37+
pub fn register(self, node: &mut Node, method: impl Into<StringName>) {
38+
node.rpc_config(method.into(), &self.into_dictionary().to_variant());
39+
}
40+
41+
/// Returns a [`Dictionary`] populated with the values required for a call to [`Node::rpc_config`].
42+
pub fn into_dictionary(self) -> Dictionary {
43+
dict! {
44+
"rpc_mode": self.rpc_mode,
45+
"transfer_mode": self.transfer_mode,
46+
"call_local": self.call_local,
47+
"channel": self.channel,
48+
}
49+
}
50+
}

godot-core/src/obj/traits.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,7 @@ pub mod cap {
455455
use super::*;
456456
use crate::builtin::{StringName, Variant};
457457
use crate::obj::{Base, Bounds, Gd};
458+
use std::any::Any;
458459

459460
/// Trait for all classes that are default-constructible from the Godot engine.
460461
///
@@ -558,6 +559,8 @@ pub mod cap {
558559
fn __register_methods();
559560
#[doc(hidden)]
560561
fn __register_constants();
562+
#[doc(hidden)]
563+
fn __register_rpcs(_: &mut dyn Any) {}
561564
}
562565

563566
pub trait ImplementsGodotExports: GodotClass {

godot-core/src/private.rs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
pub use crate::gen::classes::class_macros;
99
pub use crate::obj::rtti::ObjectRtti;
1010
pub use crate::registry::callbacks;
11-
pub use crate::registry::plugin::{ClassPlugin, ErasedRegisterFn, PluginItem};
11+
pub use crate::registry::plugin::{
12+
ClassPlugin, ErasedRegisterFn, ErasedRegisterRpcsFn, InherentImpl, PluginItem,
13+
};
1214
pub use crate::storage::{as_storage, Storage};
1315
pub use sys::out;
1416

@@ -17,11 +19,10 @@ pub use crate::meta::trace;
1719

1820
use crate::global::godot_error;
1921
use crate::meta::error::CallError;
20-
use crate::meta::CallContext;
22+
use crate::meta::{CallContext, ClassName};
2123
use crate::sys;
2224
use std::sync::{atomic, Arc, Mutex};
2325
use sys::Global;
24-
2526
// ----------------------------------------------------------------------------------------------------------------------------------------------
2627
// Global variables
2728

@@ -128,6 +129,21 @@ pub(crate) fn iterate_plugins(mut visitor: impl FnMut(&ClassPlugin)) {
128129
sys::plugin_foreach!(__GODOT_PLUGIN_REGISTRY; visitor);
129130
}
130131

132+
pub(crate) fn find_inherent_impl(class_name: ClassName) -> Option<InherentImpl> {
133+
// We do this manually instead of using `iterate_plugins()` because we want to break as soon as we find a match.
134+
let plugins = __godot_rust_plugin___GODOT_PLUGIN_REGISTRY.lock().unwrap();
135+
136+
plugins.iter().find_map(|elem| {
137+
if elem.class_name == class_name {
138+
if let PluginItem::InherentImpl(inherent_impl) = &elem.item {
139+
return Some(inherent_impl.clone());
140+
}
141+
}
142+
143+
None
144+
})
145+
}
146+
131147
// ----------------------------------------------------------------------------------------------------------------------------------------------
132148
// Traits and types
133149

godot-core/src/registry/callbacks.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,3 +354,7 @@ pub fn register_user_methods_constants<T: cap::ImplementsGodotApi>(_class_builde
354354
T::__register_methods();
355355
T::__register_constants();
356356
}
357+
358+
pub fn register_user_rpcs<T: cap::ImplementsGodotApi>(object: &mut dyn Any) {
359+
T::__register_rpcs(object);
360+
}

godot-core/src/registry/class.rs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::meta::ClassName;
1313
use crate::obj::{cap, GodotClass};
1414
use crate::private::{ClassPlugin, PluginItem};
1515
use crate::registry::callbacks;
16-
use crate::registry::plugin::ErasedRegisterFn;
16+
use crate::registry::plugin::{ErasedRegisterFn, InherentImpl};
1717
use crate::{godot_error, sys};
1818
use sys::{interface_fn, out, Global, GlobalGuard, GlobalLockError};
1919

@@ -71,7 +71,7 @@ impl ClassRegistrationInfo {
7171
// Note: when changing this match, make sure the array has sufficient size.
7272
let index = match item {
7373
PluginItem::Struct { .. } => 0,
74-
PluginItem::InherentImpl { .. } => 1,
74+
PluginItem::InherentImpl(InherentImpl { .. }) => 1,
7575
PluginItem::ITraitImpl { .. } => 2,
7676
};
7777

@@ -200,6 +200,18 @@ pub fn unregister_classes(init_level: InitLevel) {
200200
}
201201
}
202202

203+
#[cfg(feature = "codegen-full")]
204+
pub fn auto_register_rpcs<T: GodotClass>(object: &mut T) {
205+
// Find the element that matches our class, and call the closure if it exists.
206+
if let Some(InherentImpl {
207+
register_rpcs_fn: Some(closure),
208+
..
209+
}) = crate::private::find_inherent_impl(T::class_name())
210+
{
211+
(closure.raw)(object);
212+
}
213+
}
214+
203215
fn global_loaded_classes() -> GlobalGuard<'static, HashMap<InitLevel, Vec<LoadedClass>>> {
204216
match LOADED_CLASSES.try_lock() {
205217
Ok(it) => it,
@@ -281,11 +293,12 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) {
281293
}
282294
}
283295

284-
PluginItem::InherentImpl {
296+
PluginItem::InherentImpl(InherentImpl {
285297
register_methods_constants_fn,
298+
register_rpcs_fn: _,
286299
#[cfg(all(since_api = "4.3", feature = "docs"))]
287300
docs: _,
288-
} => {
301+
}) => {
289302
c.register_methods_constants_fn = Some(register_methods_constants_fn);
290303
}
291304

godot-core/src/registry/plugin.rs

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ use crate::meta::ClassName;
1212
use crate::sys;
1313
use std::any::Any;
1414
use std::fmt;
15-
1615
// TODO(bromeon): some information coming from the proc-macro API is deferred through PluginItem, while others is directly
1716
// translated to code. Consider moving more code to the PluginItem, which allows for more dynamic registration and will
1817
// be easier for a future builder API.
@@ -45,6 +44,31 @@ impl fmt::Debug for ErasedRegisterFn {
4544
}
4645
}
4746

47+
#[derive(Copy, Clone)]
48+
pub struct ErasedRegisterRpcsFn {
49+
pub raw: fn(&mut dyn Any),
50+
}
51+
52+
impl fmt::Debug for ErasedRegisterRpcsFn {
53+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54+
write!(f, "0x{:0>16x}", self.raw as usize)
55+
}
56+
}
57+
58+
#[derive(Clone, Debug)]
59+
pub struct InherentImpl {
60+
/// Callback to library-generated function which registers functions and constants in the `impl` block.
61+
///
62+
/// Always present since that's the entire point of this `impl` block.
63+
pub register_methods_constants_fn: ErasedRegisterFn,
64+
/// 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.
65+
///
66+
/// This function is called in [`UserClass::__before_ready()`](crate::obj::UserClass::__before_ready) definitions generated by the `#[derive(GodotClass)]` macro.
67+
pub register_rpcs_fn: Option<ErasedRegisterRpcsFn>,
68+
#[cfg(all(since_api = "4.3", feature = "docs"))]
69+
pub docs: InherentImplDocs,
70+
}
71+
4872
/// Represents the data part of a [`ClassPlugin`] instance.
4973
///
5074
/// Each enumerator represents a different item in Rust code, which is processed by an independent proc macro (for example,
@@ -102,14 +126,7 @@ pub enum PluginItem {
102126
},
103127

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

114131
/// Collected from `#[godot_api] impl I... for MyClass`.
115132
ITraitImpl {

godot-macros/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ homepage = "https://godot-rust.github.io"
1313
[features]
1414
api-custom = ["godot-bindings/api-custom"]
1515
docs = ["dep:markdown"]
16+
codegen-full = []
1617

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

3233
# Reverse dev dependencies so doctests can use `godot::` prefix.
3334
[dev-dependencies]
34-
godot = { path = "../godot", default-features = false }
35+
godot = { path = "../godot", default-features = false, features = ["__codegen-full"] }
3536

3637
# https://docs.rs/about/metadata
3738
[package.metadata.docs.rs]

godot-macros/src/class/data_models/field_var.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ impl GetterSetterImpl {
204204
external_attributes: Vec::new(),
205205
rename: None,
206206
is_script_virtual: false,
207+
rpc_info: None,
207208
},
208209
);
209210

0 commit comments

Comments
 (0)