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
/*
* 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 std::path::Path;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use crate::context::Context;
use crate::generator::builtins;
use crate::models::domain::{ExtensionApi, ModName, NativeStructure, TyName};
use crate::util::ident;
use crate::{SubmitFn, conv, special_cases, util};
pub fn generate_native_structures_files(
api: &ExtensionApi,
ctx: &mut Context,
gen_path: &Path,
submit_fn: &mut SubmitFn,
) {
let _ = std::fs::remove_dir_all(gen_path);
std::fs::create_dir_all(gen_path).expect("create native directory");
let mut modules = vec![];
for native_structure in api.native_structures.iter() {
// Some may be excluded in minimal codegen, because they hold codegen-excluded classes as fields.
if special_cases::is_native_struct_excluded(&native_structure.name) {
continue;
}
let module_name = ModName::from_godot(&native_structure.name);
let class_name = TyName::from_godot(&native_structure.name);
let generated_class = make_native_structure(native_structure, &class_name, ctx);
let file_contents = generated_class.code;
let out_path = gen_path.join(format!("{}.rs", module_name.rust_mod));
submit_fn(out_path, file_contents);
modules.push(builtins::GeneratedBuiltinModule {
outer_builtin: class_name.rust_ty.clone(),
inner_builtin: class_name.rust_ty.clone(),
module_name,
is_pub_sidecar: false, // Native structures don't have default extenders.
});
}
let out_path = gen_path.join("mod.rs");
let mod_contents = builtins::make_builtin_module_file(modules);
submit_fn(out_path, mod_contents);
}
// ----------------------------------------------------------------------------------------------------------------------------------------------
// Implementation
#[derive(Clone, Eq, PartialEq, Debug)]
pub(crate) struct NativeStructuresField {
/// Identifier for the field name, e.g. `collider`.
pub field_name: String,
/// Type which has a raw format that is latter mapped to `RustTy`.
///
/// Corresponds to other Godot type names, e.g. `Object*` or `enum::TextServer.Direction`.
pub field_type: String,
/// If the field is an array, this contains the number of elements.
pub array_size: Option<usize>,
}
fn make_native_structure(
structure: &NativeStructure,
class_name: &TyName,
ctx: &mut Context,
) -> builtins::GeneratedBuiltin {
let class_name = &class_name.rust_ty;
// TODO for native structures holding object pointers, we should encapsulate the raw object, as follows:
// - Make raw object pointer field private (and maybe also ID field, to keep in sync).
// - Add constructor that takes Gd<Object> instead of ID/raw-ptr fields.
// - Add getter and setter methods with Gd<Object> (already present).
// - Add Drop impl, which decrements refcount if the constructor was used, and does nothing if FromGodot pointer conversion was used.
let imports = util::make_imports();
let (fields, methods) = make_native_structure_fields_and_methods(structure, ctx);
// mod re_export needed, because class should not appear inside the file module, and we can't re-export private struct as pub
let tokens = quote! {
#imports
use std::ffi::c_void; // for opaque object pointer fields
/// Native structure; can be passed via pointer in APIs that are not exposed to GDScript.
///
#[derive(Clone, PartialEq, Debug)]
#[repr(C)]
pub struct #class_name {
#fields
}
impl #class_name {
#methods
}
// Pointer conversions are now handled by RawPtr<P>, no direct ToGodot/FromGodot impls.
};
// note: TypePtr -> ObjectPtr conversion OK?
builtins::GeneratedBuiltin {
code: tokens,
has_sidecar_module: false,
}
}
fn make_native_structure_fields_and_methods(
structure: &NativeStructure,
ctx: &mut Context,
) -> (TokenStream, TokenStream) {
let fields = parse_native_structures_format(&structure.format)
.expect("Could not parse native_structures format field");
let mut field_definitions = vec![];
let mut accessors = vec![];
for field in fields {
let (field_def, accessor) = make_native_structure_field_and_accessor(field, ctx);
field_definitions.push(field_def);
if let Some(accessor) = accessor {
accessors.push(accessor);
}
}
let fields = quote! { #( #field_definitions )* };
let methods = quote! { #( #accessors )* };
(fields, methods)
}
fn make_native_structure_field_and_accessor(
field: NativeStructuresField,
ctx: &mut Context,
) -> (TokenStream, Option<TokenStream>) {
let field_type = normalize_native_structure_field_type(&field.field_type);
let (field_type, is_object_ptr) = conv::to_rust_type_abi(&field_type, ctx);
// Make array if needed.
let field_type = if let Some(size) = field.array_size {
quote! { [#field_type; #size] }
} else {
quote! { #field_type }
};
let snake_field_name = ident(&conv::to_snake_case(&field.field_name));
let (field_name, accessor);
if is_object_ptr {
// Highlight that the pointer field is internal/opaque.
field_name = format_ident!("raw_{}_ptr", snake_field_name);
// Generate method that converts from instance ID.
let getter_name = format_ident!("get_{}", snake_field_name);
let setter_name = format_ident!("set_{}", snake_field_name);
let id_field_name = format_ident!("{}_id", snake_field_name);
// Current native structures treat all object pointers as Object (even if concrete ones like `collider` might be Node).
// Having node also avoids the lifetime issue with reference-counted objects (unclear if gdext should increment refcount or not).
// If other fields use different classes in the future, we'll need to update this.
accessor = Some(quote! {
/// Returns the object as a `Gd<Node>`, or `None` if it no longer exists.
pub fn #getter_name(&self) -> Option<Gd<Object>> {
crate::obj::InstanceId::try_from_u64(self.#id_field_name.id)
.and_then(|id| Gd::try_from_instance_id(id).ok())
// Sanity check for consistency (if Some(...)):
// let ptr = self.#field_name as sys::GDExtensionObjectPtr;
// unsafe { Gd::from_obj_sys(ptr) }
}
/// Sets the object from a `Gd` pointer holding `Node` or a derived class.
///
/// # Safety
/// You must ensure that the provided object remains alive while Godot accesses it.
/// See also [`RawPtr::new()`][crate::meta::RawPtr::new].
pub unsafe fn #setter_name<T>(&mut self, #snake_field_name: Gd<T>)
where T: crate::obj::Inherits<Object> {
use crate::meta::GodotType as _;
let obj = #snake_field_name.upcast();
#[cfg(safeguards_balanced)] #[cfg_attr(published_docs, doc(cfg(safeguards_balanced)))]
assert!(obj.is_instance_valid(), "provided node is dead");
let id = obj.instance_id().to_u64();
self.#id_field_name = ObjectId { id };
// SAFETY: provided by method safety contract.
// Godot declares void* but expects GDExtensionObjectPtr.
self.#field_name = unsafe { RawPtr::new(obj.obj_sys().cast::<std::ffi::c_void>()) };
}
});
} else {
field_name = snake_field_name;
accessor = None;
};
let field_def = quote! {
pub #field_name: #field_type,
};
(field_def, accessor)
}
/// Native structures use a different format for enums than the rest of the JSON file.
/// If we detect a scoped field, convert it to the enum format expected by to_rust_type().
///
/// Example: `TextServer::Direction` -> `enum::TextServer.Direction`.
fn normalize_native_structure_field_type(field_type: &str) -> String {
if field_type.contains("::") {
let with_dot = field_type.replace("::", ".");
format!("enum::{with_dot}")
} else {
field_type.to_string()
}
}
/// Parse a string of semicolon-separated C-style type declarations. Fail with `None` if any errors occur.
pub(crate) fn parse_native_structures_format(input: &str) -> Option<Vec<NativeStructuresField>> {
input
.split(';')
.filter(|var| !var.trim().is_empty())
.map(|var| {
let mut parts = var.trim().splitn(2, ' ');
let mut field_type = parts.next()?.to_owned();
let mut field_name = parts.next()?.to_owned();
// If the field is a pointer, put the star on the type, not the name.
if field_name.starts_with('*') {
field_name.remove(0);
field_type.push('*');
}
// If Godot provided a default value, ignore it.
// TODO We might use these if we synthetically generate constructors in the future
if let Some(index) = field_name.find(" = ") {
field_name.truncate(index);
}
// If the field is an array, store array size separately.
// Not part of type because fixed-size arrays are not a concept in the JSON outside native structures.
let mut array_size = None;
if let Some(index) = field_name.find('[') {
array_size = Some(field_name[index + 1..field_name.len() - 1].parse().ok()?);
field_name.truncate(index);
}
Some(NativeStructuresField {
field_name,
field_type,
array_size,
})
})
.collect()
}