Skip to content

Commit 9b39925

Browse files
committed
Change NotUniqueError::check() to Gd::try_to_unique()
1 parent 7ebd5ec commit 9b39925

File tree

6 files changed

+136
-115
lines changed

6 files changed

+136
-115
lines changed

godot-core/src/meta/error/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
1010
mod call_error;
1111
mod convert_error;
12+
mod not_unique_error;
1213

1314
pub use call_error::*;
1415
pub use convert_error::*;
16+
pub use not_unique_error::*;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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+
/// Error caused by a reference-counted [`Gd`] instance which is not unique.
9+
///
10+
/// See [`Gd::try_to_unique()`] for futher information.
11+
#[derive(Debug)]
12+
pub enum NotUniqueError {
13+
/// The instance is shared and has a reference count greater than 1.
14+
Shared { ref_count: usize },
15+
16+
/// The instance is not reference-counted; thus can't determine its uniqueness.
17+
NotRefCounted,
18+
}
19+
20+
impl std::error::Error for NotUniqueError {}
21+
22+
impl std::fmt::Display for NotUniqueError {
23+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24+
match self {
25+
Self::Shared { ref_count } => {
26+
write!(f, "pointer is not unique, reference count: {ref_count}")
27+
}
28+
Self::NotRefCounted => {
29+
write!(f, "pointer is not reference-counted")
30+
}
31+
}
32+
}
33+
}

godot-core/src/obj/bounds.rs

+34-12
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,10 @@ pub trait DynMemory: Sealed {
175175
#[doc(hidden)]
176176
fn is_ref_counted<T: GodotClass>(obj: &RawGd<T>) -> Option<bool>;
177177

178+
/// Return the reference count, or `None` if the object is dead or manually managed.
179+
#[doc(hidden)]
180+
fn get_ref_count<T: GodotClass>(obj: &RawGd<T>) -> Option<usize>;
181+
178182
/// Returns `true` if argument and return pointers are passed as `Ref<T>` pointers given this
179183
/// [`PtrcallType`].
180184
///
@@ -236,6 +240,13 @@ impl DynMemory for MemRefCounted {
236240
Some(true)
237241
}
238242

243+
fn get_ref_count<T: GodotClass>(obj: &RawGd<T>) -> Option<usize> {
244+
let ref_count = obj.with_ref_counted(|refc| refc.get_reference_count());
245+
246+
// TODO find a safer cast alternative, e.g. num-traits crate with ToPrimitive (Debug) + AsPrimitive (Release).
247+
Some(ref_count as usize)
248+
}
249+
239250
fn pass_as_ref(call_type: sys::PtrcallType) -> bool {
240251
matches!(call_type, sys::PtrcallType::Virtual)
241252
}
@@ -244,30 +255,30 @@ impl DynMemory for MemRefCounted {
244255
/// Memory managed through Godot reference counter, if present; otherwise manual.
245256
/// This is used only for `Object` classes.
246257
pub struct MemDynamic {}
258+
impl MemDynamic {
259+
/// Check whether dynamic type is ref-counted.
260+
fn inherits_refcounted<T: GodotClass>(obj: &RawGd<T>) -> bool {
261+
obj.instance_id_unchecked()
262+
.map(|id| id.is_ref_counted())
263+
.unwrap_or(false)
264+
}
265+
}
247266
impl Sealed for MemDynamic {}
248267
impl DynMemory for MemDynamic {
249268
fn maybe_init_ref<T: GodotClass>(obj: &mut RawGd<T>) {
250269
out!(" MemDyn::init <{}>", std::any::type_name::<T>());
251-
if obj
252-
.instance_id_unchecked()
253-
.map(|id| id.is_ref_counted())
254-
.unwrap_or(false)
255-
{
270+
if Self::inherits_refcounted(obj) {
256271
// Will call `RefCounted::init_ref()` which checks for liveness.
257-
out!("MemDyn -> MemRefc");
272+
out!(" MemDyn -> MemRefc");
258273
MemRefCounted::maybe_init_ref(obj)
259274
} else {
260-
out!("MemDyn -> MemManu");
275+
out!(" MemDyn -> MemManu");
261276
}
262277
}
263278

264279
fn maybe_inc_ref<T: GodotClass>(obj: &mut RawGd<T>) {
265280
out!(" MemDyn::inc <{}>", std::any::type_name::<T>());
266-
if obj
267-
.instance_id_unchecked()
268-
.map(|id| id.is_ref_counted())
269-
.unwrap_or(false)
270-
{
281+
if Self::inherits_refcounted(obj) {
271282
// Will call `RefCounted::reference()` which checks for liveness.
272283
MemRefCounted::maybe_inc_ref(obj)
273284
}
@@ -291,6 +302,14 @@ impl DynMemory for MemDynamic {
291302
// Return `None` if obj is dead
292303
obj.instance_id_unchecked().map(|id| id.is_ref_counted())
293304
}
305+
306+
fn get_ref_count<T: GodotClass>(obj: &RawGd<T>) -> Option<usize> {
307+
if Self::inherits_refcounted(obj) {
308+
MemRefCounted::get_ref_count(obj)
309+
} else {
310+
None
311+
}
312+
}
294313
}
295314

296315
/// No memory management, user responsible for not leaking.
@@ -307,6 +326,9 @@ impl DynMemory for MemManual {
307326
fn is_ref_counted<T: GodotClass>(_obj: &RawGd<T>) -> Option<bool> {
308327
Some(false)
309328
}
329+
fn get_ref_count<T: GodotClass>(_obj: &RawGd<T>) -> Option<usize> {
330+
None
331+
}
310332
}
311333

312334
// ----------------------------------------------------------------------------------------------------------------------------------------------

godot-core/src/obj/gd.rs

+40-81
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@ impl<T: GodotClass> Gd<T> {
411411
#[doc(hidden)]
412412
pub fn script_sys(&self) -> sys::GDExtensionScriptLanguagePtr
413413
where
414-
T: Inherits<crate::classes::ScriptLanguage>,
414+
T: Inherits<classes::ScriptLanguage>,
415415
{
416416
self.raw.script_sys()
417417
}
@@ -553,6 +553,43 @@ where
553553
}
554554
}
555555

556+
/// _The methods in this impl block are only available for objects `T` that are reference-counted,
557+
/// i.e. anything that inherits `RefCounted`._ <br><br>
558+
impl<T> Gd<T>
559+
where
560+
T: GodotClass + Bounds<Memory = bounds::MemRefCounted>,
561+
{
562+
/// Makes sure that `self` does not share references with other `Gd` instances.
563+
///
564+
/// If `self` is unique, i.e. its reference count is 1, then `Ok(self)` is returned.
565+
///
566+
/// If `self` is shared or not reference-counted (`T=Object` pointing to a dynamic type that is manually managed),
567+
/// then `Err((self, NotUniqueError))` is returned. You can thus reuse the original object (first element in the tuple).
568+
///
569+
/// ## Example
570+
///
571+
/// ```no_run
572+
/// use godot::prelude::*;
573+
/// use godot::meta::error::NotUniqueError;
574+
///
575+
/// let unique = RefCounted::new_gd();
576+
/// assert!(unique.try_to_unique().is_ok());
577+
///
578+
/// let shared = RefCounted::new_gd();
579+
/// let shared2 = shared.clone();
580+
/// assert!(shared.try_to_unique().is_err());
581+
/// ```
582+
pub fn try_to_unique(mut self) -> Result<Self, (Self, NotUniqueError)> {
583+
use crate::obj::bounds::DynMemory as _;
584+
585+
match <T as Bounds>::DynMemory::get_ref_count(&self.raw) {
586+
Some(1) => Ok(self),
587+
Some(ref_count) => Err((self, NotUniqueError::Shared { ref_count })),
588+
None => Err((self, NotUniqueError::NotRefCounted)),
589+
}
590+
}
591+
}
592+
556593
// ----------------------------------------------------------------------------------------------------------------------------------------------
557594
// Trait impls
558595

@@ -737,83 +774,5 @@ impl<T: GodotClass> std::hash::Hash for Gd<T> {
737774
impl<T: GodotClass> std::panic::UnwindSafe for Gd<T> {}
738775
impl<T: GodotClass> std::panic::RefUnwindSafe for Gd<T> {}
739776

740-
/// Error stemming from the non-uniqueness of the [`Gd`] instance.
741-
///
742-
/// Keeping track of the uniqueness of references can be crucial in many applications, especially if we want to ensure
743-
/// that the passed [`Gd`] reference will be possessed by only one different object instance or function in its lifetime.
744-
///
745-
/// Only applicable to [`GodotClass`] objects that inherit from [`RefCounted`](crate::gen::classes::RefCounted). To check the
746-
/// uniqueness, call the `check()` associated method.
747-
///
748-
/// ## Example
749-
///
750-
/// ```no_run
751-
/// use godot::prelude::*;
752-
/// use godot::obj::NotUniqueError;
753-
///
754-
/// let shared = RefCounted::new_gd();
755-
/// let cloned = shared.clone();
756-
/// let result = NotUniqueError::check(shared);
757-
///
758-
/// assert!(result.is_err());
759-
///
760-
/// if let Err(error) = result {
761-
/// assert_eq!(error.get_reference_count(), 2)
762-
/// }
763-
/// ```
764-
#[derive(Debug)]
765-
pub struct NotUniqueError {
766-
reference_count: i32,
767-
}
768-
769-
impl NotUniqueError {
770-
/// check [`Gd`] reference uniqueness.
771-
///
772-
/// Checks the [`Gd`] of the [`GodotClass`](crate::obj::GodotClass) that inherits from [`RefCounted`](crate::gen::classes::RefCounted)
773-
/// if it is an unique reference to the object.
774-
///
775-
/// ## Example
776-
///
777-
/// ```no_run
778-
/// use godot::prelude::*;
779-
/// use godot::obj::NotUniqueError;
780-
///
781-
/// let unique = RefCounted::new_gd();
782-
/// assert!(NotUniqueError::check(unique).is_ok());
783-
///
784-
/// let shared = RefCounted::new_gd();
785-
/// let cloned = shared.clone();
786-
/// assert!(NotUniqueError::check(shared).is_err());
787-
/// assert!(NotUniqueError::check(cloned).is_err());
788-
/// ```
789-
pub fn check<T>(rc: Gd<T>) -> Result<Gd<T>, Self>
790-
where
791-
T: Inherits<crate::gen::classes::RefCounted>,
792-
{
793-
let rc = rc.upcast::<crate::gen::classes::RefCounted>();
794-
let reference_count = rc.get_reference_count();
795-
796-
if reference_count != 1 {
797-
Err(Self { reference_count })
798-
} else {
799-
Ok(rc.cast::<T>())
800-
}
801-
}
802-
803-
/// Get the detected reference count
804-
pub fn get_reference_count(&self) -> i32 {
805-
self.reference_count
806-
}
807-
}
808-
809-
impl std::error::Error for NotUniqueError {}
810-
811-
impl std::fmt::Display for NotUniqueError {
812-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
813-
write!(
814-
f,
815-
"pointer is not unique, current reference count: {}",
816-
self.reference_count
817-
)
818-
}
819-
}
777+
#[deprecated = "Moved to `godot::meta::error`"]
778+
pub use crate::meta::error::NotUniqueError;

godot-core/src/tools/io_error.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,9 @@ impl IoError {
8585
});
8686
}
8787

88-
match NotUniqueError::check(file_access) {
88+
match file_access.try_to_unique() {
8989
Ok(gd) => Ok(gd),
90-
Err(err) => Err(Self {
90+
Err((_, err)) => Err(Self {
9191
data: ErrorData::GFile(GFileError {
9292
kind: GFileErrorKind::NotUniqueRef(err),
9393
path: path.to_string(),

itest/rust/src/object_tests/object_test.rs

+25-20
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ use godot::classes::{
1414
RefCounted,
1515
};
1616
use godot::global::instance_from_id;
17-
use godot::meta::GodotType;
18-
use godot::meta::{FromGodot, ToGodot};
17+
use godot::meta::error::NotUniqueError;
18+
use godot::meta::{FromGodot, GodotType, ToGodot};
1919
use godot::obj::{Base, Gd, Inherits, InstanceId, NewAlloc, NewGd, RawGd};
2020
use godot::register::{godot_api, GodotClass};
2121
use godot::sys::{self, interface_fn, GodotFfi};
@@ -841,6 +841,29 @@ fn object_get_scene_tree(ctx: &TestContext) {
841841
assert_eq!(count, 1);
842842
} // implicitly tested: node does not leak
843843

844+
#[itest]
845+
fn object_try_to_unique() {
846+
let a = RefCounted::new_gd();
847+
let id = a.instance_id();
848+
let a = a.try_to_unique().expect("a.try_to_unique()");
849+
assert_eq!(a.instance_id(), id);
850+
851+
let b = a.clone();
852+
let (b, err) = b.try_to_unique().expect_err("b.try_to_unique()");
853+
assert_eq!(b.instance_id(), id);
854+
assert!(matches!(err, NotUniqueError::Shared { ref_count: 2 }));
855+
856+
/* Re-enable if DynMemory is fixed.
857+
858+
let c = Object::new_alloc(); // manually managed
859+
let id = c.instance_id();
860+
let (c, err) = c.try_to_unique().expect_err("c.try_to_unique()");
861+
assert_eq!(c.instance_id(), id);
862+
assert!(matches!(err, NotUniqueError::NotRefCounted));
863+
c.free();
864+
*/
865+
}
866+
844867
// ----------------------------------------------------------------------------------------------------------------------------------------------
845868

846869
#[derive(GodotClass)]
@@ -1053,21 +1076,3 @@ fn double_use_reference() {
10531076
#[derive(GodotClass)]
10541077
#[class(no_init, base = EditorPlugin, editor_plugin, tool)]
10551078
struct CustomEditorPlugin;
1056-
1057-
// ----------------------------------------------------------------------------------------------------------------------------------------------
1058-
1059-
#[itest]
1060-
fn non_unique_error_works() {
1061-
use godot::classes::RefCounted;
1062-
use godot::obj::NotUniqueError;
1063-
1064-
let unique = RefCounted::new_gd();
1065-
assert!(NotUniqueError::check(unique).is_ok());
1066-
1067-
let shared = RefCounted::new_gd();
1068-
let cloned = shared.clone();
1069-
match NotUniqueError::check(cloned) {
1070-
Err(error) => assert_eq!(error.get_reference_count(), 2),
1071-
Ok(_) => panic!("NotUniqueError::check must not succeed"),
1072-
}
1073-
}

0 commit comments

Comments
 (0)