modelio/
asset_resolver.rs1use std::ffi::CStr;
2use std::panic::AssertUnwindSafe;
3use std::ptr;
4
5use crate::asset::Asset;
6use crate::error::Result;
7use crate::ffi;
8use crate::handle::ObjectHandle;
9use crate::util::{c_string, required_handle, take_string};
10
11type AssetResolverCallbackFn =
12 dyn Fn(AssetResolverEvent) -> AssetResolverResponse + Send + Sync + 'static;
13
14struct AssetResolverCallback {
15 callback: Box<AssetResolverCallbackFn>,
16}
17
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub enum AssetResolverEvent {
21 CanResolveAssetNamed(String),
23 ResolveAssetNamed(String),
25}
26
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub enum AssetResolverResponse {
30 Bool(bool),
32 Url(Option<String>),
34}
35
36fn callback_name(name: *const core::ffi::c_char) -> Option<String> {
37 (!name.is_null()).then(|| {
38 unsafe { CStr::from_ptr(name) }.to_string_lossy().into_owned()
40 })
41}
42
43fn duplicate_c_string(value: &str) -> *mut core::ffi::c_char {
44 let Ok(value) = std::ffi::CString::new(value) else {
45 return ptr::null_mut();
46 };
47 unsafe { libc::strdup(value.as_ptr()) }
49}
50
51fn callback_response(
52 context: *mut core::ffi::c_void,
53 event: AssetResolverEvent,
54) -> Option<AssetResolverResponse> {
55 let context = (!context.is_null()).then_some(context.cast::<AssetResolverCallback>())?;
56 std::panic::catch_unwind(AssertUnwindSafe(|| {
57 (unsafe { &*context }.callback)(event)
59 }))
60 .ok()
61}
62
63#[no_mangle]
64pub extern "C" fn mdlx_asset_resolver_can_resolve_named(
65 context: *mut core::ffi::c_void,
66 name: *const core::ffi::c_char,
67) -> i32 {
68 let Some(name) = callback_name(name) else {
69 return 0;
70 };
71 match callback_response(context, AssetResolverEvent::CanResolveAssetNamed(name)) {
72 Some(AssetResolverResponse::Bool(can_resolve)) => i32::from(can_resolve),
73 _ => 0,
74 }
75}
76
77#[no_mangle]
78pub extern "C" fn mdlx_asset_resolver_resolve_named(
79 context: *mut core::ffi::c_void,
80 name: *const core::ffi::c_char,
81) -> *mut core::ffi::c_char {
82 let Some(name) = callback_name(name) else {
83 return ptr::null_mut();
84 };
85 match callback_response(context, AssetResolverEvent::ResolveAssetNamed(name)) {
86 Some(AssetResolverResponse::Url(Some(url))) => duplicate_c_string(&url),
87 _ => ptr::null_mut(),
88 }
89}
90
91#[no_mangle]
92pub extern "C" fn mdlx_asset_resolver_release(context: *mut core::ffi::c_void) {
93 if context.is_null() {
94 return;
95 }
96 unsafe { drop(Box::from_raw(context.cast::<AssetResolverCallback>())) };
98}
99
100fn release_callback_context(context: *mut core::ffi::c_void) {
101 mdlx_asset_resolver_release(context);
102}
103
104#[derive(Debug, Clone)]
105pub struct AssetResolver {
107 handle: ObjectHandle,
108}
109
110impl AssetResolver {
111 pub fn new<F>(callback: F) -> Result<Self>
113 where
114 F: Fn(AssetResolverEvent) -> AssetResolverResponse + Send + Sync + 'static,
115 {
116 let callback = Box::new(AssetResolverCallback {
117 callback: Box::new(callback),
118 });
119 let callback_ptr = Box::into_raw(callback).cast::<core::ffi::c_void>();
120 let mut out_resolver = ptr::null_mut();
121 let mut out_error = ptr::null_mut();
122 let status = unsafe {
124 ffi::mdl_asset_resolver_new_with_callback(
125 callback_ptr,
126 &mut out_resolver,
127 &mut out_error,
128 )
129 };
130 if let Err(error) = crate::util::status_result(status, out_error) {
131 release_callback_context(callback_ptr);
132 return Err(error);
133 }
134 match required_handle(out_resolver, "MDLAssetResolver") {
135 Ok(handle) => Ok(Self::from_handle(handle)),
136 Err(error) => {
137 release_callback_context(callback_ptr);
138 Err(error)
139 }
140 }
141 }
142
143 pub(crate) fn from_handle(handle: ObjectHandle) -> Self {
145 Self { handle }
146 }
147
148 pub(crate) fn as_ptr(&self) -> *mut core::ffi::c_void {
150 self.handle.as_ptr()
151 }
152
153 pub fn can_resolve_asset_named(&self, name: &str) -> Result<bool> {
155 let name = c_string(name)?;
156 Ok(unsafe { ffi::mdl_asset_resolver_can_resolve_named(self.as_ptr(), name.as_ptr()) != 0 })
158 }
159
160 pub fn resolve_asset_named(&self, name: &str) -> Result<Option<String>> {
162 let name = c_string(name)?;
163 Ok(take_string(unsafe {
165 ffi::mdl_asset_resolver_resolve_named(self.as_ptr(), name.as_ptr())
166 }))
167 }
168}
169
170#[derive(Debug, Clone)]
171pub struct PathAssetResolver {
173 handle: ObjectHandle,
174}
175
176impl PathAssetResolver {
177 pub(crate) fn from_handle(handle: ObjectHandle) -> Self {
179 Self { handle }
180 }
181
182 pub fn new(path: &str) -> Result<Self> {
184 let path = c_string(path)?;
185 let mut out_resolver = ptr::null_mut();
186 let mut out_error = ptr::null_mut();
187 let status = unsafe {
189 ffi::mdl_path_asset_resolver_new(path.as_ptr(), &mut out_resolver, &mut out_error)
190 };
191 crate::util::status_result(status, out_error)?;
192 Ok(Self::from_handle(required_handle(
193 out_resolver,
194 "MDLPathAssetResolver",
195 )?))
196 }
197
198 #[must_use]
199 pub fn path(&self) -> Option<String> {
201 take_string(unsafe { ffi::mdl_path_asset_resolver_path(self.handle.as_ptr()) })
203 }
204
205 pub fn set_path(&self, path: &str) -> Result<()> {
207 let path = c_string(path)?;
208 unsafe { ffi::mdl_path_asset_resolver_set_path(self.handle.as_ptr(), path.as_ptr()) };
210 Ok(())
211 }
212
213 #[must_use]
214 pub fn as_asset_resolver(&self) -> AssetResolver {
216 AssetResolver::from_handle(self.handle.clone())
217 }
218}
219
220#[derive(Debug, Clone)]
221pub struct BundleAssetResolver {
223 handle: ObjectHandle,
224}
225
226impl BundleAssetResolver {
227 pub(crate) fn from_handle(handle: ObjectHandle) -> Self {
229 Self { handle }
230 }
231
232 pub fn new(path: &str) -> Result<Self> {
234 let path = c_string(path)?;
235 let mut out_resolver = ptr::null_mut();
236 let mut out_error = ptr::null_mut();
237 let status = unsafe {
239 ffi::mdl_bundle_asset_resolver_new(path.as_ptr(), &mut out_resolver, &mut out_error)
240 };
241 crate::util::status_result(status, out_error)?;
242 Ok(Self::from_handle(required_handle(
243 out_resolver,
244 "MDLBundleAssetResolver",
245 )?))
246 }
247
248 #[must_use]
249 pub fn path(&self) -> Option<String> {
251 take_string(unsafe { ffi::mdl_bundle_asset_resolver_path(self.handle.as_ptr()) })
253 }
254
255 pub fn set_path(&self, path: &str) -> Result<()> {
257 let path = c_string(path)?;
258 unsafe { ffi::mdl_bundle_asset_resolver_set_path(self.handle.as_ptr(), path.as_ptr()) };
260 Ok(())
261 }
262
263 #[must_use]
264 pub fn as_asset_resolver(&self) -> AssetResolver {
266 AssetResolver::from_handle(self.handle.clone())
267 }
268}
269
270#[derive(Debug, Clone)]
271pub struct RelativeAssetResolver {
273 handle: ObjectHandle,
274}
275
276impl RelativeAssetResolver {
277 pub(crate) fn from_handle(handle: ObjectHandle) -> Self {
279 Self { handle }
280 }
281
282 pub fn new(asset: &Asset) -> Result<Self> {
284 let mut out_resolver = ptr::null_mut();
285 let mut out_error = ptr::null_mut();
286 let status = unsafe {
288 ffi::mdl_relative_asset_resolver_new(asset.as_ptr(), &mut out_resolver, &mut out_error)
289 };
290 crate::util::status_result(status, out_error)?;
291 Ok(Self::from_handle(required_handle(
292 out_resolver,
293 "MDLRelativeAssetResolver",
294 )?))
295 }
296
297 #[must_use]
298 pub fn asset(&self) -> Option<Asset> {
300 let ptr = unsafe { ffi::mdl_relative_asset_resolver_asset(self.handle.as_ptr()) };
302 unsafe { ObjectHandle::from_retained_ptr(ptr) }.map(Asset::from_handle)
304 }
305
306 pub fn set_asset(&self, asset: Option<&Asset>) {
308 unsafe {
310 ffi::mdl_relative_asset_resolver_set_asset(
311 self.handle.as_ptr(),
312 asset.map_or(ptr::null_mut(), Asset::as_ptr),
313 );
314 }
315 }
316
317 #[must_use]
318 pub fn as_asset_resolver(&self) -> AssetResolver {
320 AssetResolver::from_handle(self.handle.clone())
321 }
322}