1#![cfg_attr(docsrs, feature(doc_cfg))]
20
21pub mod gdscript_layer;
22pub mod generated;
24pub mod lookup;
25pub mod model;
26
27use rustc_hash::FxHashMap;
28
29pub use lookup::MemberRef;
30pub use model::{
31 ApiData, ApiType, ApiVersion, BuiltinData, BuiltinId, BuiltinMember, ClassData, ClassId,
32 ConstInfo, DocId, ElemRef, EnumInfo, EnumValue, MethodSig, OperatorSig, Param, PropertyInfo,
33 SignalSig, TyRef, UtilityFn,
34};
35
36#[must_use]
38pub fn godot_version() -> &'static str {
39 generated::GODOT_VERSION
40}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
44pub enum LoadError {
45 Decode(String),
47}
48
49impl std::fmt::Display for LoadError {
50 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51 match self {
52 Self::Decode(msg) => write!(f, "failed to decode engine-API blob: {msg}"),
53 }
54 }
55}
56
57impl std::error::Error for LoadError {}
58
59#[derive(Debug)]
65pub struct EngineApi {
66 pub(crate) data: ApiData,
67 pub(crate) class_by_name: FxHashMap<String, ClassId>,
68 pub(crate) builtin_by_name: FxHashMap<String, BuiltinId>,
69 pub(crate) singleton_by_name: FxHashMap<String, ClassId>,
70 pub(crate) utility_by_name: FxHashMap<String, u32>,
71 pub(crate) global_enum_by_name: FxHashMap<String, u32>,
72 pub(crate) global_consts: Vec<gdscript_layer::GlobalConst>,
74 pub(crate) gdscript_builtins: Vec<gdscript_layer::BuiltinFn>,
76 pub(crate) int_builtin: Option<BuiltinId>,
78}
79
80impl EngineApi {
81 #[must_use]
84 pub fn from_data(data: ApiData) -> Self {
85 let mut class_by_name = FxHashMap::default();
86 for (i, c) in data.classes.iter().enumerate() {
87 class_by_name.insert(
88 c.name.clone(),
89 ClassId(u32::try_from(i).unwrap_or(u32::MAX)),
90 );
91 }
92 let mut builtin_by_name = FxHashMap::default();
93 for (i, b) in data.builtins.iter().enumerate() {
94 builtin_by_name.insert(
95 b.name.clone(),
96 BuiltinId(u32::try_from(i).unwrap_or(u32::MAX)),
97 );
98 }
99 let singleton_by_name = data
100 .singletons
101 .iter()
102 .map(|(name, id)| (name.clone(), *id))
103 .collect();
104 let mut utility_by_name = FxHashMap::default();
105 for (i, u) in data.utilities.iter().enumerate() {
106 utility_by_name.insert(u.name.clone(), u32::try_from(i).unwrap_or(u32::MAX));
107 }
108 let mut global_enum_by_name = FxHashMap::default();
109 for (i, e) in data.global_enums.iter().enumerate() {
110 global_enum_by_name.insert(e.name.clone(), u32::try_from(i).unwrap_or(u32::MAX));
111 }
112 let int_builtin = builtin_by_name.get("int").copied();
113
114 Self {
115 data,
116 class_by_name,
117 builtin_by_name,
118 singleton_by_name,
119 utility_by_name,
120 global_enum_by_name,
121 global_consts: gdscript_layer::global_consts(),
122 gdscript_builtins: gdscript_layer::builtin_fns(),
123 int_builtin,
124 }
125 }
126
127 pub fn from_bytes(bytes: &[u8]) -> Result<Self, LoadError> {
135 let mut aligned = rkyv::util::AlignedVec::<16>::new();
136 aligned.extend_from_slice(bytes);
137 let data = rkyv::from_bytes::<ApiData, rkyv::rancor::Error>(aligned.as_slice())
138 .map_err(|e| LoadError::Decode(e.to_string()))?;
139 Ok(Self::from_data(data))
140 }
141
142 #[must_use]
144 pub fn version(&self) -> &ApiVersion {
145 &self.data.version
146 }
147}
148
149#[cfg(all(feature = "bundled-api", not(target_arch = "wasm32")))]
160#[must_use]
161pub fn bundled() -> &'static EngineApi {
162 use std::sync::OnceLock;
163 static BUNDLED: OnceLock<EngineApi> = OnceLock::new();
164 static BYTES: &[u8] = include_bytes!("engine_api.bin");
165 BUNDLED.get_or_init(|| {
166 EngineApi::from_bytes(BYTES).expect("the bundled engine-API blob must be valid")
167 })
168}
169
170#[cfg(test)]
171mod tests {
172 #[test]
173 fn generated_metadata_is_present() {
174 assert!(!crate::generated::GODOT_VERSION.is_empty());
176 }
177
178 #[cfg(all(feature = "bundled-api", not(target_arch = "wasm32")))]
180 #[test]
181 fn bundled_blob_loads_and_resolves_golden_symbols() {
182 let api = crate::bundled();
183
184 assert_eq!(api.version().major, 4);
186 assert_eq!(api.version().minor, 5);
187
188 let node = api.class_by_name("Node").expect("Node class present");
190 let node2d = api.class_by_name("Node2D").expect("Node2D class present");
191 assert!(api.lookup_member(node, "add_child").is_some());
192 assert!(api.is_subclass(node2d, node), "Node2D is a Node");
193 assert!(
194 api.lookup_member(node2d, "add_child").is_some(),
195 "add_child is inherited onto Node2D"
196 );
197
198 let members = api.members_of(node2d);
200 assert!(members.iter().any(|m| m.name() == "add_child"));
201 assert!(members.iter().any(|m| m.name() == "position"));
202
203 assert!(api.singleton("Input").is_some());
205 let v2 = api
206 .builtin_by_name("Vector2")
207 .expect("Vector2 builtin present");
208 assert!(api.builtin_member(v2, "x").is_some());
209 assert!(api.builtin_operators(v2).iter().any(|o| o.op == "+"));
210 let process_mode = api
211 .class(node)
212 .properties
213 .iter()
214 .find(|p| p.name == "process_mode")
215 .expect("Node.process_mode present");
216 assert!(
217 process_mode.enum_of.is_some(),
218 "process_mode is recovered as enum-typed from its getter"
219 );
220
221 assert!(api.global_const("PI").is_some());
223 assert!(api.gdscript_builtin("preload").is_some());
224 }
225}