godot-testability-runtime 0.1.2

Embedded Godot runtime for comprehensive Rust testing
Documentation
//! FFI bindings to libgodot for embedded runtime support.
//!
//! This module provides Rust bindings to the libgodot library, enabling
//! embedded Godot runtime execution for testing purposes.

use std::ffi::{c_char, c_int, c_void};

/// GDExtension initialization levels
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GDExtensionInitializationLevel {
    Core = 0,
    Servers = 1,
    Scene = 2,
    Editor = 3,
    MaxInitializationLevel = 4,
}

/// GDExtension boolean type
pub type GDExtensionBool = u8;

/// Opaque pointer to GDExtension class library
pub type GDExtensionClassLibraryPtr = *mut c_void;

/// Function pointer for getting GDExtension interface functions
pub type GDExtensionInterfaceGetProcAddress =
    unsafe extern "C" fn(function_name: *const c_char) -> Option<unsafe extern "C" fn()>;

/// GDExtension initialization structure
#[repr(C)]
#[derive(Debug)]
pub struct GDExtensionInitialization {
    /// Minimum initialization level required
    pub minimum_initialization_level: GDExtensionInitializationLevel,
    /// User data pointer
    pub userdata: *mut c_void,
    /// Initialize function called for each initialization level
    pub initialize:
        Option<extern "C" fn(userdata: *mut c_void, level: GDExtensionInitializationLevel)>,
    /// Deinitialize function called for each initialization level
    pub deinitialize:
        Option<extern "C" fn(userdata: *mut c_void, level: GDExtensionInitializationLevel)>,
}

/// GDExtension initialization function signature
pub type GDExtensionInitializationFunction = extern "C" fn(
    get_proc_address: GDExtensionInterfaceGetProcAddress,
    library: GDExtensionClassLibraryPtr,
    initialization: *mut GDExtensionInitialization,
) -> GDExtensionBool;

/// Godot version structure
#[repr(C)]
#[derive(Debug)]
pub struct GDExtensionGodotVersion {
    pub major: u32,
    pub minor: u32,
    pub patch: u32,
    pub string: *const c_char,
}

/// Callback function type for libgodot_gdextension_bind
pub type LibgodotGDExtensionBindCallback = extern "C" fn(
    get_proc_address: Option<GDExtensionInterfaceGetProcAddress>,
    library: GDExtensionClassLibraryPtr,
    initialization: *mut GDExtensionInitialization,
) -> c_int;

/// Callback function type for scene loading
pub type LibgodotSceneCallback = extern "C" fn(scene_tree: *mut c_void);

// FFI bindings to libgodot functions
extern "C" {
    /// Initialize libgodot resources
    pub fn libgodot_init_resource();

    /// Load a scene into the scene tree
    pub fn libgodot_scene_load(scene: *mut c_void);

    /// Check if a scene is loadable
    pub fn libgodot_is_scene_loadable() -> c_int;

    /// Main GDExtension binding function used by language bindings
    pub fn libgodot_gdextension_bind(
        initialization_bind: LibgodotGDExtensionBindCallback,
        scene_function_bind: Option<LibgodotSceneCallback>,
    );

    /// Main Godot entry point
    pub fn godot_main(argc: c_int, argv: *const *const c_char) -> c_int;
}

/// Safe wrapper around libgodot FFI functions
#[derive(Debug)]
pub struct LibgodotRuntime {
    initialized: bool,
    // Note: We can't store raw pointers in a Send/Sync struct safely
    // Instead, we'll manage scene tree access through static callbacks
}

impl LibgodotRuntime {
    /// Create a new libgodot runtime instance
    pub fn new() -> Self {
        Self { initialized: false }
    }

    /// Initialize the libgodot runtime
    pub fn initialize(&mut self) -> Result<(), String> {
        if self.initialized {
            return Err("Runtime already initialized".to_string());
        }

        self.initialized = true;
        Ok(())
    }

    /// Start the main Godot loop with the provided arguments
    pub fn run_main(&mut self, args: &[String]) -> Result<i32, String> {
        if !self.initialized {
            return Err("Runtime not initialized".to_string());
        }

        // Convert Rust strings to C strings
        let c_strings: Vec<std::ffi::CString> = args
            .iter()
            .map(|s| std::ffi::CString::new(s.as_str()).unwrap())
            .collect();

        let c_args: Vec<*const c_char> = c_strings.iter().map(|s| s.as_ptr()).collect();

        let result = unsafe { godot_main(c_args.len() as c_int, c_args.as_ptr()) };

        Ok(result)
    }

    /// Check if the runtime is initialized
    pub fn is_initialized(&self) -> bool {
        self.initialized
    }

    /// Check if a scene tree is loaded (placeholder for now)
    pub fn has_scene_tree(&self) -> bool {
        // In a real implementation, this would check if scene tree is available
        self.initialized
    }
}

impl Default for LibgodotRuntime {
    fn default() -> Self {
        Self::new()
    }
}

// Wrapper functions for libgodot callbacks
#[allow(dead_code)]
extern "C" fn gdextension_init_wrapper(
    _get_proc_addr: Option<GDExtensionInterfaceGetProcAddress>,
    library: GDExtensionClassLibraryPtr,
    init: *mut GDExtensionInitialization,
) -> c_int {
    println!("GDExtension initialization callback called");

    if let Some(init_ptr) = unsafe { init.as_mut() } {
        init_ptr.minimum_initialization_level = GDExtensionInitializationLevel::Core;
        init_ptr.userdata = library;
        init_ptr.initialize = Some(default_initialize);
        init_ptr.deinitialize = Some(default_deinitialize);
    }

    1 // Success
}

#[allow(dead_code)]
extern "C" fn scene_load_wrapper(scene_tree: *mut c_void) {
    println!("Scene tree loaded: {:?}", scene_tree);
}

// Default callback implementations
extern "C" fn default_initialize(_userdata: *mut c_void, level: GDExtensionInitializationLevel) {
    println!("Initializing GDExtension level: {:?}", level);
}

extern "C" fn default_deinitialize(_userdata: *mut c_void, level: GDExtensionInitializationLevel) {
    println!("Deinitializing GDExtension level: {:?}", level);
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_runtime_creation() {
        let runtime = LibgodotRuntime::new();
        assert!(!runtime.is_initialized());
        assert!(!runtime.has_scene_tree());
    }

    #[test]
    fn test_gdextension_initialization_level() {
        assert_eq!(GDExtensionInitializationLevel::Core as i32, 0);
        assert_eq!(GDExtensionInitializationLevel::Scene as i32, 2);
    }
}