#![cfg_attr(docsrs, feature(doc_cfg))]
pub mod gdscript_layer;
pub mod generated;
pub mod lookup;
pub mod model;
use rustc_hash::FxHashMap;
pub use lookup::MemberRef;
pub use model::{
ApiData, ApiType, ApiVersion, BuiltinData, BuiltinId, BuiltinMember, ClassData, ClassId,
ConstInfo, DocId, ElemRef, EnumInfo, EnumValue, MethodSig, OperatorSig, Param, PropertyInfo,
SignalSig, TyRef, UtilityFn,
};
#[must_use]
pub fn godot_version() -> &'static str {
generated::GODOT_VERSION
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LoadError {
Decode(String),
}
impl std::fmt::Display for LoadError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Decode(msg) => write!(f, "failed to decode engine-API blob: {msg}"),
}
}
}
impl std::error::Error for LoadError {}
#[derive(Debug)]
pub struct EngineApi {
pub(crate) data: ApiData,
pub(crate) class_by_name: FxHashMap<String, ClassId>,
pub(crate) builtin_by_name: FxHashMap<String, BuiltinId>,
pub(crate) singleton_by_name: FxHashMap<String, ClassId>,
pub(crate) utility_by_name: FxHashMap<String, u32>,
pub(crate) global_enum_by_name: FxHashMap<String, u32>,
pub(crate) global_consts: Vec<gdscript_layer::GlobalConst>,
pub(crate) gdscript_builtins: Vec<gdscript_layer::BuiltinFn>,
pub(crate) int_builtin: Option<BuiltinId>,
}
impl EngineApi {
#[must_use]
pub fn from_data(data: ApiData) -> Self {
let mut class_by_name = FxHashMap::default();
for (i, c) in data.classes.iter().enumerate() {
class_by_name.insert(
c.name.clone(),
ClassId(u32::try_from(i).unwrap_or(u32::MAX)),
);
}
let mut builtin_by_name = FxHashMap::default();
for (i, b) in data.builtins.iter().enumerate() {
builtin_by_name.insert(
b.name.clone(),
BuiltinId(u32::try_from(i).unwrap_or(u32::MAX)),
);
}
let singleton_by_name = data
.singletons
.iter()
.map(|(name, id)| (name.clone(), *id))
.collect();
let mut utility_by_name = FxHashMap::default();
for (i, u) in data.utilities.iter().enumerate() {
utility_by_name.insert(u.name.clone(), u32::try_from(i).unwrap_or(u32::MAX));
}
let mut global_enum_by_name = FxHashMap::default();
for (i, e) in data.global_enums.iter().enumerate() {
global_enum_by_name.insert(e.name.clone(), u32::try_from(i).unwrap_or(u32::MAX));
}
let int_builtin = builtin_by_name.get("int").copied();
Self {
data,
class_by_name,
builtin_by_name,
singleton_by_name,
utility_by_name,
global_enum_by_name,
global_consts: gdscript_layer::global_consts(),
gdscript_builtins: gdscript_layer::builtin_fns(),
int_builtin,
}
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, LoadError> {
let mut aligned = rkyv::util::AlignedVec::<16>::new();
aligned.extend_from_slice(bytes);
let data = rkyv::from_bytes::<ApiData, rkyv::rancor::Error>(aligned.as_slice())
.map_err(|e| LoadError::Decode(e.to_string()))?;
Ok(Self::from_data(data))
}
#[must_use]
pub fn version(&self) -> &ApiVersion {
&self.data.version
}
}
#[cfg(all(feature = "bundled-api", not(target_arch = "wasm32")))]
#[must_use]
pub fn bundled() -> &'static EngineApi {
use std::sync::OnceLock;
static BUNDLED: OnceLock<EngineApi> = OnceLock::new();
static BYTES: &[u8] = include_bytes!("engine_api.bin");
BUNDLED.get_or_init(|| {
EngineApi::from_bytes(BYTES).expect("the bundled engine-API blob must be valid")
})
}
#[cfg(test)]
mod tests {
#[test]
fn generated_metadata_is_present() {
assert!(!crate::generated::GODOT_VERSION.is_empty());
}
#[cfg(all(feature = "bundled-api", not(target_arch = "wasm32")))]
#[test]
fn bundled_blob_loads_and_resolves_golden_symbols() {
let api = crate::bundled();
assert_eq!(api.version().major, 4);
assert_eq!(api.version().minor, 5);
let node = api.class_by_name("Node").expect("Node class present");
let node2d = api.class_by_name("Node2D").expect("Node2D class present");
assert!(api.lookup_member(node, "add_child").is_some());
assert!(api.is_subclass(node2d, node), "Node2D is a Node");
assert!(
api.lookup_member(node2d, "add_child").is_some(),
"add_child is inherited onto Node2D"
);
let members = api.members_of(node2d);
assert!(members.iter().any(|m| m.name() == "add_child"));
assert!(members.iter().any(|m| m.name() == "position"));
assert!(api.singleton("Input").is_some());
let v2 = api
.builtin_by_name("Vector2")
.expect("Vector2 builtin present");
assert!(api.builtin_member(v2, "x").is_some());
assert!(api.builtin_operators(v2).iter().any(|o| o.op == "+"));
let process_mode = api
.class(node)
.properties
.iter()
.find(|p| p.name == "process_mode")
.expect("Node.process_mode present");
assert!(
process_mode.enum_of.is_some(),
"process_mode is recovered as enum-typed from its getter"
);
assert!(api.global_const("PI").is_some());
assert!(api.gdscript_builtin("preload").is_some());
}
}