Skip to content

Commit b26e114

Browse files
authored
Merge pull request #902 from Houtamelo/rpc_annotation
Add `#[rpc]` attribute to user-defined functions
2 parents f33fe1f + 2ae2995 commit b26e114

File tree

21 files changed

+614
-101
lines changed

21 files changed

+614
-101
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: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,18 @@ mod godot_convert;
4040
mod method_info;
4141
mod property_info;
4242
mod ref_arg;
43+
// RpcConfig uses MultiplayerPeer::TransferMode and MultiplayerApi::RpcMode, which are only enabled in `codegen-full` feature.
44+
#[cfg(feature = "codegen-full")]
45+
mod rpc_config;
4346
mod sealed;
4447
mod signature;
4548
mod traits;
4649

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

5257
pub(crate) use crate::impl_godot_as_self;

godot-core/src/meta/rpc_config.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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+
/// Configuration for a remote procedure call, typically used with `#[rpc(config = ...)]`.
16+
///
17+
/// See [Godot documentation](https://docs.godotengine.org/en/stable/tutorials/networking/high_level_multiplayer.html#remote-procedure-calls).
18+
#[derive(Copy, Clone, Debug)]
19+
pub struct RpcConfig {
20+
pub rpc_mode: RpcMode,
21+
pub transfer_mode: TransferMode,
22+
pub call_local: bool,
23+
pub channel: u32,
24+
}
25+
26+
impl Default for RpcConfig {
27+
fn default() -> Self {
28+
Self {
29+
rpc_mode: RpcMode::AUTHORITY,
30+
transfer_mode: TransferMode::UNRELIABLE,
31+
call_local: false,
32+
channel: 0,
33+
}
34+
}
35+
}
36+
37+
impl RpcConfig {
38+
/// Register `method` as a remote procedure call on `node`.
39+
pub fn configure_node(self, node: &mut Node, method_name: impl Into<StringName>) {
40+
node.rpc_config(method_name.into(), &self.to_dictionary().to_variant());
41+
}
42+
43+
/// Returns a [`Dictionary`] populated with the values required for a call to [`Node::rpc_config()`].
44+
pub fn to_dictionary(&self) -> Dictionary {
45+
dict! {
46+
"rpc_mode": self.rpc_mode,
47+
"transfer_mode": self.transfer_mode,
48+
"call_local": self.call_local,
49+
"channel": self.channel,
50+
}
51+
}
52+
}

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 & 2 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

@@ -21,7 +23,6 @@ use crate::meta::CallContext;
2123
use crate::sys;
2224
use std::sync::{atomic, Arc, Mutex};
2325
use sys::Global;
24-
2526
// ----------------------------------------------------------------------------------------------------------------------------------------------
2627
// Global variables
2728

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

132+
#[cfg(feature = "codegen-full")] // Remove if used in other scenarios.
133+
pub(crate) fn find_inherent_impl(class_name: crate::meta::ClassName) -> Option<InherentImpl> {
134+
// We do this manually instead of using `iterate_plugins()` because we want to break as soon as we find a match.
135+
let plugins = __godot_rust_plugin___GODOT_PLUGIN_REGISTRY.lock().unwrap();
136+
137+
plugins.iter().find_map(|elem| {
138+
if elem.class_name == class_name {
139+
if let PluginItem::InherentImpl(inherent_impl) = &elem.item {
140+
return Some(inherent_impl.clone());
141+
}
142+
}
143+
144+
None
145+
})
146+
}
147+
131148
// ----------------------------------------------------------------------------------------------------------------------------------------------
132149
// Traits and types
133150

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(_) => 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 = ["godot/__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}
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

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
66
*/
77

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

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

0 commit comments

Comments
 (0)