use crate::error::{TestError, TestResult};
use once_cell::sync::Lazy;
use parking_lot::Mutex;
use std::sync::Arc;
use tracing::{error, info};
type GDExtensionInterfaceGetProcAddress = *const std::ffi::c_void;
type GDExtensionClassLibraryPtr = *mut std::ffi::c_void;
type SceneTreePtr = *mut std::ffi::c_void;
static USER_CALLBACKS: Mutex<Option<UserCallbacks>> = Mutex::new(None);
#[allow(clippy::type_complexity)]
static SCENE_CALLBACK: Mutex<Option<Box<dyn FnOnce(SceneTreePtr) -> TestResult<()> + Send>>> =
Mutex::new(None);
pub struct UserCallbacks {
pub initialize_ffi:
fn(GDExtensionInterfaceGetProcAddress, GDExtensionClassLibraryPtr) -> Result<(), String>,
pub load_class_method_table: fn(u32),
pub register_classes: Option<fn(u32)>,
}
static RUNTIME_STATE: Lazy<Arc<Mutex<RuntimeState>>> =
Lazy::new(|| Arc::new(Mutex::new(RuntimeState::new())));
#[derive(Debug)]
struct RuntimeState {
initialized: bool,
running: bool,
}
impl RuntimeState {
fn new() -> Self {
Self {
initialized: false,
running: false,
}
}
}
#[derive(Debug, Clone)]
pub struct RuntimeConfig {
pub headless: bool,
pub verbose: bool,
pub custom_args: Vec<String>,
}
impl Default for RuntimeConfig {
fn default() -> Self {
Self {
headless: true,
verbose: false,
custom_args: Vec::new(),
}
}
}
pub struct GodotRuntime;
#[cfg(feature = "embedded_runtime")]
impl GodotRuntime {
pub fn is_initialized() -> bool {
RUNTIME_STATE.lock().initialized
}
pub fn is_running() -> bool {
RUNTIME_STATE.lock().running
}
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(())
}
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");
{
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);
}
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);
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,
};
if let Some(callbacks) = USER_CALLBACKS.lock().as_ref() {
(callbacks.load_class_method_table)(init_level);
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);
}