Skip to content

Commit 6aa8e55

Browse files
committed
Implement support for RID
Add `RenderingServer` to minimal classes, to be able to test the RID impl against an actual server Add `Engine` to minimal classes, to be able to disable error printing in itests
1 parent 54cb201 commit 6aa8e55

File tree

7 files changed

+275
-2
lines changed

7 files changed

+275
-2
lines changed

godot-codegen/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ const SELECTED_CLASSES: &[&str] = &[
268268
"CollisionObject2D",
269269
"CollisionShape2D",
270270
"Control",
271+
"Engine",
271272
"FileAccess",
272273
"HTTPRequest",
273274
"Image",
@@ -286,6 +287,7 @@ const SELECTED_CLASSES: &[&str] = &[
286287
"PathFollow2D",
287288
"PhysicsBody2D",
288289
"RefCounted",
290+
"RenderingServer",
289291
"Resource",
290292
"ResourceLoader",
291293
"RigidBody2D",

godot-core/src/builtin/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ pub use others::*;
4545
pub use packed_array::*;
4646
pub use projection::*;
4747
pub use quaternion::*;
48+
pub use rid::*;
4849
pub use string::*;
4950
pub use string_name::*;
5051
pub use transform2d::*;
@@ -92,6 +93,7 @@ mod others;
9293
mod packed_array;
9394
mod projection;
9495
mod quaternion;
96+
mod rid;
9597
mod string;
9698
mod string_name;
9799
mod transform2d;

godot-core/src/builtin/others.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ impl_builtin_stub!(Rect2, OpaqueRect2);
1717
impl_builtin_stub!(Rect2i, OpaqueRect2i);
1818
impl_builtin_stub!(Plane, OpaquePlane);
1919
impl_builtin_stub!(Aabb, OpaqueAabb);
20-
impl_builtin_stub!(Rid, OpaqueRid);
2120
impl_builtin_stub!(Callable, OpaqueCallable);
2221
impl_builtin_stub!(Signal, OpaqueSignal);
2322

godot-core/src/builtin/rid.rs

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/*
2+
* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this
4+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
5+
*/
6+
7+
use std::num::NonZeroU64;
8+
9+
use godot_ffi as sys;
10+
use sys::{ffi_methods, GodotFfi};
11+
12+
/// A RID ("resource ID") is an opaque handle that refers to a Godot `Resource`.
13+
///
14+
/// RIDs do not grant access to the resource itself. Instead, they can be used in lower-level resource APIs
15+
/// such as the [servers]. See also [Godot API docs for `RID`][docs].
16+
///
17+
/// RIDs should be largely safe to work with. Certain calls to servers may fail, however doing so will
18+
/// trigger an error from Godot, and will not cause any UB.
19+
///
20+
/// # Layout:
21+
///
22+
/// `Rid` has the same layout as a [`u64`], where [`Rid::Invalid`] is 0, and [`Rid::Valid(i)`] is `i`.
23+
///
24+
/// # Safety Caveat:
25+
///
26+
/// In Godot 3, RID was not as safe as described here. We believe that this is fixed in Godot 4, but this has
27+
/// not been extensively tested as of yet. Some confirmed UB from Godot 3 does not occur anymore, but if you
28+
/// find anything suspicious or outright UB please open an issue.
29+
///
30+
/// [servers]: https://docs.godotengine.org/en/stable/tutorials/optimization/using_servers.html
31+
/// [docs]: https://docs.godotengine.org/en/stable/classes/class_rid.html
32+
33+
// Using normal rust repr to take advantage advantage of the nullable pointer optimization. As this enum is
34+
// eligible for it, it is also guaranteed to have it. Meaning the layout of this type is identical to `u64`.
35+
// See: https://doc.rust-lang.org/nomicon/ffi.html#the-nullable-pointer-optimization
36+
// Cannot use `#[repr(C)]` as it does not use the nullable pointer optimization.
37+
#[derive(Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Debug)]
38+
pub enum Rid {
39+
/// A valid RID may refer to some resource, but is not guaranteed to do so.
40+
Valid(NonZeroU64),
41+
/// An invalid RID will never refer to a resource. Internally it is represented as a 0.
42+
Invalid,
43+
}
44+
45+
impl Rid {
46+
/// Create a new RID.
47+
#[inline]
48+
pub const fn new(id: u64) -> Self {
49+
match NonZeroU64::new(id) {
50+
Some(id) => Self::Valid(id),
51+
None => Self::Invalid,
52+
}
53+
}
54+
55+
/// Convert this RID into a [`u64`].
56+
///
57+
/// _Godot equivalent: `Rid.get_id()`_
58+
#[inline]
59+
pub const fn to_u64(self) -> u64 {
60+
match self {
61+
Rid::Valid(id) => id.get(),
62+
Rid::Invalid => 0,
63+
}
64+
}
65+
66+
/// Convert this RID into a [`NonZeroU64`].
67+
#[inline]
68+
pub const fn to_non_zero_u64(self) -> Option<NonZeroU64> {
69+
match self {
70+
Rid::Valid(id) => Some(id),
71+
Rid::Invalid => None,
72+
}
73+
}
74+
75+
/// Returns `true` if this is a valid RID.
76+
#[inline]
77+
pub const fn is_valid(&self) -> bool {
78+
matches!(self, Rid::Valid(_))
79+
}
80+
81+
/// Returns `true` if this is an invalid RID.
82+
#[inline]
83+
pub const fn is_invalid(&self) -> bool {
84+
matches!(self, Rid::Invalid)
85+
}
86+
}
87+
88+
impl From<u64> for Rid {
89+
#[inline]
90+
fn from(id: u64) -> Self {
91+
Self::new(id)
92+
}
93+
}
94+
95+
impl From<NonZeroU64> for Rid {
96+
#[inline]
97+
fn from(id: NonZeroU64) -> Self {
98+
Self::Valid(id)
99+
}
100+
}
101+
102+
impl From<Rid> for u64 {
103+
#[inline]
104+
fn from(rid: Rid) -> Self {
105+
rid.to_u64()
106+
}
107+
}
108+
109+
impl TryFrom<Rid> for NonZeroU64 {
110+
type Error = ();
111+
112+
#[inline]
113+
fn try_from(rid: Rid) -> Result<Self, Self::Error> {
114+
rid.to_non_zero_u64().ok_or(())
115+
}
116+
}
117+
118+
impl From<Rid> for Option<NonZeroU64> {
119+
#[inline]
120+
fn from(rid: Rid) -> Self {
121+
match rid {
122+
Rid::Invalid => None,
123+
Rid::Valid(id) => Some(id),
124+
}
125+
}
126+
}
127+
128+
impl From<Option<NonZeroU64>> for Rid {
129+
#[inline]
130+
fn from(id: Option<NonZeroU64>) -> Self {
131+
match id {
132+
Some(id) => Rid::Valid(id),
133+
None => Rid::Invalid,
134+
}
135+
}
136+
}
137+
138+
impl GodotFfi for Rid {
139+
ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. }
140+
}

godot-core/src/builtin/variant/impls.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,6 @@ mod impls {
159159
impl_variant_metadata!(Rect2i, /* rect2i_to_variant, rect2i_from_variant, */ Rect2i);
160160
impl_variant_metadata!(Plane, /* plane_to_variant, plane_from_variant, */ Plane);
161161
impl_variant_metadata!(Aabb, /* aabb_to_variant, aabb_from_variant, */ Aabb);
162-
impl_variant_metadata!(Rid, /* rid_to_variant, rid_from_variant, */ Rid);
163162
impl_variant_metadata!(Callable, /* callable_to_variant, callable_from_variant, */ Callable);
164163
impl_variant_metadata!(Signal, /* signal_to_variant, signal_from_variant, */ Signal);
165164
impl_variant_traits!(PackedByteArray, packed_byte_array_to_variant, packed_byte_array_from_variant, PackedByteArray);
@@ -172,6 +171,7 @@ mod impls {
172171
impl_variant_traits!(PackedVector3Array, packed_vector3_array_to_variant, packed_vector3_array_from_variant, PackedVector3Array);
173172
impl_variant_traits!(PackedColorArray, packed_color_array_to_variant, packed_color_array_from_variant, PackedColorArray);
174173
impl_variant_traits!(Projection, projection_to_variant, projection_from_variant, Projection);
174+
impl_variant_traits!(Rid, rid_to_variant, rid_from_variant, Rid);
175175
impl_variant_traits!(Transform2D, transform_2d_to_variant, transform_2d_from_variant, Transform2D);
176176
impl_variant_traits!(Transform3D, transform_3d_to_variant, transform_3d_from_variant, Transform3D);
177177
impl_variant_traits!(Dictionary, dictionary_to_variant, dictionary_from_variant, Dictionary);

itest/rust/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ mod node_test;
2323
mod object_test;
2424
mod packed_array_test;
2525
mod quaternion_test;
26+
mod rid_test;
2627
mod singleton_test;
2728
mod string_test;
2829
mod transform2d_test;

itest/rust/src/rid_test.rs

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this
4+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
5+
*/
6+
7+
use std::{collections::HashSet, thread};
8+
9+
use godot::{
10+
engine::{Engine, RenderingServer},
11+
prelude::{inner::InnerRid, Color, Rid, Vector2},
12+
};
13+
14+
use crate::itest;
15+
16+
/// Disable printing errors from Godot. Ideally we should catch and handle errors, ensuring they happen.
17+
/// But that isn't possible, so for now we just disable printing the error to avoid spamming the terminal.
18+
fn should_error(mut f: impl FnMut()) {
19+
Engine::singleton().set_print_error_messages(false);
20+
f();
21+
Engine::singleton().set_print_error_messages(true);
22+
}
23+
24+
#[itest]
25+
fn rid_equiv() {
26+
let invalid: Rid = Rid::Invalid;
27+
let valid: Rid = Rid::new((10 << 32) | 20);
28+
assert!(!InnerRid::from_outer(&invalid).is_valid());
29+
assert!(InnerRid::from_outer(&valid).is_valid());
30+
31+
assert_eq!(InnerRid::from_outer(&invalid).get_id(), 0);
32+
assert_eq!(InnerRid::from_outer(&valid).get_id(), (10 << 32) | 20);
33+
}
34+
35+
#[itest]
36+
fn canvas_set_parent() {
37+
// This originally caused UB, but still testing it here in case it breaks.
38+
let mut server = RenderingServer::singleton();
39+
let canvas = server.canvas_create();
40+
let viewport = server.viewport_create();
41+
42+
should_error(|| server.canvas_item_set_parent(viewport, canvas));
43+
should_error(|| server.canvas_item_set_parent(viewport, viewport));
44+
45+
server.free_rid(canvas);
46+
server.free_rid(viewport);
47+
}
48+
49+
#[itest]
50+
fn multi_thread_test() {
51+
let threads = (0..10)
52+
.map(|_| {
53+
thread::spawn(|| {
54+
let mut server = RenderingServer::singleton();
55+
let mut v = vec![];
56+
for _ in 0..1000 {
57+
v.push(server.canvas_item_create());
58+
}
59+
v
60+
})
61+
})
62+
.collect::<Vec<_>>();
63+
64+
let mut rids: Vec<Rid> = vec![];
65+
66+
for thread in threads.into_iter() {
67+
rids.append(&mut thread.join().unwrap());
68+
}
69+
70+
let set = rids.iter().cloned().collect::<HashSet<_>>();
71+
assert_eq!(set.len(), rids.len());
72+
73+
let mut server = RenderingServer::singleton();
74+
75+
for rid in rids.iter() {
76+
server.canvas_item_add_circle(*rid, Vector2::ZERO, 1.0, Color::from_rgb(1.0, 0.0, 0.0));
77+
}
78+
79+
for rid in rids.iter() {
80+
server.free_rid(*rid);
81+
}
82+
}
83+
84+
#[itest]
85+
fn strange_rids() {
86+
let mut server = RenderingServer::singleton();
87+
let mut rids: Vec<u64> = vec![
88+
0,
89+
1,
90+
10,
91+
u8::MAX as u64,
92+
u16::MAX as u64,
93+
u32::MAX as u64,
94+
u64::MAX,
95+
i8::MIN as u64,
96+
i8::MAX as u64,
97+
i16::MIN as u64,
98+
i16::MAX as u64,
99+
i32::MIN as u64,
100+
i32::MAX as u64,
101+
i64::MIN as u64,
102+
i64::MAX as u64,
103+
0xFFFFFFFF << 32,
104+
0x7FFFFFFF << 32,
105+
u32::MAX as u64 + 1,
106+
u32::MAX as u64 + 2,
107+
u32::MAX as u64 - 1,
108+
u32::MAX as u64 - 2,
109+
1234567891011121314,
110+
14930753991246632225,
111+
8079365198791785081,
112+
10737267678893224303,
113+
12442588258967011829,
114+
4275912429544145425,
115+
];
116+
for i in 0..64 {
117+
for j in 0..63 {
118+
if j >= i {
119+
rids.push((1 << i) | (1 << (j + 1)))
120+
} else {
121+
rids.push((1 << i) | (1 << j))
122+
}
123+
}
124+
}
125+
126+
for id in rids.iter() {
127+
should_error(|| server.canvas_item_clear((*id).into()))
128+
}
129+
}

0 commit comments

Comments
 (0)