Skip to main content

ursus_core/
ffi.rs

1// TODO(ffi): temporarily disabled - the core's public API is not stable yet
2// and is still being actively refactored (see commit history). Uncomment
3// and update to the current API once:
4//   - the public API (App/EngineContext/components, etc.) stabilizes
5//     across releases
6//   - there is an actual consumer (C/C#/etc. wrapper)
7
8/*use crate::app::EngineContext;
9use crate::components::mesh::MeshHandle;
10use crate::components::transform::Transform;
11use std::ffi::c_void;
12
13pub struct EngineHandle {
14    pub(crate) title: String,
15    pub(crate) width: u32,
16    pub(crate) height: u32,
17    pub(crate) validation: bool,
18    pub(crate) callbacks: Option<AppCallbacksOwned>,
19    pub(crate) clear_color: [f32; 4],
20    pub(crate) ctx: *mut EngineContext,
21}
22
23#[repr(C)]
24pub struct EngineCallbacks {
25    pub on_start: Option<unsafe extern "C" fn(handle: *mut EngineHandle, userdata: *mut c_void)>,
26    pub on_update: Option<unsafe extern "C" fn(handle: *mut EngineHandle, userdata: *mut c_void, dt: f32)>,
27    pub on_stop: Option<unsafe extern "C" fn(handle: *mut EngineHandle, userdata: *mut c_void)>,
28    pub userdata: *mut c_void,
29}
30
31pub(crate) struct AppCallbacksOwned {
32    pub on_start: Option<unsafe extern "C" fn(*mut EngineHandle, *mut c_void)>,
33    pub on_update: Option<unsafe extern "C" fn(*mut EngineHandle, *mut c_void, f32)>,
34    pub on_stop: Option<unsafe extern "C" fn(*mut EngineHandle, *mut c_void)>,
35    pub userdata: *mut c_void,
36}
37
38unsafe impl Send for AppCallbacksOwned {}
39unsafe impl Sync for AppCallbacksOwned {}
40
41#[unsafe(no_mangle)]
42pub extern "C" fn engine_create() -> *mut EngineHandle {
43    Box::into_raw(Box::new(EngineHandle {
44        title: "ursus-core".to_string(),
45        width: 1280,
46        height: 720,
47        validation: cfg!(debug_assertions),
48        callbacks: None,
49        clear_color: [0.05, 0.05, 0.1, 1.0],
50        ctx: std::ptr::null_mut(),
51    }))
52}
53
54#[unsafe(no_mangle)]
55pub unsafe extern "C" fn engine_destroy(handle: *mut EngineHandle) {
56    if !handle.is_null() {
57        drop(unsafe { Box::from_raw(handle) });
58    }
59}
60
61#[unsafe(no_mangle)]
62pub unsafe extern "C" fn engine_set_title(handle: *mut EngineHandle, title: *const std::ffi::c_char) {
63    if handle.is_null() || title.is_null() {
64        return;
65    }
66    if let Ok(s) = unsafe { std::ffi::CStr::from_ptr(title) }.to_str() {
67        unsafe { (*handle).title = s.to_string() };
68    }
69}
70
71#[unsafe(no_mangle)]
72pub unsafe extern "C" fn engine_set_size(handle: *mut EngineHandle, width: u32, height: u32) {
73    if handle.is_null() {
74        return;
75    }
76    unsafe {
77        (*handle).width = width;
78        (*handle).height = height;
79    }
80}
81
82#[unsafe(no_mangle)]
83pub unsafe extern "C" fn engine_set_validation(handle: *mut EngineHandle, enabled: bool) {
84    if handle.is_null() {
85        return;
86    }
87    unsafe { (*handle).validation = enabled };
88}
89
90#[unsafe(no_mangle)]
91pub unsafe extern "C" fn engine_set_clear_color(handle: *mut EngineHandle, r: f32, g: f32, b: f32, a: f32) {
92    if handle.is_null() {
93        return;
94    }
95    unsafe { (*handle).clear_color = [r, g, b, a] };
96}
97
98#[unsafe(no_mangle)]
99pub unsafe extern "C" fn engine_run(handle: *mut EngineHandle, callbacks: *const EngineCallbacks) -> i32 {
100    if handle.is_null() {
101        return -1;
102    }
103
104    if !callbacks.is_null() {
105        let cb = unsafe { &*callbacks };
106        unsafe {
107            (*handle).callbacks = Some(AppCallbacksOwned {
108                on_start: cb.on_start,
109                on_update: cb.on_update,
110                on_stop: cb.on_stop,
111                userdata: cb.userdata,
112            });
113        }
114    }
115
116    let app = FfiApp { handle };
117    match crate::app::Engine::run(app) {
118        Ok(_) => 0,
119        Err(e) => {
120            eprintln!("[ursus-core] engine_run failed: {e}");
121            -1
122        }
123    }
124}
125
126#[unsafe(no_mangle)]
127pub unsafe extern "C" fn engine_spawn_mesh(handle: *mut EngineHandle, mesh_id: u32, x: f32, y: f32, z: f32) -> u64 {
128    let ctx = ctx_mut(handle);
129    if ctx.is_null() {
130        return u64::MAX;
131    }
132    let entity = unsafe { (*ctx).world.spawn().insert(MeshHandle(mesh_id)).insert(Transform::at(x, y, z)).build() };
133    entity.id() as u64
134}
135
136#[unsafe(no_mangle)]
137pub unsafe extern "C" fn engine_despawn(handle: *mut EngineHandle, entity_id: u64) {
138    let ctx = ctx_mut(handle);
139    if ctx.is_null() {
140        return;
141    }
142    let entity = hecs::Entity::from_bits(entity_id).unwrap();
143    unsafe {
144        let _ = (*ctx).world.despawn(entity);
145    }
146}
147
148#[unsafe(no_mangle)]
149pub unsafe extern "C" fn engine_set_transform(
150    handle: *mut EngineHandle,
151    entity_id: u64,
152    x: f32,
153    y: f32,
154    z: f32,
155    scale: f32,
156) {
157    let ctx = ctx_mut(handle);
158    if ctx.is_null() {
159        return;
160    }
161    let entity = hecs::Entity::from_bits(entity_id).unwrap();
162    unsafe {
163        if let Ok(mut t) = (*ctx).world.inner.get::<&mut Transform>(entity) {
164            t.position = glam::Vec3::new(x, y, z);
165            t.scale = glam::Vec3::splat(scale);
166        }
167    }
168}
169
170#[unsafe(no_mangle)]
171pub unsafe extern "C" fn engine_mesh_cube() -> u32 {
172    1
173}
174#[unsafe(no_mangle)]
175pub unsafe extern "C" fn engine_mesh_triangle() -> u32 {
176    0
177}
178#[unsafe(no_mangle)]
179pub unsafe extern "C" fn engine_mesh_plane() -> u32 {
180    2
181}
182
183struct FfiApp {
184    handle: *mut EngineHandle,
185}
186unsafe impl Send for FfiApp {}
187
188impl FfiApp {
189    fn callbacks(&self) -> Option<&AppCallbacksOwned> {
190        if self.handle.is_null() {
191            return None;
192        }
193        unsafe { (*self.handle).callbacks.as_ref() }
194    }
195}
196
197impl crate::app::App for FfiApp {
198    fn on_start(&mut self, ctx: &mut EngineContext) {
199        unsafe { (*self.handle).ctx = ctx as *mut EngineContext };
200
201        if let Some(cb) = self.callbacks() {
202            if let Some(f) = cb.on_start {
203                unsafe { f(self.handle, cb.userdata) };
204            }
205        }
206    }
207
208    fn on_update(&mut self, ctx: &mut EngineContext, dt: f32) {
209        unsafe { (*self.handle).ctx = ctx as *mut EngineContext };
210
211        if let Some(cb) = self.callbacks() {
212            if let Some(f) = cb.on_update {
213                unsafe { f(self.handle, cb.userdata, dt) };
214            }
215        }
216    }
217
218    fn on_render(&mut self, _ctx: &mut EngineContext) {}
219
220    fn on_stop(&mut self, ctx: &mut EngineContext) {
221        let _ = ctx;
222        unsafe { (*self.handle).ctx = std::ptr::null_mut() };
223
224        if let Some(cb) = self.callbacks() {
225            if let Some(f) = cb.on_stop {
226                unsafe { f(self.handle, cb.userdata) };
227            }
228        }
229    }
230}
231
232fn ctx_mut(handle: *mut EngineHandle) -> *mut EngineContext {
233    if handle.is_null() {
234        return std::ptr::null_mut();
235    }
236    let ctx = unsafe { (*handle).ctx };
237    if ctx.is_null() {
238        eprintln!("[ursus-core] API called outside of on_start/on_update");
239    }
240    ctx
241}
242*/