Skip to content

Commit fdf5ae0

Browse files
committed
Add UnsafeOnceCell
Make binding use `UnsafeOnceCell` Remove most code duplication
1 parent 157f8e0 commit fdf5ae0

File tree

5 files changed

+475
-362
lines changed

5 files changed

+475
-362
lines changed

godot-ffi/src/binding/mod.rs

Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
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::{
9+
BuiltinLifecycleTable, BuiltinMethodTable, ClassEditorMethodTable, ClassSceneMethodTable,
10+
ClassServersMethodTable, GDExtensionClassLibraryPtr, GDExtensionInterface,
11+
GdextRuntimeMetadata, UnsafeOnceCell, UtilityFunctionTable,
12+
};
13+
14+
#[cfg(feature = "experimental-threads")]
15+
mod multi_threaded;
16+
#[cfg(not(feature = "experimental-threads"))]
17+
mod single_threaded;
18+
19+
#[cfg(feature = "experimental-threads")]
20+
use multi_threaded::BindingStorage;
21+
#[cfg(not(feature = "experimental-threads"))]
22+
use single_threaded::BindingStorage;
23+
24+
#[cfg(feature = "experimental-threads")]
25+
pub use multi_threaded::GdextConfig;
26+
#[cfg(not(feature = "experimental-threads"))]
27+
pub use single_threaded::GdextConfig;
28+
29+
// Note, this is `Sync` and `Send` when "experimental-threads" is enabled because all its fields are. We have avoided implementing `Sync`
30+
// and `Send` for `GodotBinding` as that could hide issues if any of the field types are changed to no longer be sync/send, but the manual
31+
// implementation for `GodotBinding` wouldn't detect that.
32+
pub(crate) struct GodotBinding {
33+
interface: GDExtensionInterface,
34+
library: ClassLibraryPtr,
35+
global_method_table: BuiltinLifecycleTable,
36+
class_server_method_table: UnsafeOnceCell<ClassServersMethodTable>,
37+
class_scene_method_table: UnsafeOnceCell<ClassSceneMethodTable>,
38+
class_editor_method_table: UnsafeOnceCell<ClassEditorMethodTable>,
39+
builtin_method_table: UnsafeOnceCell<BuiltinMethodTable>,
40+
utility_function_table: UtilityFunctionTable,
41+
runtime_metadata: GdextRuntimeMetadata,
42+
config: GdextConfig,
43+
}
44+
45+
impl GodotBinding {
46+
pub fn new(
47+
interface: GDExtensionInterface,
48+
library: GDExtensionClassLibraryPtr,
49+
global_method_table: BuiltinLifecycleTable,
50+
utility_function_table: UtilityFunctionTable,
51+
runtime_metadata: GdextRuntimeMetadata,
52+
config: GdextConfig,
53+
) -> Self {
54+
Self {
55+
interface,
56+
library: ClassLibraryPtr(library),
57+
global_method_table,
58+
class_server_method_table: UnsafeOnceCell::new(),
59+
class_scene_method_table: UnsafeOnceCell::new(),
60+
class_editor_method_table: UnsafeOnceCell::new(),
61+
builtin_method_table: UnsafeOnceCell::new(),
62+
utility_function_table,
63+
runtime_metadata,
64+
config,
65+
}
66+
}
67+
}
68+
69+
/// Newtype around `GDExtensionClassLibraryPtr` so we can implement `Sync` and `Send` manually for this.
70+
struct ClassLibraryPtr(crate::GDExtensionClassLibraryPtr);
71+
72+
// SAFETY: This implementation of `Sync` and `Send` does not guarantee that reading from or writing to the pointer is actually
73+
// thread safe. It merely means we can send/share the pointer itself between threads. Which is safe since any place that actually
74+
// reads/writes to this pointer must ensure they do so in a thread safe manner.
75+
//
76+
// So these implementations effectively just pass the responsibility for thread safe usage of the library pointer onto whomever
77+
// reads/writes to the pointer from a different thread. Since doing so requires `unsafe` anyway this is something we can do soundly.
78+
unsafe impl Sync for ClassLibraryPtr {}
79+
// SAFETY: See `Sync` impl safety doc.
80+
unsafe impl Send for ClassLibraryPtr {}
81+
82+
/// Initializes the Godot binding.
83+
///
84+
/// Most other functions in this module rely on this function being called first as a safety condition.
85+
///
86+
/// # Safety
87+
///
88+
/// Must not be called concurrently with other functions that interact with the bindings - this is trivially true if "experimental-threads"
89+
/// is not enabled.
90+
///
91+
/// If "experimental-threads" is enabled, then must be called from the main thread.
92+
pub(crate) unsafe fn initialize_binding(binding: GodotBinding) {
93+
unsafe {
94+
BindingStorage::initialize(binding);
95+
}
96+
}
97+
98+
/// # Safety
99+
///
100+
/// The Godot binding must have been initialized before calling this function.
101+
///
102+
/// If "experimental-threads" is not enabled, then this must be called from the same thread that the bindings were initialized from.
103+
#[inline(always)]
104+
pub(crate) unsafe fn get_binding() -> &'static GodotBinding {
105+
BindingStorage::get_binding_unchecked()
106+
}
107+
108+
/// # Safety
109+
///
110+
/// - The Godot binding must have been initialized before calling this function.
111+
/// - Must only be called once.
112+
///
113+
/// If "experimental-threads" is not enabled, then this must be called from the same thread that the bindings were initialized from.
114+
pub(crate) unsafe fn initialize_class_server_method_table(table: ClassServersMethodTable) {
115+
// SAFETY: `get_binding` has the same preconditions as this function.
116+
let binding = unsafe { get_binding() };
117+
118+
debug_assert!(
119+
!binding.class_editor_method_table.is_initialized(),
120+
"server method table should only be initialized once"
121+
);
122+
123+
// SAFETY: Is only called once, and is called before any accesses to this table.
124+
unsafe { binding.class_server_method_table.set(table) }
125+
}
126+
127+
/// # Safety
128+
///
129+
/// - The Godot binding must have been initialized before calling this function.
130+
/// - Must only be called once.
131+
///
132+
/// If "experimental-threads" is not enabled, then this must be called from the same thread that the bindings were initialized from.
133+
pub(crate) unsafe fn initialize_class_scene_method_table(table: ClassSceneMethodTable) {
134+
// SAFETY: `get_binding` has the same preconditions as this function.
135+
let binding = unsafe { get_binding() };
136+
137+
debug_assert!(
138+
!binding.class_scene_method_table.is_initialized(),
139+
"scene method table should only be initialized once"
140+
);
141+
142+
// SAFETY: Is only called once, and is called before any accesses to this table.
143+
unsafe { binding.class_scene_method_table.set(table) }
144+
}
145+
146+
/// # Safety
147+
///
148+
/// - The Godot binding must have been initialized before calling this function.
149+
/// - Must only be called once.
150+
///
151+
/// If "experimental-threads" is not enabled, then this must be called from the same thread that the bindings were initialized from.
152+
pub(crate) unsafe fn initialize_class_editor_method_table(table: ClassEditorMethodTable) {
153+
// SAFETY: `get_binding` has the same preconditions as this function.
154+
let binding = unsafe { get_binding() };
155+
156+
debug_assert!(
157+
!binding.class_editor_method_table.is_initialized(),
158+
"editor method table should only be initialized once"
159+
);
160+
161+
// SAFETY: Is only called once, and is called before any accesses to this table.
162+
unsafe { binding.class_editor_method_table.set(table) }
163+
}
164+
165+
/// # Safety
166+
///
167+
/// - The Godot binding must have been initialized before calling this function.
168+
/// - Must only be called once.
169+
///
170+
/// If "experimental-threads" is not enabled, then this must be called from the same thread that the bindings were initialized from.
171+
pub(crate) unsafe fn initialize_builtin_method_table(table: BuiltinMethodTable) {
172+
// SAFETY: `get_binding` has the same preconditions as this function.
173+
let binding = unsafe { get_binding() };
174+
175+
debug_assert!(
176+
!binding.builtin_method_table.is_initialized(),
177+
"builtin method table should only be initialized once"
178+
);
179+
180+
// SAFETY: Is only called once, and is called before any accesses to this table.
181+
unsafe { binding.builtin_method_table.set(table) }
182+
}
183+
184+
/// # Safety
185+
///
186+
/// The Godot binding must have been initialized before calling this function.
187+
///
188+
/// If "experimental-threads" is not enabled, then this must be called from the same thread that the bindings were initialized from.
189+
#[inline(always)]
190+
pub unsafe fn get_interface() -> &'static GDExtensionInterface {
191+
&get_binding().interface
192+
}
193+
194+
/// # Safety
195+
///
196+
/// The Godot binding must have been initialized before calling this function.
197+
///
198+
/// If "experimental-threads" is not enabled, then this must be called from the same thread that the bindings were initialized from.
199+
#[inline(always)]
200+
pub unsafe fn get_library() -> crate::GDExtensionClassLibraryPtr {
201+
get_binding().library.0
202+
}
203+
204+
/// # Safety
205+
///
206+
/// The Godot binding must have been initialized before calling this function.
207+
///
208+
/// If "experimental-threads" is not enabled, then this must be called from the same thread that the bindings were initialized from.
209+
#[inline(always)]
210+
pub unsafe fn builtin_lifecycle_api() -> &'static BuiltinLifecycleTable {
211+
&get_binding().global_method_table
212+
}
213+
214+
/// # Safety
215+
///
216+
/// - The Godot binding must have been initialized before calling this function.
217+
/// - The class servers method table must have been initialized before calling this function.
218+
///
219+
/// If "experimental-threads" is not enabled, then this must be called from the same thread that the bindings were initialized from.
220+
#[inline(always)]
221+
pub unsafe fn class_servers_api() -> &'static ClassServersMethodTable {
222+
// SAFETY: `get_binding` has the same preconditions as this function.
223+
let binding = unsafe { get_binding() };
224+
225+
debug_assert!(
226+
binding.class_server_method_table.is_initialized(),
227+
"cannot fetch classes; init level 'Servers' not yet loaded"
228+
);
229+
230+
// SAFETY: `initialize_class_server_method_table` has been called.
231+
unsafe { binding.class_server_method_table.get_unchecked() }
232+
}
233+
234+
/// # Safety
235+
///
236+
/// - The Godot binding must have been initialized before calling this function.
237+
/// - The class scene method table must have been initialized before calling this function.
238+
///
239+
/// If "experimental-threads" is not enabled, then this must be called from the same thread that the bindings were initialized from.
240+
#[inline(always)]
241+
pub unsafe fn class_scene_api() -> &'static ClassSceneMethodTable {
242+
// SAFETY: `get_binding` has the same preconditions as this function.
243+
let binding = unsafe { get_binding() };
244+
245+
debug_assert!(
246+
binding.class_scene_method_table.is_initialized(),
247+
"cannot fetch classes; init level 'Scene' not yet loaded"
248+
);
249+
250+
// SAFETY: `initialize_class_scene_method_table` has been called.
251+
unsafe { binding.class_scene_method_table.get_unchecked() }
252+
}
253+
254+
/// # Safety
255+
///
256+
/// - The Godot binding must have been initialized before calling this function.
257+
/// - The class editor method table must have been initialized before calling this function.
258+
///
259+
/// If "experimental-threads" is not enabled, then this must be called from the same thread that the bindings were initialized from.
260+
#[inline(always)]
261+
pub unsafe fn class_editor_api() -> &'static ClassEditorMethodTable {
262+
// SAFETY: `get_binding` has the same preconditions as this function.
263+
let binding = unsafe { get_binding() };
264+
265+
debug_assert!(
266+
binding.class_editor_method_table.is_initialized(),
267+
"cannot fetch classes; init level 'Editor' not yet loaded"
268+
);
269+
270+
// SAFETY: `initialize_class_editor_method_table` has been called.
271+
unsafe { binding.class_editor_method_table.get_unchecked() }
272+
}
273+
274+
/// # Safety
275+
///
276+
/// - The Godot binding must have been initialized before calling this function.
277+
/// - The builtin method table must have been initialized before calling this function.
278+
///
279+
/// If "experimental-threads" is not enabled, then this must be called from the same thread that the bindings were initialized from.
280+
#[inline(always)]
281+
pub unsafe fn builtin_method_table() -> &'static BuiltinMethodTable {
282+
// SAFETY: `get_binding` has the same preconditions as this function.
283+
let binding = unsafe { get_binding() };
284+
285+
debug_assert!(binding.builtin_method_table.is_initialized());
286+
287+
// SAFETY: `initialize_builtin_method_table` has been called.
288+
unsafe { binding.builtin_method_table.get_unchecked() }
289+
}
290+
291+
/// # Safety
292+
///
293+
/// The Godot binding must have been initialized before calling this function.
294+
///
295+
/// If "experimental-threads" is not enabled, then this must be called from the same thread that the bindings were initialized from.
296+
#[inline(always)]
297+
pub unsafe fn utility_function_table() -> &'static UtilityFunctionTable {
298+
&get_binding().utility_function_table
299+
}
300+
301+
/// # Safety
302+
///
303+
/// The Godot binding must have been initialized before calling this function.
304+
///
305+
/// If "experimental-threads" is not enabled, then this must be called from the same thread that the bindings were initialized from.
306+
#[inline(always)]
307+
pub(crate) unsafe fn runtime_metadata() -> &'static GdextRuntimeMetadata {
308+
&get_binding().runtime_metadata
309+
}
310+
311+
/// # Safety
312+
///
313+
/// The Godot binding must have been initialized before calling this function.
314+
///
315+
/// If "experimental-threads" is not enabled, then this must be called from the same thread that the bindings were initialized from.
316+
#[inline]
317+
pub unsafe fn config() -> &'static GdextConfig {
318+
&get_binding().config
319+
}
320+
321+
#[inline]
322+
pub fn is_initialized() -> bool {
323+
BindingStorage::is_initialized()
324+
}

0 commit comments

Comments
 (0)