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*/