godot_ffi/
string_cache.rs

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
8use std::collections::HashMap;
9use std::mem::MaybeUninit;
10use std::ptr;
11
12use crate as sys;
13
14/// Caches `StringName` instances at initialization.
15pub struct StringCache<'a> {
16    // Box is needed for element stability (new insertions don't move object; i.e. pointers to it remain valid).
17    instances_by_str: HashMap<&'static str, Box<sys::types::OpaqueStringName>>,
18    interface: &'a sys::GDExtensionInterface,
19    builtin_lifecycle: &'a sys::BuiltinLifecycleTable,
20}
21
22impl<'a> StringCache<'a> {
23    pub fn new(
24        interface: &'a sys::GDExtensionInterface,
25        builtin_lifecycle: &'a sys::BuiltinLifecycleTable,
26    ) -> Self {
27        Self {
28            instances_by_str: HashMap::new(),
29            interface,
30            builtin_lifecycle,
31        }
32    }
33
34    /// Get a pointer to a `StringName`. Reuses cached instances, only deallocates on destruction of this cache.
35    pub fn fetch(&mut self, key: &'static str) -> sys::GDExtensionStringNamePtr {
36        assert!(key.is_ascii(), "string is not ASCII: {key}");
37
38        // Already cached.
39        if let Some(opaque_box) = self.instances_by_str.get_mut(key) {
40            return box_to_sname_ptr(opaque_box);
41        }
42
43        let mut sname = MaybeUninit::<sys::types::OpaqueStringName>::uninit();
44        let sname_ptr = sname.as_mut_ptr();
45
46        // Construct StringName directly from C string (possible since Godot 4.2).
47        unsafe {
48            let string_name_new_with_utf8_chars_and_len = self
49                .interface
50                .string_name_new_with_utf8_chars_and_len
51                .unwrap_unchecked();
52
53            // Construct StringName from string (non-static, we only need them during the cache's lifetime).
54            // There is no _latin_*() variant that takes length, so we have to use _utf8_*() instead.
55            string_name_new_with_utf8_chars_and_len(
56                sname_uninit_ptr(sname_ptr),
57                key.as_ptr() as *const std::os::raw::c_char,
58                key.len() as sys::GDExtensionInt,
59            );
60        }
61
62        // Return StringName.
63        let opaque = unsafe { sname.assume_init() };
64
65        let mut opaque_box = Box::new(opaque);
66        let sname_ptr = box_to_sname_ptr(&mut opaque_box);
67
68        self.instances_by_str.insert(key, opaque_box);
69        sname_ptr
70    }
71}
72
73/// Destroy all string names.
74impl Drop for StringCache<'_> {
75    fn drop(&mut self) {
76        let string_name_destroy = self.builtin_lifecycle.string_name_destroy;
77
78        unsafe {
79            for (_, mut opaque_box) in self.instances_by_str.drain() {
80                let opaque_ptr = ptr::addr_of_mut!(*opaque_box);
81                string_name_destroy(sname_type_ptr(opaque_ptr));
82            }
83        }
84    }
85}
86
87// ----------------------------------------------------------------------------------------------------------------------------------------------
88// Implementation
89// These are tiny wrappers to avoid exposed `as` casts (which are very easy to get wrong, i.e. extra dereference).
90// Using a trait to abstract over String/StringName is overkill and also doesn't work due to both having same Opaque<N> type.
91
92fn box_to_sname_ptr(
93    boxed: &mut Box<sys::types::OpaqueStringName>,
94) -> sys::GDExtensionStringNamePtr {
95    let opaque_ptr = ptr::addr_of_mut!(**boxed);
96    opaque_ptr as sys::GDExtensionStringNamePtr
97}
98
99unsafe fn sname_uninit_ptr(
100    opaque_ptr: *mut sys::types::OpaqueStringName,
101) -> sys::GDExtensionUninitializedStringNamePtr {
102    opaque_ptr as sys::GDExtensionUninitializedStringNamePtr
103}
104
105unsafe fn sname_type_ptr(opaque_ptr: *mut sys::types::OpaqueStringName) -> sys::GDExtensionTypePtr {
106    opaque_ptr as sys::GDExtensionTypePtr
107}