Skip to content

Commit 5d5132c

Browse files
bors[bot]TitanNanoBromeon
authored
Merge #212
212: Implement InstanceStorage optionally as sync for #18 r=Bromeon a=TitanNano This is a second implementation of `InstanceStorage` that is `Sync` and `Send` and should make it suitable for usage with rust threads. I hope it fits with the general plans for multithreading in #18. Feedback is more than welcome. Co-authored-by: Jovan Gerodetti <[email protected]> Co-authored-by: Jan Haller <[email protected]>
2 parents ca5e331 + 338779d commit 5d5132c

File tree

6 files changed

+217
-70
lines changed

6 files changed

+217
-70
lines changed

.github/workflows/full-ci.yml

+6
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,12 @@ jobs:
169169
godot-binary: godot.linuxbsd.editor.dev.double.x86_64
170170
rust-extra-args: --features double-precision
171171

172+
- name: linux-threads
173+
os: ubuntu-20.04
174+
artifact-name: linux
175+
godot-binary: godot.linuxbsd.editor.dev.x86_64
176+
rust-extra-args: --features threads
177+
172178
- name: linux-nightly
173179
os: ubuntu-20.04
174180
artifact-name: linux

godot-core/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ codegen-fmt = ["godot-ffi/codegen-fmt", "godot-codegen/codegen-fmt"]
1414
codegen-full = ["godot-codegen/codegen-full"]
1515
double-precision = ["godot-codegen/double-precision"]
1616
custom-godot = ["godot-ffi/custom-godot", "godot-codegen/custom-godot"]
17+
threads = []
1718

1819
[dependencies]
1920
godot-ffi = { path = "../godot-ffi" }

godot-core/src/obj/guards.rs

+23
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,35 @@
44
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
55
*/
66

7+
#[cfg(not(feature = "threads"))]
78
use std::cell;
89
use std::fmt::Debug;
910
use std::ops::{Deref, DerefMut};
11+
#[cfg(feature = "threads")]
12+
use std::sync;
1013

1114
/// Immutably/shared bound reference guard for a [`Gd`][crate::obj::Gd] smart pointer.
1215
///
1316
/// See [`Gd::bind`][crate::obj::Gd::bind] for usage.
1417
#[derive(Debug)]
1518
pub struct GdRef<'a, T> {
19+
#[cfg(not(feature = "threads"))]
1620
cell_ref: cell::Ref<'a, T>,
21+
22+
#[cfg(feature = "threads")]
23+
cell_ref: sync::RwLockReadGuard<'a, T>,
1724
}
1825

1926
impl<'a, T> GdRef<'a, T> {
27+
#[cfg(not(feature = "threads"))]
2028
pub(crate) fn from_cell(cell_ref: cell::Ref<'a, T>) -> Self {
2129
Self { cell_ref }
2230
}
31+
32+
#[cfg(feature = "threads")]
33+
pub(crate) fn from_cell(cell_ref: sync::RwLockReadGuard<'a, T>) -> Self {
34+
Self { cell_ref }
35+
}
2336
}
2437

2538
impl<T> Deref for GdRef<'_, T> {
@@ -39,13 +52,23 @@ impl<T> Deref for GdRef<'_, T> {
3952
/// See [`Gd::bind_mut`][crate::obj::Gd::bind_mut] for usage.
4053
#[derive(Debug)]
4154
pub struct GdMut<'a, T> {
55+
#[cfg(not(feature = "threads"))]
4256
cell_ref: cell::RefMut<'a, T>,
57+
58+
#[cfg(feature = "threads")]
59+
cell_ref: sync::RwLockWriteGuard<'a, T>,
4360
}
4461

4562
impl<'a, T> GdMut<'a, T> {
63+
#[cfg(not(feature = "threads"))]
4664
pub(crate) fn from_cell(cell_ref: cell::RefMut<'a, T>) -> Self {
4765
Self { cell_ref }
4866
}
67+
68+
#[cfg(feature = "threads")]
69+
pub(crate) fn from_cell(cell_ref: sync::RwLockWriteGuard<'a, T>) -> Self {
70+
Self { cell_ref }
71+
}
4972
}
5073

5174
impl<T> Deref for GdMut<'_, T> {

godot-core/src/storage.rs

+185-70
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,6 @@ use crate::out;
99
use godot_ffi as sys;
1010

1111
use std::any::type_name;
12-
use std::cell;
13-
14-
/// Manages storage and lifecycle of user's extension class instances.
15-
pub struct InstanceStorage<T: GodotClass> {
16-
user_instance: cell::RefCell<T>,
17-
18-
// Declared after `user_instance`, is dropped last
19-
pub lifecycle: Lifecycle,
20-
godot_ref_count: i32,
21-
}
2212

2313
#[derive(Copy, Clone, Debug)]
2414
pub enum Lifecycle {
@@ -27,76 +17,201 @@ pub enum Lifecycle {
2717
Dead, // reading this would typically already be too late, only best-effort in case of UB
2818
}
2919

30-
/// For all Godot extension classes
31-
impl<T: GodotClass> InstanceStorage<T> {
32-
pub fn construct(user_instance: T) -> Self {
33-
out!(" Storage::construct <{}>", type_name::<T>());
20+
#[cfg(not(feature = "threads"))]
21+
pub use single_thread::*;
3422

35-
Self {
36-
user_instance: cell::RefCell::new(user_instance),
37-
lifecycle: Lifecycle::Alive,
38-
godot_ref_count: 1,
39-
}
40-
}
23+
#[cfg(feature = "threads")]
24+
pub use multi_thread::*;
4125

42-
pub(crate) fn on_inc_ref(&mut self) {
43-
self.godot_ref_count += 1;
44-
out!(
45-
" Storage::on_inc_ref (rc={}) <{}>", // -- {:?}",
46-
self.godot_ref_count,
47-
type_name::<T>(),
48-
//self.user_instance
49-
);
26+
#[cfg(not(feature = "threads"))]
27+
mod single_thread {
28+
use std::any::type_name;
29+
use std::cell;
30+
31+
use crate::obj::GodotClass;
32+
use crate::out;
33+
34+
use super::Lifecycle;
35+
36+
/// Manages storage and lifecycle of user's extension class instances.
37+
pub struct InstanceStorage<T: GodotClass> {
38+
user_instance: cell::RefCell<T>,
39+
40+
// Declared after `user_instance`, is dropped last
41+
pub lifecycle: Lifecycle,
42+
godot_ref_count: u32,
5043
}
5144

52-
pub(crate) fn on_dec_ref(&mut self) {
53-
self.godot_ref_count -= 1;
54-
out!(
55-
" | Storage::on_dec_ref (rc={}) <{}>", // -- {:?}",
56-
self.godot_ref_count,
57-
type_name::<T>(),
58-
//self.user_instance
59-
);
45+
/// For all Godot extension classes
46+
impl<T: GodotClass> InstanceStorage<T> {
47+
pub fn construct(user_instance: T) -> Self {
48+
out!(" Storage::construct <{}>", type_name::<T>());
49+
50+
Self {
51+
user_instance: cell::RefCell::new(user_instance),
52+
lifecycle: Lifecycle::Alive,
53+
godot_ref_count: 1,
54+
}
55+
}
56+
57+
pub(crate) fn on_inc_ref(&mut self) {
58+
self.godot_ref_count += 1;
59+
out!(
60+
" Storage::on_inc_ref (rc={}) <{}>", // -- {:?}",
61+
self.godot_ref_count(),
62+
type_name::<T>(),
63+
//self.user_instance
64+
);
65+
}
66+
67+
pub(crate) fn on_dec_ref(&mut self) {
68+
self.godot_ref_count -= 1;
69+
out!(
70+
" | Storage::on_dec_ref (rc={}) <{}>", // -- {:?}",
71+
self.godot_ref_count(),
72+
type_name::<T>(),
73+
//self.user_instance
74+
);
75+
}
76+
77+
/* pub fn destroy(&mut self) {
78+
assert!(
79+
self.user_instance.is_some(),
80+
"Cannot destroy user instance which is not yet initialized"
81+
);
82+
assert!(
83+
!self.destroyed,
84+
"Cannot destroy user instance multiple times"
85+
);
86+
self.user_instance = None; // drops T
87+
// TODO drop entire Storage
88+
}*/
89+
90+
pub fn get(&self) -> cell::Ref<T> {
91+
self.user_instance.try_borrow().unwrap_or_else(|_e| {
92+
panic!(
93+
"Gd<T>::bind() failed, already bound; T = {}.\n \
94+
Make sure there is no &mut T live at the time.\n \
95+
This often occurs when calling a GDScript function/signal from Rust, which then calls again Rust code.",
96+
type_name::<T>()
97+
)
98+
})
99+
}
100+
101+
pub fn get_mut(&mut self) -> cell::RefMut<T> {
102+
self.user_instance.try_borrow_mut().unwrap_or_else(|_e| {
103+
panic!(
104+
"Gd<T>::bind_mut() failed, already bound; T = {}.\n \
105+
Make sure there is no &T or &mut T live at the time.\n \
106+
This often occurs when calling a GDScript function/signal from Rust, which then calls again Rust code.",
107+
type_name::<T>()
108+
)
109+
})
110+
}
111+
112+
pub(super) fn godot_ref_count(&self) -> u32 {
113+
self.godot_ref_count
114+
}
60115
}
116+
}
61117

62-
/* pub fn destroy(&mut self) {
63-
assert!(
64-
self.user_instance.is_some(),
65-
"Cannot destroy user instance which is not yet initialized"
66-
);
67-
assert!(
68-
!self.destroyed,
69-
"Cannot destroy user instance multiple times"
70-
);
71-
self.user_instance = None; // drops T
72-
// TODO drop entire Storage
73-
}*/
118+
#[cfg(feature = "threads")]
119+
mod multi_thread {
120+
use std::any::type_name;
121+
use std::sync;
122+
use std::sync::atomic::{AtomicU32, Ordering};
74123

75-
#[must_use]
76-
pub fn into_raw(self) -> *mut Self {
77-
Box::into_raw(Box::new(self))
124+
use crate::obj::GodotClass;
125+
use crate::out;
126+
127+
use super::Lifecycle;
128+
129+
/// Manages storage and lifecycle of user's extension class instances.
130+
pub struct InstanceStorage<T: GodotClass> {
131+
user_instance: sync::RwLock<T>,
132+
133+
// Declared after `user_instance`, is dropped last
134+
pub lifecycle: Lifecycle,
135+
godot_ref_count: AtomicU32,
78136
}
79137

80-
pub fn get(&self) -> cell::Ref<T> {
81-
self.user_instance.try_borrow().unwrap_or_else(|_e| {
82-
panic!(
83-
"Gd<T>::bind() failed, already bound; T = {}.\n \
84-
Make sure there is no &mut T live at the time.\n \
85-
This often occurs when calling a GDScript function/signal from Rust, which then calls again Rust code.",
86-
type_name::<T>()
87-
)
88-
})
138+
/// For all Godot extension classes
139+
impl<T: GodotClass> InstanceStorage<T> {
140+
pub fn construct(user_instance: T) -> Self {
141+
out!(" Storage::construct <{}>", type_name::<T>());
142+
143+
Self {
144+
user_instance: sync::RwLock::new(user_instance),
145+
lifecycle: Lifecycle::Alive,
146+
godot_ref_count: AtomicU32::new(1),
147+
}
148+
}
149+
150+
pub(crate) fn on_inc_ref(&mut self) {
151+
self.godot_ref_count.fetch_add(1, Ordering::Relaxed);
152+
out!(
153+
" Storage::on_inc_ref (rc={}) <{}>", // -- {:?}",
154+
self.godot_ref_count(),
155+
type_name::<T>(),
156+
//self.user_instance
157+
);
158+
}
159+
160+
pub(crate) fn on_dec_ref(&mut self) {
161+
self.godot_ref_count.fetch_sub(1, Ordering::Relaxed);
162+
out!(
163+
" | Storage::on_dec_ref (rc={}) <{}>", // -- {:?}",
164+
self.godot_ref_count(),
165+
type_name::<T>(),
166+
//self.user_instance
167+
);
168+
}
169+
170+
/* pub fn destroy(&mut self) {
171+
assert!(
172+
self.user_instance.is_some(),
173+
"Cannot destroy user instance which is not yet initialized"
174+
);
175+
assert!(
176+
!self.destroyed,
177+
"Cannot destroy user instance multiple times"
178+
);
179+
self.user_instance = None; // drops T
180+
// TODO drop entire Storage
181+
}*/
182+
183+
pub fn get(&self) -> sync::RwLockReadGuard<T> {
184+
self.user_instance.read().unwrap_or_else(|_e| {
185+
panic!(
186+
"Gd<T>::bind() failed, already bound; T = {}.\n \
187+
Make sure there is no &mut T live at the time.\n \
188+
This often occurs when calling a GDScript function/signal from Rust, which then calls again Rust code.",
189+
type_name::<T>()
190+
)
191+
})
192+
}
193+
194+
pub fn get_mut(&mut self) -> sync::RwLockWriteGuard<T> {
195+
self.user_instance.write().unwrap_or_else(|_e| {
196+
panic!(
197+
"Gd<T>::bind_mut() failed, already bound; T = {}.\n \
198+
Make sure there is no &T or &mut T live at the time.\n \
199+
This often occurs when calling a GDScript function/signal from Rust, which then calls again Rust code.",
200+
type_name::<T>()
201+
)
202+
})
203+
}
204+
205+
pub(super) fn godot_ref_count(&self) -> u32 {
206+
self.godot_ref_count.load(Ordering::Relaxed)
207+
}
89208
}
209+
}
90210

91-
pub fn get_mut(&mut self) -> cell::RefMut<T> {
92-
self.user_instance.try_borrow_mut().unwrap_or_else(|_e| {
93-
panic!(
94-
"Gd<T>::bind_mut() failed, already bound; T = {}.\n \
95-
Make sure there is no &T or &mut T live at the time.\n \
96-
This often occurs when calling a GDScript function/signal from Rust, which then calls again Rust code.",
97-
type_name::<T>()
98-
)
99-
})
211+
impl<T: GodotClass> InstanceStorage<T> {
212+
#[must_use]
213+
pub fn into_raw(self) -> *mut Self {
214+
Box::into_raw(Box::new(self))
100215
}
101216

102217
pub fn mark_destroyed_by_godot(&mut self) {
@@ -127,7 +242,7 @@ impl<T: GodotClass> Drop for InstanceStorage<T> {
127242
fn drop(&mut self) {
128243
out!(
129244
" Storage::drop (rc={}) <{}>", // -- {:?}",
130-
self.godot_ref_count,
245+
self.godot_ref_count(),
131246
type_name::<T>(),
132247
//self.user_instance
133248
);

godot/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ default = ["codegen-full"]
1212
formatted = ["godot-core/codegen-fmt"]
1313
double-precision = ["godot-core/double-precision"]
1414
custom-godot = ["godot-core/custom-godot"]
15+
threads = ["godot-core/threads"]
1516

1617
# Private features, they are under no stability guarantee
1718
codegen-full = ["godot-core/codegen-full"]

itest/rust/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ crate-type = ["cdylib"]
1212
default = []
1313
trace = ["godot/trace"]
1414
double-precision = ["godot/double-precision"]
15+
threads = ["godot/threads"]
1516

1617
[dependencies]
1718
godot = { path = "../../godot", default-features = false, features = ["formatted"] }

0 commit comments

Comments
 (0)