godot_ffi/
string_cache.rs

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
/*
 * 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 as sys;

use std::collections::HashMap;
use std::mem::MaybeUninit;
use std::ptr;

/// Caches `StringName` instances at initialization.
pub struct StringCache<'a> {
    // Box is needed for element stability (new insertions don't move object; i.e. pointers to it remain valid).
    instances_by_str: HashMap<&'static str, Box<sys::types::OpaqueStringName>>,
    interface: &'a sys::GDExtensionInterface,
    builtin_lifecycle: &'a sys::BuiltinLifecycleTable,
}

impl<'a> StringCache<'a> {
    pub fn new(
        interface: &'a sys::GDExtensionInterface,
        builtin_lifecycle: &'a sys::BuiltinLifecycleTable,
    ) -> Self {
        Self {
            instances_by_str: HashMap::new(),
            interface,
            builtin_lifecycle,
        }
    }

    /// Get a pointer to a `StringName`. Reuses cached instances, only deallocates on destruction of this cache.
    pub fn fetch(&mut self, key: &'static str) -> sys::GDExtensionStringNamePtr {
        assert!(key.is_ascii(), "string is not ASCII: {key}");

        // Already cached.
        if let Some(opaque_box) = self.instances_by_str.get_mut(key) {
            return box_to_sname_ptr(opaque_box);
        }

        let mut sname = MaybeUninit::<sys::types::OpaqueStringName>::uninit();
        let sname_ptr = sname.as_mut_ptr();

        // For Godot 4.1, construct StringName via String + conversion.
        #[cfg(before_api = "4.2")]
        unsafe {
            let string_new_with_latin1_chars_and_len = self
                .interface
                .string_new_with_latin1_chars_and_len
                .unwrap_unchecked();
            let string_name_from_string = self.builtin_lifecycle.string_name_from_string;
            let string_destroy = self.builtin_lifecycle.string_destroy;

            let mut string = MaybeUninit::<sys::types::OpaqueString>::uninit();
            let string_ptr = string.as_mut_ptr();

            // Construct String.
            string_new_with_latin1_chars_and_len(
                string_uninit_ptr(string_ptr),
                key.as_ptr() as *const std::os::raw::c_char,
                key.len() as sys::GDExtensionInt,
            );

            // Convert String -> StringName.
            string_name_from_string(
                sname_uninit_type_ptr(sname_ptr),
                [sys::to_const_ptr(string_type_ptr(string_ptr))].as_ptr(),
            );

            // Destroy String.
            string_destroy(string_type_ptr(string_ptr));
        }

        // For Godot 4.2+, construct StringName directly from C string.
        #[cfg(since_api = "4.2")]
        unsafe {
            let string_name_new_with_utf8_chars_and_len = self
                .interface
                .string_name_new_with_utf8_chars_and_len
                .unwrap_unchecked();

            // Construct StringName from string (non-static, we only need them during the cache's lifetime).
            // There is no _latin_*() variant that takes length, so we have to use _utf8_*() instead.
            string_name_new_with_utf8_chars_and_len(
                sname_uninit_ptr(sname_ptr),
                key.as_ptr() as *const std::os::raw::c_char,
                key.len() as sys::GDExtensionInt,
            );
        }

        // Return StringName.
        let opaque = unsafe { sname.assume_init() };

        let mut opaque_box = Box::new(opaque);
        let sname_ptr = box_to_sname_ptr(&mut opaque_box);

        self.instances_by_str.insert(key, opaque_box);
        sname_ptr
    }
}

/// Destroy all string names.
impl<'a> Drop for StringCache<'a> {
    fn drop(&mut self) {
        let string_name_destroy = self.builtin_lifecycle.string_name_destroy;

        unsafe {
            for (_, mut opaque_box) in self.instances_by_str.drain() {
                let opaque_ptr = ptr::addr_of_mut!(*opaque_box);
                string_name_destroy(sname_type_ptr(opaque_ptr));
            }
        }
    }
}

// ----------------------------------------------------------------------------------------------------------------------------------------------
// Implementation
// These are tiny wrappers to avoid exposed `as` casts (which are very easy to get wrong, i.e. extra dereference).
// Using a trait to abstract over String/StringName is overkill and also doesn't work due to both having same Opaque<N> type.

fn box_to_sname_ptr(
    boxed: &mut Box<sys::types::OpaqueStringName>,
) -> sys::GDExtensionStringNamePtr {
    let opaque_ptr = ptr::addr_of_mut!(**boxed);
    opaque_ptr as sys::GDExtensionStringNamePtr
}

#[cfg(before_api = "4.2")]
unsafe fn string_type_ptr(opaque_ptr: *mut sys::types::OpaqueString) -> sys::GDExtensionTypePtr {
    opaque_ptr as sys::GDExtensionTypePtr
}

#[cfg(before_api = "4.2")]
unsafe fn string_uninit_ptr(
    opaque_ptr: *mut sys::types::OpaqueString,
) -> sys::GDExtensionUninitializedStringPtr {
    opaque_ptr as sys::GDExtensionUninitializedStringPtr
}

#[cfg(since_api = "4.2")]
unsafe fn sname_uninit_ptr(
    opaque_ptr: *mut sys::types::OpaqueStringName,
) -> sys::GDExtensionUninitializedStringNamePtr {
    opaque_ptr as sys::GDExtensionUninitializedStringNamePtr
}

unsafe fn sname_type_ptr(opaque_ptr: *mut sys::types::OpaqueStringName) -> sys::GDExtensionTypePtr {
    opaque_ptr as sys::GDExtensionTypePtr
}

#[cfg(before_api = "4.2")]
unsafe fn sname_uninit_type_ptr(
    opaque_ptr: *mut sys::types::OpaqueStringName,
) -> sys::GDExtensionUninitializedTypePtr {
    opaque_ptr as sys::GDExtensionUninitializedTypePtr
}