godot_testability_runtime/
ffi.rs

1//! FFI bindings to libgodot for embedded runtime support.
2//!
3//! This module provides Rust bindings to the libgodot library, enabling
4//! embedded Godot runtime execution for testing purposes.
5
6use std::ffi::{c_char, c_int, c_void};
7
8/// GDExtension initialization levels
9#[repr(C)]
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum GDExtensionInitializationLevel {
12    Core = 0,
13    Servers = 1,
14    Scene = 2,
15    Editor = 3,
16    MaxInitializationLevel = 4,
17}
18
19/// GDExtension boolean type
20pub type GDExtensionBool = u8;
21
22/// Opaque pointer to GDExtension class library
23pub type GDExtensionClassLibraryPtr = *mut c_void;
24
25/// Function pointer for getting GDExtension interface functions
26pub type GDExtensionInterfaceGetProcAddress =
27    unsafe extern "C" fn(function_name: *const c_char) -> Option<unsafe extern "C" fn()>;
28
29/// GDExtension initialization structure
30#[repr(C)]
31#[derive(Debug)]
32pub struct GDExtensionInitialization {
33    /// Minimum initialization level required
34    pub minimum_initialization_level: GDExtensionInitializationLevel,
35    /// User data pointer
36    pub userdata: *mut c_void,
37    /// Initialize function called for each initialization level
38    pub initialize:
39        Option<extern "C" fn(userdata: *mut c_void, level: GDExtensionInitializationLevel)>,
40    /// Deinitialize function called for each initialization level
41    pub deinitialize:
42        Option<extern "C" fn(userdata: *mut c_void, level: GDExtensionInitializationLevel)>,
43}
44
45/// GDExtension initialization function signature
46pub type GDExtensionInitializationFunction = extern "C" fn(
47    get_proc_address: GDExtensionInterfaceGetProcAddress,
48    library: GDExtensionClassLibraryPtr,
49    initialization: *mut GDExtensionInitialization,
50) -> GDExtensionBool;
51
52/// Godot version structure
53#[repr(C)]
54#[derive(Debug)]
55pub struct GDExtensionGodotVersion {
56    pub major: u32,
57    pub minor: u32,
58    pub patch: u32,
59    pub string: *const c_char,
60}
61
62/// Callback function type for libgodot_gdextension_bind
63pub type LibgodotGDExtensionBindCallback = extern "C" fn(
64    get_proc_address: Option<GDExtensionInterfaceGetProcAddress>,
65    library: GDExtensionClassLibraryPtr,
66    initialization: *mut GDExtensionInitialization,
67) -> c_int;
68
69/// Callback function type for scene loading
70pub type LibgodotSceneCallback = extern "C" fn(scene_tree: *mut c_void);
71
72// FFI bindings to libgodot functions
73extern "C" {
74    /// Initialize libgodot resources
75    pub fn libgodot_init_resource();
76
77    /// Load a scene into the scene tree
78    pub fn libgodot_scene_load(scene: *mut c_void);
79
80    /// Check if a scene is loadable
81    pub fn libgodot_is_scene_loadable() -> c_int;
82
83    /// Main GDExtension binding function used by language bindings
84    pub fn libgodot_gdextension_bind(
85        initialization_bind: LibgodotGDExtensionBindCallback,
86        scene_function_bind: Option<LibgodotSceneCallback>,
87    );
88
89    /// Main Godot entry point
90    pub fn godot_main(argc: c_int, argv: *const *const c_char) -> c_int;
91}
92
93/// Safe wrapper around libgodot FFI functions
94#[derive(Debug)]
95pub struct LibgodotRuntime {
96    initialized: bool,
97    // Note: We can't store raw pointers in a Send/Sync struct safely
98    // Instead, we'll manage scene tree access through static callbacks
99}
100
101impl LibgodotRuntime {
102    /// Create a new libgodot runtime instance
103    pub fn new() -> Self {
104        Self { initialized: false }
105    }
106
107    /// Initialize the libgodot runtime
108    pub fn initialize(&mut self) -> Result<(), String> {
109        if self.initialized {
110            return Err("Runtime already initialized".to_string());
111        }
112
113        self.initialized = true;
114        Ok(())
115    }
116
117    /// Start the main Godot loop with the provided arguments
118    pub fn run_main(&mut self, args: &[String]) -> Result<i32, String> {
119        if !self.initialized {
120            return Err("Runtime not initialized".to_string());
121        }
122
123        // Convert Rust strings to C strings
124        let c_strings: Vec<std::ffi::CString> = args
125            .iter()
126            .map(|s| std::ffi::CString::new(s.as_str()).unwrap())
127            .collect();
128
129        let c_args: Vec<*const c_char> = c_strings.iter().map(|s| s.as_ptr()).collect();
130
131        let result = unsafe { godot_main(c_args.len() as c_int, c_args.as_ptr()) };
132
133        Ok(result)
134    }
135
136    /// Check if the runtime is initialized
137    pub fn is_initialized(&self) -> bool {
138        self.initialized
139    }
140
141    /// Check if a scene tree is loaded (placeholder for now)
142    pub fn has_scene_tree(&self) -> bool {
143        // In a real implementation, this would check if scene tree is available
144        self.initialized
145    }
146}
147
148impl Default for LibgodotRuntime {
149    fn default() -> Self {
150        Self::new()
151    }
152}
153
154// Wrapper functions for libgodot callbacks
155#[allow(dead_code)]
156extern "C" fn gdextension_init_wrapper(
157    _get_proc_addr: Option<GDExtensionInterfaceGetProcAddress>,
158    library: GDExtensionClassLibraryPtr,
159    init: *mut GDExtensionInitialization,
160) -> c_int {
161    println!("GDExtension initialization callback called");
162
163    if let Some(init_ptr) = unsafe { init.as_mut() } {
164        init_ptr.minimum_initialization_level = GDExtensionInitializationLevel::Core;
165        init_ptr.userdata = library;
166        init_ptr.initialize = Some(default_initialize);
167        init_ptr.deinitialize = Some(default_deinitialize);
168    }
169
170    1 // Success
171}
172
173#[allow(dead_code)]
174extern "C" fn scene_load_wrapper(scene_tree: *mut c_void) {
175    println!("Scene tree loaded: {:?}", scene_tree);
176}
177
178// Default callback implementations
179extern "C" fn default_initialize(_userdata: *mut c_void, level: GDExtensionInitializationLevel) {
180    println!("Initializing GDExtension level: {:?}", level);
181}
182
183extern "C" fn default_deinitialize(_userdata: *mut c_void, level: GDExtensionInitializationLevel) {
184    println!("Deinitializing GDExtension level: {:?}", level);
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190
191    #[test]
192    fn test_runtime_creation() {
193        let runtime = LibgodotRuntime::new();
194        assert!(!runtime.is_initialized());
195        assert!(!runtime.has_scene_tree());
196    }
197
198    #[test]
199    fn test_gdextension_initialization_level() {
200        assert_eq!(GDExtensionInitializationLevel::Core as i32, 0);
201        assert_eq!(GDExtensionInitializationLevel::Scene as i32, 2);
202    }
203}