Skip to content

Commit 8c55947

Browse files
committed
Callable: use Cow<str> instead of AsArg<GString>
Provided arguments are mostly string literals, which are cheap to store in `Cow`, but expensive to convert to `GString`. Doing this on every call can have quite an impact.
1 parent d3328df commit 8c55947

File tree

7 files changed

+79
-65
lines changed

7 files changed

+79
-65
lines changed

godot-core/src/builtin/callable.rs

Lines changed: 58 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use std::{fmt, ptr};
1111
use godot_ffi as sys;
1212
use sys::{ffi_methods, ExtVariantType, GodotFfi};
1313

14-
use crate::builtin::{inner, AnyArray, GString, StringName, Variant};
14+
use crate::builtin::{inner, AnyArray, CowStr, StringName, Variant};
1515
use crate::meta::{GodotType, ToGodot};
1616
use crate::obj::bounds::DynMemory;
1717
use crate::obj::{Bounds, Gd, GodotClass, InstanceId, Singleton};
@@ -114,10 +114,10 @@ impl Callable {
114114
};
115115

116116
#[cfg(feature = "experimental-threads")]
117-
let callable = Self::from_sync_fn(&callable_name, function);
117+
let callable = Self::from_sync_fn(callable_name, function);
118118

119119
#[cfg(not(feature = "experimental-threads"))]
120-
let callable = Self::from_fn(&callable_name, function);
120+
let callable = Self::from_fn(callable_name, function);
121121

122122
callable
123123
}
@@ -161,9 +161,14 @@ impl Callable {
161161
where
162162
R: ToGodot,
163163
F: 'static + FnMut(&[&Variant]) -> R,
164-
S: meta::AsArg<GString>,
164+
S: Into<CowStr>,
165165
{
166-
Self::from_fn_wrapper(name, rust_function, Some(std::thread::current().id()), None)
166+
Self::from_fn_wrapper(
167+
name.into(),
168+
rust_function,
169+
Some(std::thread::current().id()),
170+
None,
171+
)
167172
}
168173

169174
/// Creates a new callable linked to the given object from **single-threaded** Rust function or closure.
@@ -179,10 +184,10 @@ impl Callable {
179184
R: ToGodot,
180185
T: GodotClass,
181186
F: 'static + FnMut(&[&Variant]) -> R,
182-
S: meta::AsArg<GString>,
187+
S: Into<CowStr>,
183188
{
184189
Self::from_fn_wrapper(
185-
name,
190+
name.into(),
186191
rust_function,
187192
Some(std::thread::current().id()),
188193
Some(linked_object.instance_id()),
@@ -196,14 +201,19 @@ impl Callable {
196201
pub fn from_local_fn<F, S>(name: S, mut rust_function: F) -> Self
197202
where
198203
F: 'static + FnMut(&[&Variant]) -> Result<Variant, ()>,
199-
S: meta::AsArg<GString>,
204+
S: Into<CowStr>,
200205
{
201-
meta::arg_into_owned!(name);
206+
let name_cow = name.into();
202207

203-
Self::from_fn(&name, move |args| {
204-
// Ignore errors.
205-
rust_function(args).unwrap_or_else(|()| Variant::nil())
206-
})
208+
Self::from_fn_wrapper(
209+
name_cow,
210+
move |args| {
211+
// Ignore errors.
212+
rust_function(args).unwrap_or_else(|()| Variant::nil())
213+
},
214+
Some(std::thread::current().id()),
215+
None,
216+
)
207217
}
208218

209219
/// Create callable from **single-threaded** Rust function or closure that can only be called once.
@@ -216,38 +226,47 @@ impl Callable {
216226
where
217227
R: ToGodot,
218228
F: 'static + FnOnce(&[&Variant]) -> R,
219-
S: meta::AsArg<GString>,
229+
S: Into<CowStr>,
220230
{
221-
meta::arg_into_owned!(name);
231+
let name_cow = name.into();
222232

223233
let mut rust_fn_once = Some(rust_function);
224-
Self::from_fn(&name, move |args| {
225-
let rust_fn_once = rust_fn_once
226-
.take()
227-
.expect("callable created with from_once_fn() has already been consumed");
228-
229-
rust_fn_once(args)
230-
})
234+
Self::from_fn_wrapper(
235+
name_cow,
236+
move |args| {
237+
let rust_fn_once = rust_fn_once
238+
.take()
239+
.expect("callable created with from_once_fn() has already been consumed");
240+
241+
rust_fn_once(args)
242+
},
243+
Some(std::thread::current().id()),
244+
None,
245+
)
231246
}
232247

233248
#[cfg(feature = "trace")] // Test only.
234249
#[doc(hidden)]
235250
pub fn __once_fn<F, S>(name: S, rust_function: F) -> Self
236251
where
237252
F: 'static + FnOnce(&[&Variant]) -> Variant,
238-
S: meta::AsArg<GString>,
253+
S: Into<CowStr>,
239254
{
240255
Self::from_once_fn(name, rust_function)
241256
}
242257

243258
pub(crate) fn with_scoped_fn<S, F, Fc, R>(name: S, rust_function: F, callable_usage: Fc) -> R
244259
where
245-
S: meta::AsArg<GString>,
260+
S: Into<CowStr>,
246261
F: FnMut(&[&Variant]) -> Variant,
247262
Fc: FnOnce(&Callable) -> R,
248263
{
249-
let callable =
250-
Self::from_fn_wrapper(name, rust_function, Some(std::thread::current().id()), None);
264+
let callable = Self::from_fn_wrapper(
265+
name.into(),
266+
rust_function,
267+
Some(std::thread::current().id()),
268+
None,
269+
);
251270

252271
callable_usage(&callable)
253272
}
@@ -276,9 +295,9 @@ impl Callable {
276295
where
277296
R: ToGodot,
278297
F: 'static + Send + Sync + FnMut(&[&Variant]) -> R,
279-
S: meta::AsArg<GString>,
298+
S: Into<CowStr>,
280299
{
281-
Self::from_fn_wrapper(name, rust_function, None, None)
300+
Self::from_fn_wrapper(name.into(), rust_function, None, None)
282301
}
283302

284303
/// Create a highly configurable callable from Rust.
@@ -305,21 +324,19 @@ impl Callable {
305324
Self::from_custom_info(info)
306325
}
307326

308-
fn from_fn_wrapper<F, R, S>(
309-
_name: S,
327+
fn from_fn_wrapper<F, R>(
328+
name: CowStr,
310329
rust_function: F,
311330
thread_id: Option<ThreadId>,
312331
linked_object_id: Option<InstanceId>,
313332
) -> Self
314333
where
315334
F: FnMut(&[&Variant]) -> R,
316335
R: ToGodot,
317-
S: meta::AsArg<GString>,
318336
{
319337
let wrapper = FnWrapper {
320338
rust_function,
321-
#[cfg(safeguards_balanced)]
322-
name: { _name.into_arg().cow_into_owned() },
339+
name,
323340
thread_id,
324341
linked_object_id,
325342
};
@@ -332,7 +349,6 @@ impl Callable {
332349
callable_userdata: Box::into_raw(Box::new(userdata)) as *mut std::ffi::c_void,
333350
call_func: Some(rust_callable_call_fn::<F, R>),
334351
free_func: Some(rust_callable_destroy::<FnWrapper<F>>),
335-
#[cfg(safeguards_balanced)]
336352
to_string_func: Some(rust_callable_to_string_named::<F>),
337353
is_valid_func: Some(rust_callable_is_valid),
338354
..Self::default_callable_custom_info()
@@ -601,8 +617,7 @@ mod custom_callable {
601617

602618
pub(crate) struct FnWrapper<F> {
603619
pub(super) rust_function: F,
604-
#[cfg(safeguards_balanced)]
605-
pub(super) name: GString,
620+
pub(super) name: CowStr,
606621

607622
/// `None` if the callable is multi-threaded ([`Callable::from_sync_fn`]).
608623
pub(super) thread_id: Option<ThreadId>,
@@ -692,24 +707,20 @@ mod custom_callable {
692707
{
693708
let arg_refs: &[&Variant] = Variant::borrow_ref_slice(p_args, p_argument_count as usize);
694709

695-
let name = name_or_optimized! {
696-
let w: &FnWrapper<F> = CallableUserdata::inner_from_raw(callable_userdata);
697-
w.name.to_string()
698-
};
699-
let ctx = meta::CallContext::custom_callable(&name);
710+
let w: &FnWrapper<F> = CallableUserdata::inner_from_raw(callable_userdata);
711+
let ctx = meta::CallContext::custom_callable(&w.name);
700712

701713
crate::private::handle_fallible_varcall(&ctx, &mut *r_error, move || {
702714
// Get the FnWrapper again inside closure so the FnMut doesn't have to be UnwindSafe.
703715
let w: &mut FnWrapper<F> = CallableUserdata::inner_from_raw(callable_userdata);
704716

705-
let name = name_or_optimized!(w.name.to_string());
706-
707717
// NOTE: this panic is currently not propagated to the caller, but results in an error message and Nil return.
708718
// See comments in itest callable_call() for details.
709719
sys::balanced_assert!(
710720
w.thread_id.is_none() || w.thread_id == Some(std::thread::current().id()),
711-
"Callable '{name}' created with from_fn() must be called from the same thread it was created in.\n\
712-
If you need to call it from any thread, use from_sync_fn() instead (requires `experimental-threads` feature)."
721+
"Callable '{}' created with from_fn() must be called from the same thread it was created in.\n\
722+
If you need to call it from any thread, use from_sync_fn() instead (requires `experimental-threads` feature).",
723+
w.name
713724
);
714725

715726
let result = (w.rust_function)(arg_refs).to_variant();
@@ -754,15 +765,15 @@ mod custom_callable {
754765
*r_is_valid = sys::conv::SYS_TRUE;
755766
}
756767

757-
#[cfg(safeguards_balanced)]
758768
pub unsafe extern "C" fn rust_callable_to_string_named<F>(
759769
callable_userdata: *mut std::ffi::c_void,
760770
r_is_valid: *mut sys::GDExtensionBool,
761771
r_out: sys::GDExtensionStringPtr,
762772
) {
763773
let w: &mut FnWrapper<F> = CallableUserdata::inner_from_raw(callable_userdata);
774+
let gstring = GString::from(w.name.as_ref());
764775

765-
w.name.clone().move_into_string_ptr(r_out);
776+
gstring.move_into_string_ptr(r_out);
766777
*r_is_valid = sys::conv::SYS_TRUE;
767778
}
768779

godot-core/src/obj/base.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ impl<T: GodotClass> Base<T> {
217217
});
218218

219219
let name = format!("Base<{}> deferred unref", T::class_id());
220-
let callable = Callable::from_once_fn(&name, move |_args| {
220+
let callable = Callable::from_once_fn(name, move |_args| {
221221
Self::drop_strong_ref(instance_id);
222222
});
223223

godot-core/src/obj/gd.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use godot_ffi as sys;
1212
use godot_ffi::is_main_thread;
1313
use sys::{static_assert_eq_size_align, SysPtr as _};
1414

15-
use crate::builtin::{Callable, GString, NodePath, StringName, Variant};
15+
use crate::builtin::{Callable, CowStr, NodePath, StringName, Variant};
1616
use crate::meta::error::{ConvertError, FromFfiError};
1717
use crate::meta::{
1818
ArrayElement, AsArg, ClassId, FromGodot, GodotConvert, GodotType, PropertyHintInfo, RefArg,
@@ -586,7 +586,7 @@ impl<T: GodotClass> Gd<T> {
586586
/// If you need a Callable which can live indefinitely, use [`Callable::from_fn()`].
587587
pub fn linked_callable<R, F>(
588588
&self,
589-
method_name: impl AsArg<GString>,
589+
method_name: impl Into<CowStr>,
590590
rust_function: F,
591591
) -> Callable
592592
where

godot-core/src/registry/signal/connect_builder.rs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
use super::{make_callable_name, make_godot_fn};
99
#[cfg(feature = "experimental-threads")]
1010
use crate::builtin::Callable;
11-
use crate::builtin::{GString, Variant};
11+
use crate::builtin::{CowStr, Variant};
1212
use crate::classes::object::ConnectFlags;
1313
use crate::meta;
1414
use crate::meta::{InParamTuple, ObjectToOwned};
@@ -76,7 +76,7 @@ pub struct ConnectBuilder<'ts, 'c, C: WithSignals, Ps> {
7676
#[derive(Default)]
7777
struct BuilderData {
7878
/// User-specified name; if not provided, the Rust RTTI type name of the function is used.
79-
callable_name: Option<GString>,
79+
callable_name: Option<CowStr>,
8080

8181
/// Godot connection flags.
8282
connect_flags: Option<ConnectFlags>,
@@ -98,14 +98,13 @@ where
9898
/// Name of the `Callable`, mostly used for debugging.
9999
///
100100
/// If not provided, the Rust type name of the function/method is used.
101-
pub fn name(mut self, name: impl meta::AsArg<GString>) -> Self {
101+
pub fn name(mut self, name: impl Into<CowStr>) -> Self {
102102
assert!(
103103
self.data.callable_name.is_none(),
104104
"name() called twice on the same builder."
105105
);
106106

107-
meta::arg_into_owned!(name);
108-
self.data.callable_name = Some(name);
107+
self.data.callable_name = Some(name.into());
109108
self
110109
}
111110

@@ -129,9 +128,9 @@ where
129128
godot_fn: impl FnMut(&[&Variant]) -> Variant + 'static,
130129
bound: &Gd<impl GodotClass>,
131130
) -> ConnectHandle {
132-
let callable_name = match &self.data.callable_name {
131+
let callable_name = match self.data.callable_name {
133132
Some(user_provided_name) => user_provided_name,
134-
None => &make_callable_name::<F>(),
133+
None => make_callable_name::<F>().to_string().into(),
135134
};
136135

137136
let callable = bound.linked_callable(callable_name, godot_fn);
@@ -311,9 +310,9 @@ impl<C: WithSignals, Ps: InParamTuple + 'static> ConnectBuilder<'_, '_, C, Ps> {
311310
.call((), args);
312311
});
313312

314-
let callable_name = match &self.data.callable_name {
313+
let callable_name = match self.data.callable_name {
315314
Some(user_provided_name) => user_provided_name,
316-
None => &make_callable_name::<F>(),
315+
None => make_callable_name::<F>(),
317316
};
318317

319318
let callable = Callable::from_sync_fn(callable_name, godot_fn);

godot-core/src/registry/signal/mod.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@ mod signal_object;
1111
mod signal_receiver;
1212
mod typed_signal;
1313

14+
use std::borrow::Cow;
15+
1416
pub(crate) use connect_builder::*;
1517
pub(crate) use connect_handle::*;
1618
pub(crate) use signal_object::*;
1719
pub(crate) use typed_signal::*;
1820

19-
use crate::builtin::{GString, Variant};
21+
use crate::builtin::{CowStr, Variant};
2022
use crate::meta;
2123

2224
// Used in `godot` crate.
@@ -52,9 +54,9 @@ where
5254
}
5355

5456
// Used by both `TypedSignal` and `ConnectBuilder`.
55-
fn make_callable_name<F>() -> GString {
57+
fn make_callable_name<F>() -> CowStr {
5658
// When using sys::short_type_name() in the future, make sure global "func" and member "MyClass::func" are rendered as such.
5759
// PascalCase heuristic should then be good enough.
5860

59-
std::any::type_name::<F>().into()
61+
Cow::Borrowed(std::any::type_name::<F>())
6062
}

godot-core/src/registry/signal/typed_signal.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ impl<'c, C: WithSignals, Ps: meta::ParamTuple> TypedSignal<'c, C, Ps> {
132132
bound: &Gd<impl GodotClass>,
133133
) -> ConnectHandle {
134134
let callable_name = make_callable_name::<F>();
135-
let callable = bound.linked_callable(&callable_name, godot_fn);
135+
let callable = bound.linked_callable(callable_name, godot_fn);
136136
self.inner_connect_untyped(callable, None)
137137
}
138138

itest/rust/src/benchmarks/mod.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,15 +117,17 @@ fn packed_array_from_iter_unknown_size() -> PackedInt32Array {
117117
#[bench(manual)]
118118
fn call_callv_rust_fn() -> BenchResult {
119119
let callable = Callable::from_fn("RustFunction", |_| ());
120+
let arg = varray![];
120121

121-
bench_measure(25, || callable.callv(&varray![]))
122+
bench_measure(25000, || callable.callv(&arg))
122123
}
123124

124125
#[bench(manual)]
125126
fn call_callv_custom() -> BenchResult {
126127
let callable = Callable::from_custom(MyRustCallable {});
128+
let arg = varray![];
127129

128-
bench_measure(25, || callable.callv(&varray![]))
130+
bench_measure(25, || callable.callv(&arg))
129131
}
130132

131133
// ----------------------------------------------------------------------------------------------------------------------------------------------

0 commit comments

Comments
 (0)