Skip to main content

modelio/
asset_resolver.rs

1use 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)]
19/// Describes one `MDLAssetResolver` protocol request routed into Rust.
20pub enum AssetResolverEvent {
21    /// Asks whether the named asset can be resolved.
22    CanResolveAssetNamed(String),
23    /// Asks for a URL string for the named asset.
24    ResolveAssetNamed(String),
25}
26
27#[derive(Debug, Clone, PartialEq, Eq)]
28/// Returns the result of one `MDLAssetResolver` protocol request.
29pub enum AssetResolverResponse {
30    /// Returns a boolean for `AssetResolverEvent::CanResolveAssetNamed`.
31    Bool(bool),
32    /// Returns an optional URL string for `AssetResolverEvent::ResolveAssetNamed`.
33    Url(Option<String>),
34}
35
36fn callback_name(name: *const core::ffi::c_char) -> Option<String> {
37    (!name.is_null()).then(|| {
38        // SAFETY: The unsafe operation is valid in this context.
39        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    // SAFETY: The unsafe operation is valid in this context.
48    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        // SAFETY: The unsafe operation is valid in this context.
58        (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    // SAFETY: The unsafe operation is valid in this context.
97    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)]
105/// Wraps the corresponding Model I/O asset resolver counterpart.
106pub struct AssetResolver {
107    handle: ObjectHandle,
108}
109
110impl AssetResolver {
111    /// Wraps a Rust callback as the corresponding Model I/O asset resolver protocol counterpart.
112    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        // SAFETY: The unsafe operation is valid in this context.
123        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    /// Builds this wrapper from the retained handle of the wrapped Model I/O asset resolver counterpart.
144    pub(crate) fn from_handle(handle: ObjectHandle) -> Self {
145        Self { handle }
146    }
147
148    /// Returns the opaque pointer used to call the wrapped Model I/O asset resolver counterpart.
149    pub(crate) fn as_ptr(&self) -> *mut core::ffi::c_void {
150        self.handle.as_ptr()
151    }
152
153    /// Calls the corresponding Model I/O method on the wrapped Model I/O asset resolver counterpart.
154    pub fn can_resolve_asset_named(&self, name: &str) -> Result<bool> {
155        let name = c_string(name)?;
156        // SAFETY: ObjectHandle wraps a valid opaque pointer from Swift; FFI function accepts it safely.
157        Ok(unsafe { ffi::mdl_asset_resolver_can_resolve_named(self.as_ptr(), name.as_ptr()) != 0 })
158    }
159
160    /// Calls the corresponding Model I/O method on the wrapped Model I/O asset resolver counterpart.
161    pub fn resolve_asset_named(&self, name: &str) -> Result<Option<String>> {
162        let name = c_string(name)?;
163        // SAFETY: The unsafe operation is valid in this context.
164        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)]
171/// Wraps the corresponding Model I/O path asset resolver counterpart.
172pub struct PathAssetResolver {
173    handle: ObjectHandle,
174}
175
176impl PathAssetResolver {
177    /// Builds this wrapper from the retained handle of the wrapped Model I/O path asset resolver counterpart.
178    pub(crate) fn from_handle(handle: ObjectHandle) -> Self {
179        Self { handle }
180    }
181
182    /// Wraps the corresponding Model I/O initializer for the wrapped Model I/O path asset resolver counterpart.
183    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        // SAFETY: The unsafe operation is valid in this context.
188        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    /// Calls the corresponding Model I/O method on the wrapped Model I/O path asset resolver counterpart.
200    pub fn path(&self) -> Option<String> {
201        // SAFETY: ObjectHandle wraps a valid opaque pointer from Swift; FFI function accepts it safely.
202        take_string(unsafe { ffi::mdl_path_asset_resolver_path(self.handle.as_ptr()) })
203    }
204
205    /// Calls the corresponding Model I/O method on the wrapped Model I/O path asset resolver counterpart.
206    pub fn set_path(&self, path: &str) -> Result<()> {
207        let path = c_string(path)?;
208        // SAFETY: ObjectHandle wraps a valid opaque pointer from Swift; FFI function accepts it safely.
209        unsafe { ffi::mdl_path_asset_resolver_set_path(self.handle.as_ptr(), path.as_ptr()) };
210        Ok(())
211    }
212
213    #[must_use]
214    /// Calls the corresponding Model I/O method on the wrapped Model I/O path asset resolver counterpart.
215    pub fn as_asset_resolver(&self) -> AssetResolver {
216        AssetResolver::from_handle(self.handle.clone())
217    }
218}
219
220#[derive(Debug, Clone)]
221/// Wraps the corresponding Model I/O bundle asset resolver counterpart.
222pub struct BundleAssetResolver {
223    handle: ObjectHandle,
224}
225
226impl BundleAssetResolver {
227    /// Builds this wrapper from the retained handle of the wrapped Model I/O bundle asset resolver counterpart.
228    pub(crate) fn from_handle(handle: ObjectHandle) -> Self {
229        Self { handle }
230    }
231
232    /// Wraps the corresponding Model I/O initializer for the wrapped Model I/O bundle asset resolver counterpart.
233    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        // SAFETY: The unsafe operation is valid in this context.
238        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    /// Calls the corresponding Model I/O method on the wrapped Model I/O bundle asset resolver counterpart.
250    pub fn path(&self) -> Option<String> {
251        // SAFETY: ObjectHandle wraps a valid opaque pointer from Swift; FFI function accepts it safely.
252        take_string(unsafe { ffi::mdl_bundle_asset_resolver_path(self.handle.as_ptr()) })
253    }
254
255    /// Calls the corresponding Model I/O method on the wrapped Model I/O bundle asset resolver counterpart.
256    pub fn set_path(&self, path: &str) -> Result<()> {
257        let path = c_string(path)?;
258        // SAFETY: ObjectHandle wraps a valid opaque pointer from Swift; FFI function accepts it safely.
259        unsafe { ffi::mdl_bundle_asset_resolver_set_path(self.handle.as_ptr(), path.as_ptr()) };
260        Ok(())
261    }
262
263    #[must_use]
264    /// Calls the corresponding Model I/O method on the wrapped Model I/O bundle asset resolver counterpart.
265    pub fn as_asset_resolver(&self) -> AssetResolver {
266        AssetResolver::from_handle(self.handle.clone())
267    }
268}
269
270#[derive(Debug, Clone)]
271/// Wraps the corresponding Model I/O relative asset resolver counterpart.
272pub struct RelativeAssetResolver {
273    handle: ObjectHandle,
274}
275
276impl RelativeAssetResolver {
277    /// Builds this wrapper from the retained handle of the wrapped Model I/O relative asset resolver counterpart.
278    pub(crate) fn from_handle(handle: ObjectHandle) -> Self {
279        Self { handle }
280    }
281
282    /// Wraps the corresponding Model I/O initializer for the wrapped Model I/O relative asset resolver counterpart.
283    pub fn new(asset: &Asset) -> Result<Self> {
284        let mut out_resolver = ptr::null_mut();
285        let mut out_error = ptr::null_mut();
286        // SAFETY: The unsafe operation is valid in this context.
287        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    /// Calls the corresponding Model I/O method on the wrapped Model I/O relative asset resolver counterpart.
299    pub fn asset(&self) -> Option<Asset> {
300        // SAFETY: ObjectHandle wraps a valid opaque pointer from Swift; FFI function accepts it safely.
301        let ptr = unsafe { ffi::mdl_relative_asset_resolver_asset(self.handle.as_ptr()) };
302        // SAFETY: The unsafe operation is valid in this context.
303        unsafe { ObjectHandle::from_retained_ptr(ptr) }.map(Asset::from_handle)
304    }
305
306    /// Calls the corresponding Model I/O method on the wrapped Model I/O relative asset resolver counterpart.
307    pub fn set_asset(&self, asset: Option<&Asset>) {
308        // SAFETY: The unsafe operation is valid in this context.
309        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    /// Calls the corresponding Model I/O method on the wrapped Model I/O relative asset resolver counterpart.
319    pub fn as_asset_resolver(&self) -> AssetResolver {
320        AssetResolver::from_handle(self.handle.clone())
321    }
322}