wormhole-engine 0.1.0

A portable, no-editor game engine with Rust core and Crystal scripting
Documentation
// FFI Bridge - Exposes engine functions to Crystal scripts via C ABI
// Uses callback pattern to avoid static state sharing issues

use std::sync::{Arc, Mutex};

// Engine state that's stored in the Rust binary (not in the library)
pub struct EngineState {
    rotation: Arc<Mutex<f32>>,
    pub(crate) input_state: Arc<Mutex<crate::input::InputState>>,
}

// Clone is safe because Arc<Mutex<T>> is cloneable
impl Clone for EngineState {
    fn clone(&self) -> Self {
        Self {
            rotation: self.rotation.clone(),
            input_state: self.input_state.clone(),
        }
    }
}

impl EngineState {
    pub fn new() -> Self {
        Self {
            rotation: Arc::new(Mutex::new(0.0)),
            input_state: Arc::new(Mutex::new(crate::input::InputState::new())),
        }
    }

    pub fn set_rotation(&self, angle: f32) {
        if let Ok(mut rot) = self.rotation.lock() {
            *rot = angle;
        }
    }

    pub fn get_rotation(&self) -> f32 {
        self.rotation.lock().map(|r| *r).unwrap_or(0.0)
    }

    pub fn is_key_pressed(&self, key_code: u32) -> bool {
        if let Ok(input) = self.input_state.lock() {
            if let Some(code) = crate::input::u32_to_key_code(key_code) {
                return input.is_key_pressed(code);
            }
        }
        false
    }

    pub fn get_mouse_position(&self) -> (f32, f32) {
        if let Ok(input) = self.input_state.lock() {
            return input.mouse_position();
        }
        (0.0, 0.0)
    }

    pub fn get_mouse_delta(&self) -> (f32, f32) {
        if let Ok(input) = self.input_state.lock() {
            return input.mouse_delta();
        }
        (0.0, 0.0)
    }
}

// Global state (this will be set by the engine)
static mut ENGINE_STATE_PTR: *mut EngineState = std::ptr::null_mut();

/// Register the engine state pointer (called from binary to set library's pointer)
#[no_mangle]
pub extern "C" fn engine_register_state_ptr(ptr: *mut EngineState) {
    unsafe {
        ENGINE_STATE_PTR = ptr;
    }
}

// Callback functions that Crystal can call
// These access the engine state via the global pointer
#[no_mangle]
pub extern "C" fn engine_set_rotation_callback(angle: f32) {
    unsafe {
        if !ENGINE_STATE_PTR.is_null() {
            (*ENGINE_STATE_PTR).set_rotation(angle);
        }
    }
}

#[no_mangle]
pub extern "C" fn engine_get_rotation_callback() -> f32 {
    unsafe {
        if !ENGINE_STATE_PTR.is_null() {
            (*ENGINE_STATE_PTR).get_rotation()
        } else {
            0.0
        }
    }
}

// Input query callbacks
#[no_mangle]
pub extern "C" fn engine_is_key_pressed(key_code: u32) -> bool {
    unsafe {
        if !ENGINE_STATE_PTR.is_null() {
            (*ENGINE_STATE_PTR).is_key_pressed(key_code)
        } else {
            false
        }
    }
}

#[no_mangle]
pub extern "C" fn engine_get_mouse_x() -> f32 {
    unsafe {
        if !ENGINE_STATE_PTR.is_null() {
            (*ENGINE_STATE_PTR).get_mouse_position().0
        } else {
            0.0
        }
    }
}

#[no_mangle]
pub extern "C" fn engine_get_mouse_y() -> f32 {
    unsafe {
        if !ENGINE_STATE_PTR.is_null() {
            (*ENGINE_STATE_PTR).get_mouse_position().1
        } else {
            0.0
        }
    }
}

#[no_mangle]
pub extern "C" fn engine_get_mouse_delta_x() -> f32 {
    unsafe {
        if !ENGINE_STATE_PTR.is_null() {
            (*ENGINE_STATE_PTR).get_mouse_delta().0
        } else {
            0.0
        }
    }
}

#[no_mangle]
pub extern "C" fn engine_get_mouse_delta_y() -> f32 {
    unsafe {
        if !ENGINE_STATE_PTR.is_null() {
            (*ENGINE_STATE_PTR).get_mouse_delta().1
        } else {
            0.0
        }
    }
}

// Camera control callbacks
static mut CAMERA_PTR: *mut crate::camera::Camera = std::ptr::null_mut();

/// Register the camera pointer (called from binary to set library's pointer)
#[no_mangle]
pub extern "C" fn engine_register_camera_ptr(ptr: *mut crate::camera::Camera) {
    unsafe {
        CAMERA_PTR = ptr;
        eprintln!("DEBUG: engine_register_camera_ptr called with ptr: {:p}", ptr);
    }
}

#[no_mangle]
pub extern "C" fn engine_camera_set_position(x: f32, y: f32, z: f32) {
    unsafe {
        if !CAMERA_PTR.is_null() {
            (*CAMERA_PTR).set_position(x, y, z);
        }
    }
}

#[no_mangle]
pub extern "C" fn engine_camera_set_rotation(pitch: f32, yaw: f32) {
    unsafe {
        if !CAMERA_PTR.is_null() {
            (*CAMERA_PTR).set_rotation(pitch, yaw);
        }
    }
}

#[no_mangle]
pub extern "C" fn engine_camera_translate(dx: f32, dy: f32, dz: f32) {
    unsafe {
        if CAMERA_PTR.is_null() {
            eprintln!("DEBUG: CAMERA_PTR is null in engine_camera_translate!");
            return;
        }
        (*CAMERA_PTR).translate(dx, dy, dz);
    }
}

#[no_mangle]
pub extern "C" fn engine_camera_set_fov(fov: f32) {
    unsafe {
        if !CAMERA_PTR.is_null() {
            (*CAMERA_PTR).set_fov(fov);
        }
    }
}

/// Initialize the FFI bridge with engine state
/// Returns the state and callback functions
pub fn init_bridge() -> EngineState {
    let state = EngineState::new();
    let state_ptr = Box::leak(Box::new(EngineState {
        rotation: state.rotation.clone(),
        input_state: state.input_state.clone(),
    }));
    unsafe {
        ENGINE_STATE_PTR = state_ptr;
    }
    state
}

/// Internal function to get rotation (called from Rust engine)
pub fn get_rotation(state: &EngineState) -> f32 {
    state.get_rotation()
}