wormhole/
ffi_bridge.rs

1// FFI Bridge - Exposes engine functions to Crystal scripts via C ABI
2// Uses callback pattern to avoid static state sharing issues
3
4use std::sync::{Arc, Mutex};
5
6// Engine state that's stored in the Rust binary (not in the library)
7pub struct EngineState {
8    rotation: Arc<Mutex<f32>>,
9    pub(crate) input_state: Arc<Mutex<crate::input::InputState>>,
10}
11
12// Clone is safe because Arc<Mutex<T>> is cloneable
13impl Clone for EngineState {
14    fn clone(&self) -> Self {
15        Self {
16            rotation: self.rotation.clone(),
17            input_state: self.input_state.clone(),
18        }
19    }
20}
21
22impl EngineState {
23    pub fn new() -> Self {
24        Self {
25            rotation: Arc::new(Mutex::new(0.0)),
26            input_state: Arc::new(Mutex::new(crate::input::InputState::new())),
27        }
28    }
29
30    pub fn set_rotation(&self, angle: f32) {
31        if let Ok(mut rot) = self.rotation.lock() {
32            *rot = angle;
33        }
34    }
35
36    pub fn get_rotation(&self) -> f32 {
37        self.rotation.lock().map(|r| *r).unwrap_or(0.0)
38    }
39
40    pub fn is_key_pressed(&self, key_code: u32) -> bool {
41        if let Ok(input) = self.input_state.lock() {
42            if let Some(code) = crate::input::u32_to_key_code(key_code) {
43                return input.is_key_pressed(code);
44            }
45        }
46        false
47    }
48
49    pub fn get_mouse_position(&self) -> (f32, f32) {
50        if let Ok(input) = self.input_state.lock() {
51            return input.mouse_position();
52        }
53        (0.0, 0.0)
54    }
55
56    pub fn get_mouse_delta(&self) -> (f32, f32) {
57        if let Ok(input) = self.input_state.lock() {
58            return input.mouse_delta();
59        }
60        (0.0, 0.0)
61    }
62}
63
64// Global state (this will be set by the engine)
65static mut ENGINE_STATE_PTR: *mut EngineState = std::ptr::null_mut();
66
67/// Register the engine state pointer (called from binary to set library's pointer)
68#[no_mangle]
69pub extern "C" fn engine_register_state_ptr(ptr: *mut EngineState) {
70    unsafe {
71        ENGINE_STATE_PTR = ptr;
72    }
73}
74
75// Callback functions that Crystal can call
76// These access the engine state via the global pointer
77#[no_mangle]
78pub extern "C" fn engine_set_rotation_callback(angle: f32) {
79    unsafe {
80        if !ENGINE_STATE_PTR.is_null() {
81            (*ENGINE_STATE_PTR).set_rotation(angle);
82        }
83    }
84}
85
86#[no_mangle]
87pub extern "C" fn engine_get_rotation_callback() -> f32 {
88    unsafe {
89        if !ENGINE_STATE_PTR.is_null() {
90            (*ENGINE_STATE_PTR).get_rotation()
91        } else {
92            0.0
93        }
94    }
95}
96
97// Input query callbacks
98#[no_mangle]
99pub extern "C" fn engine_is_key_pressed(key_code: u32) -> bool {
100    unsafe {
101        if !ENGINE_STATE_PTR.is_null() {
102            (*ENGINE_STATE_PTR).is_key_pressed(key_code)
103        } else {
104            false
105        }
106    }
107}
108
109#[no_mangle]
110pub extern "C" fn engine_get_mouse_x() -> f32 {
111    unsafe {
112        if !ENGINE_STATE_PTR.is_null() {
113            (*ENGINE_STATE_PTR).get_mouse_position().0
114        } else {
115            0.0
116        }
117    }
118}
119
120#[no_mangle]
121pub extern "C" fn engine_get_mouse_y() -> f32 {
122    unsafe {
123        if !ENGINE_STATE_PTR.is_null() {
124            (*ENGINE_STATE_PTR).get_mouse_position().1
125        } else {
126            0.0
127        }
128    }
129}
130
131#[no_mangle]
132pub extern "C" fn engine_get_mouse_delta_x() -> f32 {
133    unsafe {
134        if !ENGINE_STATE_PTR.is_null() {
135            (*ENGINE_STATE_PTR).get_mouse_delta().0
136        } else {
137            0.0
138        }
139    }
140}
141
142#[no_mangle]
143pub extern "C" fn engine_get_mouse_delta_y() -> f32 {
144    unsafe {
145        if !ENGINE_STATE_PTR.is_null() {
146            (*ENGINE_STATE_PTR).get_mouse_delta().1
147        } else {
148            0.0
149        }
150    }
151}
152
153// Camera control callbacks
154static mut CAMERA_PTR: *mut crate::camera::Camera = std::ptr::null_mut();
155
156/// Register the camera pointer (called from binary to set library's pointer)
157#[no_mangle]
158pub extern "C" fn engine_register_camera_ptr(ptr: *mut crate::camera::Camera) {
159    unsafe {
160        CAMERA_PTR = ptr;
161        eprintln!("DEBUG: engine_register_camera_ptr called with ptr: {:p}", ptr);
162    }
163}
164
165#[no_mangle]
166pub extern "C" fn engine_camera_set_position(x: f32, y: f32, z: f32) {
167    unsafe {
168        if !CAMERA_PTR.is_null() {
169            (*CAMERA_PTR).set_position(x, y, z);
170        }
171    }
172}
173
174#[no_mangle]
175pub extern "C" fn engine_camera_set_rotation(pitch: f32, yaw: f32) {
176    unsafe {
177        if !CAMERA_PTR.is_null() {
178            (*CAMERA_PTR).set_rotation(pitch, yaw);
179        }
180    }
181}
182
183#[no_mangle]
184pub extern "C" fn engine_camera_translate(dx: f32, dy: f32, dz: f32) {
185    unsafe {
186        if CAMERA_PTR.is_null() {
187            eprintln!("DEBUG: CAMERA_PTR is null in engine_camera_translate!");
188            return;
189        }
190        (*CAMERA_PTR).translate(dx, dy, dz);
191    }
192}
193
194#[no_mangle]
195pub extern "C" fn engine_camera_set_fov(fov: f32) {
196    unsafe {
197        if !CAMERA_PTR.is_null() {
198            (*CAMERA_PTR).set_fov(fov);
199        }
200    }
201}
202
203/// Initialize the FFI bridge with engine state
204/// Returns the state and callback functions
205pub fn init_bridge() -> EngineState {
206    let state = EngineState::new();
207    let state_ptr = Box::leak(Box::new(EngineState {
208        rotation: state.rotation.clone(),
209        input_state: state.input_state.clone(),
210    }));
211    unsafe {
212        ENGINE_STATE_PTR = state_ptr;
213    }
214    state
215}
216
217/// Internal function to get rotation (called from Rust engine)
218pub fn get_rotation(state: &EngineState) -> f32 {
219    state.get_rotation()
220}