godot-ffi 0.5.4

Internal crate used by godot-rust
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
/*
 * Copyright (c) godot-rust; Bromeon and contributors.
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

use crate::{
    BuiltinLifecycleTable, BuiltinMethodTable, ClassCoreMethodTable, ClassEditorMethodTable,
    ClassSceneMethodTable, ClassServersMethodTable, GDExtensionClassLibraryPtr,
    GDExtensionConstTypePtr, GDExtensionInterface, GDExtensionTypePtr,
    GDExtensionUninitializedTypePtr, GDExtensionUninitializedVariantPtr, GDExtensionVariantPtr,
    GdextRuntimeMetadata, ManualInitCell, UtilityFunctionTable,
};

#[cfg(feature = "experimental-threads")] #[cfg_attr(published_docs, doc(cfg(feature = "experimental-threads")))]
mod multi_threaded;
#[cfg(not(feature = "experimental-threads"))] #[cfg_attr(published_docs, doc(cfg(not(feature = "experimental-threads"))))]
mod single_threaded;

#[cfg(feature = "experimental-threads")] #[cfg_attr(published_docs, doc(cfg(feature = "experimental-threads")))]
use multi_threaded::BindingStorage;
// ----------------------------------------------------------------------------------------------------------------------------------------------
// Public re-exports
#[cfg(feature = "experimental-threads")] #[cfg_attr(published_docs, doc(cfg(feature = "experimental-threads")))]
pub use multi_threaded::GdextConfig;
#[cfg(not(feature = "experimental-threads"))] #[cfg_attr(published_docs, doc(cfg(not(feature = "experimental-threads"))))]
use single_threaded::BindingStorage;
#[cfg(not(feature = "experimental-threads"))] #[cfg_attr(published_docs, doc(cfg(not(feature = "experimental-threads"))))]
pub use single_threaded::GdextConfig;

// Note, this is `Sync` and `Send` when "experimental-threads" is enabled because all its fields are. We have avoided implementing `Sync`
// 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
// implementation for `GodotBinding` wouldn't detect that.
pub(crate) struct GodotBinding {
    interface: GDExtensionInterface,
    get_proc_address: crate::GDExtensionInterfaceGetProcAddress,
    library: ClassLibraryPtr,
    global_method_table: BuiltinLifecycleTable,
    class_core_method_table: ManualInitCell<ClassCoreMethodTable>,
    class_server_method_table: ManualInitCell<ClassServersMethodTable>,
    class_scene_method_table: ManualInitCell<ClassSceneMethodTable>,
    class_editor_method_table: ManualInitCell<ClassEditorMethodTable>,
    builtin_method_table: ManualInitCell<BuiltinMethodTable>,
    utility_function_table: UtilityFunctionTable,
    runtime_metadata: GdextRuntimeMetadata,
    config: GdextConfig,
    thread_safe_lifecycle: ThreadSafeLifecycle,
}

// The reviewed thread-safe subset of `BuiltinLifecycleTable`. Field names must match the table 1:1; listed once here, so the struct
// declaration and `from_table` copy cannot drift. Each entry being a separate field is the guardrail: macros can only reach reviewed functions.
macro_rules! thread_safe_lifecycle {
    ($( $field:ident: $sig:ty ),* $(,)?) => {
        #[derive(Copy, Clone)]
        pub struct ThreadSafeLifecycle {
            $( pub $field: $sig, )*
        }

        impl ThreadSafeLifecycle {
            /// Picks the reviewed thread-safe subset out of the full lifecycle table. Built once at binding init, handed out by reference.
            fn from_table(table: &BuiltinLifecycleTable) -> Self {
                Self { $( $field: table.$field, )* }
            }
        }
    };
}

thread_safe_lifecycle! {
    string_construct_default: unsafe extern "C" fn(GDExtensionUninitializedTypePtr, *const GDExtensionConstTypePtr),
    string_construct_copy: unsafe extern "C" fn(GDExtensionUninitializedTypePtr, *const GDExtensionConstTypePtr),
    string_destroy: unsafe extern "C" fn(GDExtensionTypePtr),
    string_operator_equal: unsafe extern "C" fn(GDExtensionConstTypePtr, GDExtensionConstTypePtr, GDExtensionTypePtr),
    string_operator_less: unsafe extern "C" fn(GDExtensionConstTypePtr, GDExtensionConstTypePtr, GDExtensionTypePtr),
    string_from_string_name: unsafe extern "C" fn(GDExtensionUninitializedTypePtr, *const GDExtensionConstTypePtr),
    string_from_node_path: unsafe extern "C" fn(GDExtensionUninitializedTypePtr, *const GDExtensionConstTypePtr),
    string_to_variant: unsafe extern "C" fn(GDExtensionUninitializedVariantPtr, GDExtensionTypePtr),
    string_from_variant: unsafe extern "C" fn(GDExtensionUninitializedTypePtr, GDExtensionVariantPtr),
    string_name_construct_default: unsafe extern "C" fn(GDExtensionUninitializedTypePtr, *const GDExtensionConstTypePtr),
    string_name_construct_copy: unsafe extern "C" fn(GDExtensionUninitializedTypePtr, *const GDExtensionConstTypePtr),
    string_name_destroy: unsafe extern "C" fn(GDExtensionTypePtr),
    string_name_operator_equal: unsafe extern "C" fn(GDExtensionConstTypePtr, GDExtensionConstTypePtr, GDExtensionTypePtr),
    string_name_operator_less: unsafe extern "C" fn(GDExtensionConstTypePtr, GDExtensionConstTypePtr, GDExtensionTypePtr),
    string_name_from_string: unsafe extern "C" fn(GDExtensionUninitializedTypePtr, *const GDExtensionConstTypePtr),
    string_name_to_variant: unsafe extern "C" fn(GDExtensionUninitializedVariantPtr, GDExtensionTypePtr),
    string_name_from_variant: unsafe extern "C" fn(GDExtensionUninitializedTypePtr, GDExtensionVariantPtr),
}

impl GodotBinding {
    pub fn new(
        interface: GDExtensionInterface,
        get_proc_address: crate::GDExtensionInterfaceGetProcAddress,
        library: GDExtensionClassLibraryPtr,
        global_method_table: BuiltinLifecycleTable,
        utility_function_table: UtilityFunctionTable,
        runtime_metadata: GdextRuntimeMetadata,
        config: GdextConfig,
    ) -> Self {
        let thread_safe_lifecycle = ThreadSafeLifecycle::from_table(&global_method_table);

        Self {
            interface,
            get_proc_address,
            library: ClassLibraryPtr(library),
            global_method_table,
            thread_safe_lifecycle,
            class_core_method_table: ManualInitCell::new(),
            class_server_method_table: ManualInitCell::new(),
            class_scene_method_table: ManualInitCell::new(),
            class_editor_method_table: ManualInitCell::new(),
            builtin_method_table: ManualInitCell::new(),
            utility_function_table,
            runtime_metadata,
            config,
        }
    }
}

/// Newtype around `GDExtensionClassLibraryPtr` so we can implement `Sync` and `Send` manually for this.
struct ClassLibraryPtr(crate::GDExtensionClassLibraryPtr);

// SAFETY: This implementation of `Sync` and `Send` does not guarantee that reading from or writing to the pointer is actually
// thread safe. It merely means we can send/share the pointer itself between threads. Which is safe since any place that actually
// reads/writes to this pointer must ensure they do so in a thread safe manner.
//
// So these implementations effectively just pass the responsibility for thread safe usage of the library pointer onto whomever
// reads/writes to the pointer from a different thread. Since doing so requires `unsafe` anyway this is something we can do soundly.
unsafe impl Sync for ClassLibraryPtr {}
// SAFETY: See `Sync` impl safety doc.
unsafe impl Send for ClassLibraryPtr {}

// ----------------------------------------------------------------------------------------------------------------------------------------------

/// # Safety
/// The table must not have been initialized yet.
#[allow(unsafe_op_in_unsafe_fn)] // Safety preconditions forwarded 1:1.
unsafe fn initialize_table<T>(table: &ManualInitCell<T>, value: T, _what: &str) {
    crate::strict_assert!(
        !table.is_initialized(),
        "method table for {_what} should only be initialized once"
    );

    // SAFETY: One-time, non-shared access during init.
    table.set(value)
}

/// # Safety
/// The table must have been initialized.
#[allow(unsafe_op_in_unsafe_fn)] // Safety preconditions forwarded 1:1.
unsafe fn get_table<T>(table: &'static ManualInitCell<T>, _msg: &str) -> &'static T {
    crate::strict_assert!(table.is_initialized(), "{_msg}");

    table.get_unchecked()
}

// ----------------------------------------------------------------------------------------------------------------------------------------------
// Public API

/// # Safety
///
/// The Godot binding must have been initialized before calling this function.
///
/// If "experimental-threads" is not enabled, then this must be called from the same thread that the bindings were initialized from.
#[inline(always)]
#[allow(unsafe_op_in_unsafe_fn)] // Safety preconditions forwarded 1:1.
pub unsafe fn get_interface() -> &'static GDExtensionInterface {
    &get_binding().interface
}

/// Interface for FFI functions that touch shared engine/scene state and must run on the main thread.
///
/// Asserts main thread (non-disengaged profiles) plus binding-live check. For `classdb_*`, `object_method_bind_*`, etc.
/// Not a thread-safe alternative; use [`thread_safe()`] for functions that only touch caller-owned memory.
///
/// # Safety
/// The Godot binding must have been initialized before calling this function.
#[inline(always)]
#[allow(unsafe_op_in_unsafe_fn)] // Safety preconditions forwarded 1:1.
pub unsafe fn on_main() -> &'static GDExtensionInterface {
    &get_binding().interface
}

/// Access the interface for thread-safe FFI functions (they only touch caller-owned memory).
///
/// Skips the main-thread assertion, keeping only the binding-live check. Default to [`on_main`] until a function is reviewed thread-safe.
/// Returns the full interface, so the restriction holds by convention only. TODO(v0.6): codegen a typed subset like [`thread_safe_lifecycle`],
/// so misuse becomes a compile error.
///
/// # Safety
/// The Godot binding must have been initialized before calling this function, and the accessed function must not touch shared engine state.
#[inline(always)]
#[allow(unsafe_op_in_unsafe_fn)] // Safety preconditions forwarded 1:1.
pub unsafe fn thread_safe() -> &'static GDExtensionInterface {
    &get_binding_thread_safe().interface
}

/// # Safety
///
/// The Godot binding must have been initialized before calling this function.
///
/// If "experimental-threads" is not enabled, then this must be called from the same thread that the bindings were initialized from.
#[inline(always)]
#[allow(unsafe_op_in_unsafe_fn)] // Safety preconditions forwarded 1:1.
pub unsafe fn get_library() -> crate::GDExtensionClassLibraryPtr {
    get_binding().library.0
}

/// Looks up an FFI function by name using `get_proc_address`.
///
/// Argument must be null-terminated, e.g. `b"my_ffi_function\0"`.
///
/// Returns `None` if the function is not available (e.g. the runtime Godot version predates the function). Necessary only in niche cases
/// where polyfill/cross-version behavior needs to be emulated. Likely obsolete once there's `gdextension_interface.json`.
///
/// # Safety
/// The Godot binding must have been initialized before calling this function, and the function must be run on the main thread.
#[inline]
#[allow(unsafe_op_in_unsafe_fn)] // Safety preconditions forwarded 1:1.
pub unsafe fn get_ffi_ptr_by_cstr(name: &[u8]) -> crate::GDExtensionInterfaceFunctionPtr {
    let get_proc_address = get_binding()
        .get_proc_address
        .expect("get_proc_address should be available");

    get_proc_address(crate::c_str(name))
}

/// Access the builtin lifecycle table.
///
/// # Safety
///
/// The Godot binding must have been initialized before calling this function.
///
/// If "experimental-threads" is not enabled, then this must be called from the same thread that the bindings were initialized from.
#[inline(always)]
#[allow(unsafe_op_in_unsafe_fn)] // Safety preconditions forwarded 1:1.
pub unsafe fn builtin_lifecycle_api() -> &'static BuiltinLifecycleTable {
    &get_binding().global_method_table
}

/// Access the reviewed builtin lifecycle functions that are thread-safe.
///
/// Intentionally a narrow subset, not the full lifecycle table; additions must be reviewed individually.
///
/// # Safety
/// The Godot binding must have been initialized before calling this function, and the accessed function must not touch shared engine state.
#[inline(always)]
#[allow(unsafe_op_in_unsafe_fn)] // Safety preconditions forwarded 1:1.
pub unsafe fn thread_safe_lifecycle() -> &'static ThreadSafeLifecycle {
    &get_binding_thread_safe().thread_safe_lifecycle
}

/// # Safety
///
/// - The Godot binding must have been initialized before calling this function.
/// - The class servers method table must have been initialized before calling this function.
///
/// If "experimental-threads" is not enabled, then this must be called from the same thread that the bindings were initialized from.
#[inline(always)]
#[allow(unsafe_op_in_unsafe_fn)] // Safety preconditions forwarded 1:1.
pub unsafe fn class_servers_api() -> &'static ClassServersMethodTable {
    get_table(
        &get_binding().class_server_method_table,
        "cannot fetch classes; init level 'Servers' not yet loaded",
    )
}

/// # Safety
///
/// - The Godot binding must have been initialized before calling this function.
/// - The class core method table must have been initialized before calling this function.
///
/// If "experimental-threads" is not enabled, then this must be called from the same thread that the bindings were initialized from.
#[inline(always)]
#[allow(unsafe_op_in_unsafe_fn)] // Safety preconditions forwarded 1:1.
pub unsafe fn class_core_api() -> &'static ClassCoreMethodTable {
    get_table(
        &get_binding().class_core_method_table,
        "cannot fetch classes; init level 'Core' not yet loaded",
    )
}

/// # Safety
///
/// - The Godot binding must have been initialized before calling this function.
/// - The class scene method table must have been initialized before calling this function.
///
/// If "experimental-threads" is not enabled, then this must be called from the same thread that the bindings were initialized from.
#[inline(always)]
#[allow(unsafe_op_in_unsafe_fn)] // Safety preconditions forwarded 1:1.
pub unsafe fn class_scene_api() -> &'static ClassSceneMethodTable {
    get_table(
        &get_binding().class_scene_method_table,
        "cannot fetch classes; init level 'Scene' not yet loaded",
    )
}

/// # Safety
///
/// - The Godot binding must have been initialized before calling this function.
/// - The class editor method table must have been initialized before calling this function.
///
/// If "experimental-threads" is not enabled, then this must be called from the same thread that the bindings were initialized from.
#[inline(always)]
#[allow(unsafe_op_in_unsafe_fn)] // Safety preconditions forwarded 1:1.
pub unsafe fn class_editor_api() -> &'static ClassEditorMethodTable {
    get_table(
        &get_binding().class_editor_method_table,
        "cannot fetch classes; init level 'Editor' not yet loaded",
    )
}

/// # Safety
///
/// - The Godot binding must have been initialized before calling this function.
/// - The builtin method table must have been initialized before calling this function.
///
/// If "experimental-threads" is not enabled, then this must be called from the same thread that the bindings were initialized from.
#[inline(always)]
#[allow(unsafe_op_in_unsafe_fn)] // Safety preconditions forwarded 1:1.
pub unsafe fn builtin_method_table() -> &'static BuiltinMethodTable {
    get_table(
        &get_binding().builtin_method_table,
        "cannot fetch builtin methods; table not ready",
    )
}

/// # Safety
///
/// The Godot binding must have been initialized before calling this function.
///
/// If "experimental-threads" is not enabled, then this must be called from the same thread that the bindings were initialized from.
#[inline(always)]
#[allow(unsafe_op_in_unsafe_fn)] // Safety preconditions forwarded 1:1.
pub unsafe fn utility_function_table() -> &'static UtilityFunctionTable {
    &get_binding().utility_function_table
}

/// Access the utility-function table for functions reviewed as thread-safe (currently the print group and `str`).
///
/// Skips the main-thread assertion, keeping only the binding-live check. Most utility functions touch shared engine state and must therefore
/// keep going through [`utility_function_table`]; codegen routes only the individually reviewed ones here (see `is_utility_function_thread_safe`
/// in `godot-codegen`).
///
/// # Safety
///
/// The Godot binding must have been initialized before calling this function, and the accessed function must not touch shared engine state.
#[inline(always)]
#[allow(unsafe_op_in_unsafe_fn)] // Safety preconditions forwarded 1:1.
pub unsafe fn utility_function_table_thread_safe() -> &'static UtilityFunctionTable {
    &get_binding_thread_safe().utility_function_table
}

/// # Safety
///
/// The Godot binding must have been initialized before calling this function.
///
/// If "experimental-threads" is not enabled, then this must be called from the same thread that the bindings were initialized from.
#[inline]
#[allow(unsafe_op_in_unsafe_fn)] // Safety preconditions forwarded 1:1.
pub unsafe fn config() -> &'static GdextConfig {
    &get_binding().config
}

/// Returns true if godot-rust bindings are initialized at a very low level.
///
/// The bindings are initialized when the library is loaded and deinitialized when it is unloaded (which may happen e.g. during a hot reload).
/// Do not use this as a general check whether certain Godot APIs can be used -- this is more complex and may depend on class/singleton.
#[inline]
pub fn is_initialized() -> bool {
    BindingStorage::is_initialized()
}

// ----------------------------------------------------------------------------------------------------------------------------------------------
// Crate-local implementation

/// Initializes the Godot binding.
///
/// Most other functions in this module rely on this function being called first as a safety condition.
///
/// # Safety
///
/// Must not be called concurrently with other functions that interact with the bindings - this is trivially true if "experimental-threads"
/// is not enabled.
///
/// If "experimental-threads" is enabled, then must be called from the main thread.
#[allow(unsafe_op_in_unsafe_fn)] // Safety preconditions forwarded 1:1.
pub(crate) unsafe fn initialize_binding(binding: GodotBinding) {
    BindingStorage::initialize(binding);
}

/// Deinitializes the Godot binding.
///
/// # Safety
///
/// See [`initialize_binding`].
#[allow(unsafe_op_in_unsafe_fn)] // Safety preconditions forwarded 1:1.
pub(crate) unsafe fn deinitialize_binding() {
    BindingStorage::deinitialize();
}

/// # Safety
///
/// The Godot binding must have been initialized before calling this function.
///
/// If "experimental-threads" is not enabled, then this must be called from the same thread that the bindings were initialized from.
#[inline(always)]
#[allow(unsafe_op_in_unsafe_fn)] // Safety preconditions forwarded 1:1.
pub(crate) unsafe fn get_binding() -> &'static GodotBinding {
    // Restricted access: assert main thread (no-op in multi-threaded builds), then perform the binding-live check.
    BindingStorage::ensure_main_thread();
    BindingStorage::get_binding_unchecked()
}

/// Like [`get_binding`], but without the main-thread assertion -- for thread-safe FFI functions.
///
/// # Safety
/// The Godot binding must have been initialized before calling this function. The accessed FFI function must only touch caller-owned memory
/// (e.g. builtin value-type ctors/dtors, mem alloc/free, type constructors), not shared engine or scene state.
#[inline(always)]
#[allow(unsafe_op_in_unsafe_fn)] // Safety preconditions forwarded 1:1.
pub(crate) unsafe fn get_binding_thread_safe() -> &'static GodotBinding {
    BindingStorage::get_binding_unchecked()
}

/// # Safety
///
/// - The Godot binding must have been initialized before calling this function.
/// - Must only be called once.
///
/// If "experimental-threads" is not enabled, then this must be called from the same thread that the bindings were initialized from.
#[allow(unsafe_op_in_unsafe_fn)] // Safety preconditions forwarded 1:1.
pub(crate) unsafe fn initialize_class_core_method_table(table: ClassCoreMethodTable) {
    initialize_table(
        &get_binding().class_core_method_table,
        table,
        "classes (Core level)",
    )
}

/// # Safety
///
/// - The Godot binding must have been initialized before calling this function.
/// - Must only be called once.
///
/// If "experimental-threads" is not enabled, then this must be called from the same thread that the bindings were initialized from.
#[allow(unsafe_op_in_unsafe_fn)] // Safety preconditions forwarded 1:1.
pub(crate) unsafe fn initialize_class_server_method_table(table: ClassServersMethodTable) {
    initialize_table(
        &get_binding().class_server_method_table,
        table,
        "classes (Server level)",
    )
}

/// # Safety
///
/// - The Godot binding must have been initialized before calling this function.
/// - Must only be called once.
///
/// If "experimental-threads" is not enabled, then this must be called from the same thread that the bindings were initialized from.
#[allow(unsafe_op_in_unsafe_fn)] // Safety preconditions forwarded 1:1.
pub(crate) unsafe fn initialize_class_scene_method_table(table: ClassSceneMethodTable) {
    initialize_table(
        &get_binding().class_scene_method_table,
        table,
        "classes (Scene level)",
    )
}

/// # Safety
///
/// The Godot binding must have been initialized before calling this function.
///
/// If "experimental-threads" is not enabled, then this must be called from the same thread that the bindings were initialized from.
#[inline(always)]
#[allow(unsafe_op_in_unsafe_fn)] // Safety preconditions forwarded 1:1.
pub(crate) unsafe fn runtime_metadata() -> &'static GdextRuntimeMetadata {
    &get_binding().runtime_metadata
}

/// # Safety
///
/// - The Godot binding must have been initialized before calling this function.
/// - Must only be called once.
///
/// If "experimental-threads" is not enabled, then this must be called from the same thread that the bindings were initialized from.
#[allow(unsafe_op_in_unsafe_fn)] // Safety preconditions forwarded 1:1.
pub(crate) unsafe fn initialize_class_editor_method_table(table: ClassEditorMethodTable) {
    initialize_table(
        &get_binding().class_editor_method_table,
        table,
        "classes (Editor level)",
    )
}

/// # Safety
///
/// - The Godot binding must have been initialized before calling this function.
/// - Must only be called once.
///
/// If "experimental-threads" is not enabled, then this must be called from the same thread that the bindings were initialized from.
#[allow(unsafe_op_in_unsafe_fn)] // Safety preconditions forwarded 1:1.
pub(crate) unsafe fn initialize_builtin_method_table(table: BuiltinMethodTable) {
    initialize_table(&get_binding().builtin_method_table, table, "builtins")
}