godot-testability-runtime 0.1.2

Embedded Godot runtime for comprehensive Rust testing
Documentation
//! Godot runtime management for embedded testing.
//!
//! This module provides utilities for managing a Godot runtime instance
//! within the test environment. It's inspired by the SwiftGodot approach
//! but adapted for Rust and the current godot-rust ecosystem.

use crate::error::{TestError, TestResult};
use once_cell::sync::Lazy;
use parking_lot::Mutex;
use std::sync::Arc;
use tracing::{error, info};

// Type aliases for FFI pointers
type GDExtensionInterfaceGetProcAddress = *const std::ffi::c_void;
type GDExtensionClassLibraryPtr = *mut std::ffi::c_void;
type SceneTreePtr = *mut std::ffi::c_void;

// Global callbacks provided by the user
static USER_CALLBACKS: Mutex<Option<UserCallbacks>> = Mutex::new(None);

// Global scene callback to be executed when SceneTree is ready
#[allow(clippy::type_complexity)]
static SCENE_CALLBACK: Mutex<Option<Box<dyn FnOnce(SceneTreePtr) -> TestResult<()> + Send>>> =
    Mutex::new(None);

/// Callbacks that users must provide to integrate with their godot-rust version
pub struct UserCallbacks {
    /// Initialize godot-rust FFI
    pub initialize_ffi:
        fn(GDExtensionInterfaceGetProcAddress, GDExtensionClassLibraryPtr) -> Result<(), String>,
    /// Load class method table for given init level
    pub load_class_method_table: fn(u32),
    /// Register classes for given init level (optional)
    pub register_classes: Option<fn(u32)>,
}

/// Global state for the embedded Godot runtime.
static RUNTIME_STATE: Lazy<Arc<Mutex<RuntimeState>>> =
    Lazy::new(|| Arc::new(Mutex::new(RuntimeState::new())));

/// Internal state of the Godot runtime.
#[derive(Debug)]
struct RuntimeState {
    initialized: bool,
    running: bool,
}

impl RuntimeState {
    fn new() -> Self {
        Self {
            initialized: false,
            running: false,
        }
    }
}

/// Configuration options for the Godot runtime.
#[derive(Debug, Clone)]
pub struct RuntimeConfig {
    /// Run Godot in headless mode (no visual output).
    pub headless: bool,
    /// Enable verbose logging from Godot.
    pub verbose: bool,
    /// Custom command line arguments to pass to Godot.
    pub custom_args: Vec<String>,
}

impl Default for RuntimeConfig {
    fn default() -> Self {
        Self {
            headless: true,
            verbose: false,
            custom_args: Vec::new(),
        }
    }
}

/// Manager for the embedded Godot runtime.
///
/// This provides a safe interface for initializing, managing, and shutting down
/// a Godot runtime instance for testing purposes. The runtime is designed to be
/// lightweight and suitable for automated testing environments.
pub struct GodotRuntime;

#[cfg(feature = "embedded_runtime")]
impl GodotRuntime {
    /// Check if the Godot runtime is currently initialized.
    pub fn is_initialized() -> bool {
        RUNTIME_STATE.lock().initialized
    }

    /// Check if the Godot runtime is currently running.
    pub fn is_running() -> bool {
        RUNTIME_STATE.lock().running
    }

    /// Shut down the Godot runtime.
    ///
    /// This resets the runtime state. Actual cleanup is handled by run_godot.
    pub fn shutdown() -> TestResult<()> {
        let mut state = RUNTIME_STATE.lock();
        if !state.initialized {
            return Ok(());
        }

        info!("Shutting down Godot runtime");
        state.running = false;
        state.initialized = false;
        Ok(())
    }

    /// Run Godot with SwiftGodot-style initialization.
    ///
    /// The load_scene callback receives a raw SceneTree pointer.
    /// Users are responsible for converting this to their Godot type.
    pub fn run_godot<F>(
        _config: RuntimeConfig,
        callbacks: UserCallbacks,
        load_scene: F,
    ) -> TestResult<i32>
    where
        F: FnOnce(SceneTreePtr) -> TestResult<()> + Send + 'static,
    {
        use crate::ffi::{
            libgodot_gdextension_bind, GDExtensionClassLibraryPtr, GDExtensionInitialization,
            GDExtensionInitializationLevel, GDExtensionInterfaceGetProcAddress,
        };
        use std::ffi::c_void;

        info!("Starting Godot runtime with SwiftGodot-style initialization");

        // Store callbacks globally
        {
            USER_CALLBACKS.lock().replace(callbacks);
            SCENE_CALLBACK.lock().replace(Box::new(load_scene));
        }

        extern "C" fn initialization_callback(
            get_proc_addr: Option<GDExtensionInterfaceGetProcAddress>,
            library: GDExtensionClassLibraryPtr,
            r_initialization: *mut GDExtensionInitialization,
        ) -> i32 {
            if get_proc_addr.is_none() || library.is_null() {
                return 0;
            }

            unsafe {
                if !r_initialization.is_null() {
                    (*r_initialization).minimum_initialization_level =
                        GDExtensionInitializationLevel::Core;
                    (*r_initialization).userdata = library;
                    (*r_initialization).initialize = Some(godot_rust_bridge_initialize);
                    (*r_initialization).deinitialize = Some(godot_rust_bridge_deinitialize);
                }

                // Initialize godot-rust FFI using user's callback
                if let Some(callbacks) = USER_CALLBACKS.lock().as_ref() {
                    if let Some(get_proc_address_fn) = get_proc_addr {
                        if let Err(e) = (callbacks.initialize_ffi)(
                            get_proc_address_fn as *const c_void,
                            library,
                        ) {
                            error!("Failed to initialize godot-rust FFI: {}", e);
                            return 0;
                        }
                    }
                }
            }
            1
        }

        extern "C" fn scene_callback(scene_tree_ptr: *mut c_void) {
            info!("Scene tree ready - Godot engine available");
            if !scene_tree_ptr.is_null() {
                if let Some(callback) = SCENE_CALLBACK.lock().take() {
                    info!("Executing test callback with SceneTree pointer");
                    match callback(scene_tree_ptr) {
                        Ok(()) => {
                            info!("Test callback executed successfully");
                        }
                        Err(e) => {
                            error!("Test callback failed: {:?}", e);
                        }
                    }
                } else {
                    info!("No test callback to execute");
                }
            } else {
                error!("Scene tree pointer is null!");
            }
        }

        unsafe {
            libgodot_gdextension_bind(initialization_callback, Some(scene_callback));
        }

        std::env::set_var("__CFBundleIdentifier", "GodotBevyKit");

        let args = vec![
            "GodotBevyKit".to_string(),
            "--headless".to_string(),
            "--verbose".to_string(),
        ];

        let mut runtime = crate::ffi::LibgodotRuntime::new();
        runtime
            .initialize()
            .map_err(TestError::RuntimeInitialization)?;

        info!("Starting godot_main");
        let result = runtime
            .run_main(&args)
            .map_err(TestError::RuntimeInitialization)?;

        info!("Godot main loop finished with exit code: {}", result);
        Ok(result)
    }
}

extern "C" fn godot_rust_bridge_initialize(
    _userdata: *mut std::ffi::c_void,
    level: crate::ffi::GDExtensionInitializationLevel,
) {
    info!("Godot-Rust bridge initialize (level: {:?})", level);

    // Map to u32 for the user callback
    let init_level = match level {
        crate::ffi::GDExtensionInitializationLevel::Core => 0,
        crate::ffi::GDExtensionInitializationLevel::Servers => 1,
        crate::ffi::GDExtensionInitializationLevel::Scene => 2,
        crate::ffi::GDExtensionInitializationLevel::Editor => 3,
        _ => return,
    };

    // Call user's load_class_method_table
    if let Some(callbacks) = USER_CALLBACKS.lock().as_ref() {
        (callbacks.load_class_method_table)(init_level);

        // Call optional class registration
        if let Some(register_classes) = callbacks.register_classes {
            register_classes(init_level);
        }
    }
}

extern "C" fn godot_rust_bridge_deinitialize(
    _userdata: *mut std::ffi::c_void,
    level: crate::ffi::GDExtensionInitializationLevel,
) {
    info!("Godot-Rust bridge deinitialize (level: {:?})", level);
}