rsciter/
som.rs

1use std::{
2    ffi::CStr,
3    num::NonZero,
4    ops::{Deref, DerefMut},
5    os::raw::{c_char, c_long, c_void},
6    slice, str,
7    sync::atomic::Ordering,
8};
9
10use crate::{api::sapi, bindings::*, Result, Value};
11
12#[repr(C)]
13#[derive(Debug, Copy, Clone)]
14pub struct Atom(NonZero<som_atom_t>);
15
16impl Atom {
17    pub fn new(name: impl AsRef<CStr>) -> Result<Self> {
18        sapi()?
19            .atom_value(name.as_ref())
20            .map(|v| Self(unsafe { NonZero::new_unchecked(v) }))
21    }
22
23    pub fn name(&self) -> Result<String> {
24        let mut target = String::new();
25        let done =
26            sapi()?.atom_name_cb(self.0.get(), Some(str_thunk), &mut target as *mut _ as _)?;
27        if done {
28            Ok(target)
29        } else {
30            Err(crate::Error::InvalidAtom(self.0.get()))
31        }
32    }
33}
34
35impl From<Atom> for som_atom_t {
36    fn from(value: Atom) -> Self {
37        value.0.get()
38    }
39}
40
41unsafe extern "C" fn str_thunk(data: LPCSTR, len: UINT, target_ptr: LPVOID) {
42    let data = slice::from_raw_parts(data as _, len as _);
43    let data = str::from_utf8_unchecked(data);
44    let target = target_ptr as *mut String;
45    *target = data.to_string();
46}
47
48#[repr(transparent)]
49struct RawAssetObj(som_asset_t);
50
51#[allow(dead_code)]
52impl RawAssetObj {
53    pub(crate) fn new(class_data: som_asset_class_t) -> Self {
54        let isa = Box::new(class_data);
55        Self(som_asset_t {
56            isa: Box::into_raw(isa),
57        })
58    }
59
60    pub(crate) fn vtable(&self) -> &som_asset_class_t {
61        unsafe { &*self.0.isa }
62    }
63
64    pub(crate) fn add_ref(&self) -> c_long {
65        unsafe {
66            let Some(f) = self.vtable().asset_add_ref else {
67                return -1;
68            };
69
70            f(core::mem::transmute_copy(&self))
71        }
72    }
73
74    pub(crate) fn release(&self) -> c_long {
75        unsafe {
76            let Some(f) = self.vtable().asset_release else {
77                return -1;
78            };
79
80            f(core::mem::transmute_copy(&self))
81        }
82    }
83
84    pub fn passport(&self) -> Option<&som_passport_t> {
85        unsafe {
86            self.vtable()
87                .asset_get_passport
88                .map(|f| &*f(core::mem::transmute_copy(&self)))
89        }
90    }
91}
92
93pub type Passport = crate::bindings::som_passport_t;
94pub trait HasPassport {
95    fn passport(&self) -> Result<&'static Passport>;
96}
97
98pub trait ItemGetter: HasPassport {
99    fn get_item(&self, key: &Value) -> Result<Option<Value>>;
100}
101pub trait HasItemGetter {
102    fn has_item_getter(&self) -> bool;
103    fn do_get_item(&self, key: &Value) -> Result<Option<Value>>;
104}
105
106impl<T> HasItemGetter for &&T {
107    #[inline(always)]
108    fn has_item_getter(&self) -> bool {
109        false
110    }
111
112    fn do_get_item(&self, _key: &Value) -> Result<Option<Value>> {
113        Ok(None)
114    }
115}
116
117impl<T: ItemGetter> HasItemGetter for &mut &&T {
118    #[inline(always)]
119    fn has_item_getter(&self) -> bool {
120        true
121    }
122
123    fn do_get_item(&self, key: &Value) -> Result<Option<Value>> {
124        self.get_item(key)
125    }
126}
127
128pub trait ItemSetter: HasPassport {
129    fn set_item(&self, key: &Value, value: &Value) -> Result<()>;
130}
131
132pub trait HasItemSetter {
133    fn has_item_setter(&self) -> bool;
134    fn do_set_item(&self, key: &Value, value: &Value) -> Result<()>;
135}
136
137impl<T> HasItemSetter for &&T {
138    #[inline(always)]
139    fn has_item_setter(&self) -> bool {
140        false
141    }
142
143    fn do_set_item(&self, _key: &Value, _value: &Value) -> Result<()> {
144        Ok(())
145    }
146}
147
148impl<T: ItemSetter> HasItemSetter for &mut &&T {
149    #[inline(always)]
150    fn has_item_setter(&self) -> bool {
151        true
152    }
153
154    fn do_set_item(&self, key: &Value, value: &Value) -> Result<()> {
155        self.set_item(key, value)
156    }
157}
158
159#[macro_export]
160macro_rules! impl_item_getter {
161    ($type:ty) => {
162        impl_item_getter!($type, item_getter)
163    };
164    ($type:ty, $name:ident) => {
165        unsafe extern "C" fn $name(
166            thing: *mut ::rsciter::bindings::som_asset_t,
167            p_key: *const ::rsciter::bindings::SCITER_VALUE,
168            p_value: *mut ::rsciter::bindings::SCITER_VALUE,
169        ) -> ::rsciter::bindings::SBOOL {
170            use rsciter::AsValueRef;
171            let key = p_key.as_value_ref();
172            let asset_ref = ::rsciter::som::AssetRef::<$type>::new(thing);
173            let Ok(Some(res)) = (&mut &asset_ref.data()).do_get_item(key) else {
174                return 0;
175            };
176
177            *p_value = res.take();
178            return 1;
179        }
180    };
181}
182
183#[macro_export]
184macro_rules! impl_item_setter {
185    ($type:ty) => {
186        impl_item_setter!($type, item_setter)
187    };
188    ($type:ty, $name:ident) => {
189        unsafe extern "C" fn $name(
190            thing: *mut ::rsciter::bindings::som_asset_t,
191            p_key: *const ::rsciter::bindings::SCITER_VALUE,
192            p_value: *const ::rsciter::bindings::SCITER_VALUE,
193        ) -> ::rsciter::bindings::SBOOL {
194            use rsciter::AsValueRef;
195            let key = p_key.as_value_ref();
196            let value = p_value.as_value_ref();
197            let asset_ref = ::rsciter::som::AssetRef::<$type>::new(thing);
198            let Ok(_) = (&mut &asset_ref.data()).do_set_item(key, value) else {
199                return 0;
200            };
201
202            return 1;
203        }
204    };
205}
206
207pub use impl_item_getter;
208pub use impl_item_setter;
209
210pub type PropertyDef = crate::bindings::som_property_def_t;
211pub type PropertyAccessorDef = crate::bindings::som_property_def_t__bindgen_ty_1;
212pub type PropertyAccessors = crate::bindings::som_property_def_t__bindgen_ty_1__bindgen_ty_1;
213unsafe impl Sync for PropertyDef {}
214unsafe impl Send for PropertyDef {}
215
216#[macro_export]
217macro_rules! impl_prop {
218    ($type:ident :: $name:ident) => {
219        impl_prop!($type :: $name : true true)
220    };
221    ($type:ident :: $name:ident get) => {
222        impl_prop!($type :: $name : true false)
223    };
224    ($type:ident :: $name:ident set) => {
225        impl_prop!($type :: $name : false true)
226    };
227    ($type:ident :: $name:ident get set) => {
228        impl_prop!($type :: $name : true true)
229    };
230    ($type:ident :: $name:ident set get) => {
231        impl_prop!($type :: $name : true true)
232    };
233
234    ($type:ident :: $name:ident : $has_getter:literal $has_setter:literal) => {{
235        use ::rsciter::*;
236
237        unsafe extern "C" fn getter(
238            thing: *mut bindings::som_asset_t,
239            p_value: *mut bindings::SCITER_VALUE,
240        ) -> bindings::SBOOL {
241            let asset_ref = som::AssetRef::<$type>::new(thing);
242            let Ok(value) = conv::ToValue::to_value(&asset_ref.$name) else {
243                return 0;
244            };
245
246            *p_value = value.take();
247
248            1
249        }
250
251        unsafe extern "C" fn setter(
252            thing: *mut bindings::som_asset_t,
253            p_value: *mut bindings::SCITER_VALUE,
254        ) -> bindings::SBOOL {
255            let mut asset_mut = som::AssetRefMut::<$type>::new(thing);
256            let value = p_value.as_value_ref();
257            let Ok(_) = ::rsciter::conv::FromValue::from_value(value)
258                .map(|v| asset_mut.$name = v)
259            else {
260                return 0;
261            };
262
263            1
264        }
265
266        som::Atom::new(::rsciter::cstr!($name)).map(|name| som::PropertyDef {
267            type_: bindings::SOM_PROP_TYPE::SOM_PROP_ACCSESSOR.0 as _,
268            name: name.into(),
269            u: som::PropertyAccessorDef {
270                accs: som::PropertyAccessors {
271                    getter: if $has_getter { Some(getter) } else { None },
272                    setter: if $has_setter { Some(setter) } else { None },
273                },
274            },
275        })
276
277
278    }};
279}
280pub use impl_prop;
281
282/// There may be two property sources:
283///
284/// 1) Struct fields (handled via the [Fields] trait):
285/// ```rust,ignore
286/// #[rsciter::asset]
287/// struct Asset {
288///     name: String,
289///     id: u32,
290/// }
291/// ```
292///
293/// 2) Virtual properties in `impl` blocks (handled via the [VirtualProperties] trait):
294/// ```rust,ignore
295/// #[rsciter::asset]
296/// impl Asset {
297///     #[get]
298///     pub fn year(&self) -> String { ... }
299///     #[set]
300///     pub fn set_year(&self) -> String { ... }
301/// }
302/// ```
303/// `property_name` and `set_property_name` patterns are handled automatically and bound to the same `property_name`.
304/// Note: All public methods without `get` or `set` attributes are exported as functions.
305///
306/// Alternative syntax with explicit `year` name:
307/// ```rust,ignore
308/// #[rsciter::asset]
309/// impl Asset {
310///     #[get(year)]
311///     fn any_get_year_name(&self) -> String { ... }
312///
313///     #[set(year)]
314///     fn any_set_year_name(&self) -> String { ... }
315/// }
316/// ```
317///
318/// Note: The `get` and `set` attributes ignore visibility!
319pub trait Fields: HasPassport {
320    fn fields() -> &'static [Result<PropertyDef>];
321}
322
323/// See [Fields]. The traits are splitted only for codegen reasons.
324pub trait VirtualProperties: HasPassport {
325    fn properties() -> &'static [Result<PropertyDef>];
326}
327
328pub trait HasFields {
329    fn enum_fields(&self) -> &'static [Result<PropertyDef>];
330}
331
332impl<T> HasFields for &&T {
333    fn enum_fields(&self) -> &'static [Result<PropertyDef>] {
334        &[]
335    }
336}
337
338impl<T: Fields> HasFields for &mut &&T {
339    fn enum_fields(&self) -> &'static [Result<PropertyDef>] {
340        T::fields()
341    }
342}
343
344pub trait HasVirtualProperties {
345    fn enum_properties(&self) -> &'static [Result<PropertyDef>];
346}
347
348impl<T> HasVirtualProperties for &&T {
349    fn enum_properties(&self) -> &'static [Result<PropertyDef>] {
350        &[]
351    }
352}
353
354impl<T: VirtualProperties> HasVirtualProperties for &mut &&T {
355    fn enum_properties(&self) -> &'static [Result<PropertyDef>] {
356        T::properties()
357    }
358}
359
360pub type MethodDef = som_method_def_t;
361unsafe impl Send for MethodDef {}
362unsafe impl Sync for MethodDef {}
363pub trait Methods: HasPassport {
364    fn methods() -> &'static [Result<MethodDef>];
365}
366
367pub trait HasMethods {
368    fn enum_methods(&self) -> &'static [Result<MethodDef>];
369}
370
371impl<T> HasMethods for &&T {
372    fn enum_methods(&self) -> &'static [Result<MethodDef>] {
373        &[]
374    }
375}
376
377impl<T: Methods> HasMethods for &mut &&T {
378    fn enum_methods(&self) -> &'static [Result<MethodDef>] {
379        T::methods()
380    }
381}
382
383trait IAsset {
384    fn class() -> som_asset_class_t
385    where
386        Self: Sized;
387}
388
389pub struct GlobalAsset<T: HasPassport> {
390    ptr: *mut AssetData<T>,
391}
392
393impl<T: HasPassport> IAsset for GlobalAsset<T> {
394    fn class() -> som_asset_class_t {
395        // global assets are not ref-counted.
396        unsafe extern "C" fn ref_count_stub(_thing: *mut som_asset_t) -> c_long {
397            return 1;
398        }
399
400        unsafe extern "C" fn asset_get_interface(
401            _thing: *mut som_asset_t,
402            _name: *const c_char,
403            _out: *mut *mut c_void,
404        ) -> c_long {
405            // TODO: query interface (any usage?)
406            return 0;
407        }
408
409        unsafe extern "C" fn asset_get_passport<T: HasPassport>(
410            thing: *mut som_asset_t,
411        ) -> *mut som_passport_t {
412            let asset_ref = AssetRef::<T>::new(thing);
413            let Ok(passport) = asset_ref.passport() else {
414                return std::ptr::null_mut();
415            };
416            passport as *const _ as *mut _
417        }
418
419        som_asset_class_t {
420            asset_add_ref: Some(ref_count_stub),
421            asset_release: Some(ref_count_stub),
422            asset_get_interface: Some(asset_get_interface),
423            asset_get_passport: Some(asset_get_passport::<T>),
424        }
425    }
426}
427
428impl<T: HasPassport> Drop for GlobalAsset<T> {
429    fn drop(&mut self) {
430        let ptr: *mut som_asset_t = self.ptr.cast();
431        let _res = sapi().and_then(|api| api.release_global_asset(ptr));
432        debug_assert!(_res.is_ok());
433    }
434}
435
436impl<T: HasPassport> GlobalAsset<T> {
437    pub fn new(data: T) -> Result<Self> {
438        let obj = RawAssetObj::new(Self::class());
439        let res = AssetData::new(obj, data);
440        let boxed = Box::new(res);
441        let ptr = Box::into_raw(boxed);
442
443        // SciterSetGlobalAsset overrides assets, so it might return false only if there is no asset_get_passport callback,
444        // as we always provide one, it's safe to ignore the result
445        let _res = sapi()?.set_global_asset(ptr as _)?;
446        debug_assert!(_res);
447
448        Ok(Self { ptr })
449    }
450
451    pub fn as_ref(&self) -> AssetRef<T> {
452        unsafe { AssetRef::new(self.ptr.cast()) }
453    }
454}
455
456#[macro_export]
457macro_rules! impl_passport {
458    ($self:ident, $type:ident) => {{
459        static PASSPORT: std::sync::OnceLock<::rsciter::Result<::rsciter::bindings::som_passport_t>> =
460            std::sync::OnceLock::new();
461
462        let res = PASSPORT.get_or_init(|| {
463            let mut passport =
464                ::rsciter::bindings::som_passport_t::new(::rsciter::cstr!($type))?;
465            use ::rsciter::som::{
466                self, HasFields, HasItemGetter, HasItemSetter, HasMethods, HasVirtualProperties
467            };
468
469            let autoref_trick = &mut &$self;
470
471            if autoref_trick.has_item_getter() {
472                som::impl_item_getter!($type);
473                passport.item_getter = Some(item_getter);
474            }
475
476            if autoref_trick.has_item_setter() {
477                som::impl_item_setter!($type);
478                passport.item_setter = Some(item_setter);
479            }
480
481            let mut properties = Vec::new();
482            for f in autoref_trick.enum_fields() {
483                match f {
484                    Ok(v) => properties.push(v.clone()),
485                    Err(e) => return Err(e.clone()),
486                }
487            }
488            for p in autoref_trick.enum_properties() {
489                match p {
490                    Ok(v) => properties.push(v.clone()),
491                    Err(e) => return Err(e.clone()),
492                }
493            }
494
495            let mut methods = Vec::new();
496            for m in autoref_trick.enum_methods() {
497                match m {
498                    Ok(v) => methods.push(v.clone()),
499                    Err(e) => return Err(e.clone()),
500                }
501            }
502
503            let boxed_props = properties.into_boxed_slice();
504            passport.n_properties = boxed_props.len();
505            if passport.n_properties > 0 {
506                passport.properties = Box::into_raw(boxed_props) as *const _; // leak is acceptable here!
507            }
508
509            let boxed_methods = methods.into_boxed_slice();
510            passport.n_methods = boxed_methods.len();
511            if passport.n_methods > 0 {
512                passport.methods = Box::into_raw(boxed_methods) as *const _; // leak is acceptable here!
513            }
514
515            Ok(passport)
516        });
517
518        match res {
519            Ok(p) => Ok(p),
520            Err(e) => Err(e.clone()),
521        }
522    }};
523}
524pub use impl_passport;
525
526#[repr(C)]
527struct AssetData<T> {
528    obj: RawAssetObj,
529    pub data: T,
530}
531
532impl<T> AssetData<T> {
533    fn new(obj: RawAssetObj, data: T) -> Self {
534        Self { obj, data }
535    }
536}
537
538// TODO: refactor to support AsRef and Borrow
539/// AssetRef does not use add_ref\release machinery,
540/// Instead, it utilizes a lifetime and is guaranteed to be a valid reference to an asset.
541pub struct AssetRef<'a, T> {
542    this: &'a AssetData<T>,
543}
544
545impl<'a, T> AssetRef<'a, T> {
546    pub unsafe fn new(thing: *const som_asset_t) -> Self {
547        let this = thing as *const AssetData<T>;
548        let this = unsafe { &*this };
549        Self { this }
550    }
551
552    pub fn data(&self) -> &T {
553        &self.this.data
554    }
555}
556
557impl<T> Deref for AssetRef<'_, T> {
558    type Target = T;
559
560    fn deref(&self) -> &Self::Target {
561        &self.data()
562    }
563}
564
565pub struct AssetRefMut<'a, T> {
566    this: &'a mut AssetData<T>,
567}
568
569impl<'a, T> AssetRefMut<'a, T> {
570    pub unsafe fn new(thing: *mut som_asset_t) -> Self {
571        let this = thing as *mut AssetData<T>;
572        let this = unsafe { &mut *this };
573        Self { this }
574    }
575
576    pub fn data(&self) -> &T {
577        &self.this.data
578    }
579
580    pub fn data_mut(&mut self) -> &mut T {
581        &mut self.this.data
582    }
583}
584
585impl<T> Deref for AssetRefMut<'_, T> {
586    type Target = T;
587
588    fn deref(&self) -> &Self::Target {
589        &self.data()
590    }
591}
592
593impl<T> DerefMut for AssetRefMut<'_, T> {
594    fn deref_mut(&mut self) -> &mut Self::Target {
595        self.data_mut()
596    }
597}
598
599pub struct Asset<T: HasPassport> {
600    boxed: Box<AssetDataWithCounter<T>>,
601}
602
603#[repr(C)]
604struct AssetDataWithCounter<T> {
605    data: AssetData<T>,
606    counter: std::sync::atomic::AtomicI32,
607}
608
609impl<T> AssetDataWithCounter<T> {
610    unsafe fn get_mut_ref<'c>(thing: *mut som_asset_t) -> &'c mut Self {
611        let this = thing as *mut AssetDataWithCounter<T>;
612        &mut *this
613    }
614}
615
616impl<T: HasPassport> IAsset for Asset<T> {
617    fn class() -> som_asset_class_t {
618        unsafe extern "C" fn asset_add_ref<TT>(thing: *mut som_asset_t) -> c_long {
619            let this = AssetDataWithCounter::<TT>::get_mut_ref(thing);
620            let refc = this.counter.fetch_add(1, Ordering::SeqCst) + 1;
621            return refc;
622        }
623
624        unsafe extern "C" fn asset_release<TT>(thing: *mut som_asset_t) -> c_long {
625            let this = AssetDataWithCounter::<TT>::get_mut_ref(thing);
626            let refc = this.counter.fetch_sub(1, Ordering::SeqCst) - 1;
627            if refc == 0 {
628                // TODO: should not panic, reason: for each asset we got unexpected asset_release call with bad ptr
629                let _ = std::panic::catch_unwind(|| {
630                    let _asset_to_drop = Box::from_raw(thing as *mut AssetDataWithCounter<TT>);
631                });
632            }
633            return refc;
634        }
635
636        unsafe extern "C" fn asset_get_interface(
637            _thing: *mut som_asset_t,
638            _name: *const c_char,
639            _out: *mut *mut c_void,
640        ) -> c_long {
641            // TODO: query interface (any usage?)
642            return 0;
643        }
644
645        unsafe extern "C" fn asset_get_passport<TT: HasPassport>(
646            thing: *mut som_asset_t,
647        ) -> *mut som_passport_t {
648            let asset_ref = AssetRef::<TT>::new(thing);
649            let Ok(passport) = asset_ref.passport() else {
650                return std::ptr::null_mut();
651            };
652            passport as *const _ as *mut _
653        }
654
655        som_asset_class_t {
656            asset_add_ref: Some(asset_add_ref::<T>),
657            asset_release: Some(asset_release::<T>),
658            asset_get_interface: Some(asset_get_interface),
659            asset_get_passport: Some(asset_get_passport::<T>),
660        }
661    }
662}
663
664impl<T: HasPassport> Asset<T> {
665    pub fn new(data: T) -> Self {
666        let obj = RawAssetObj::new(Self::class());
667        Self {
668            boxed: Box::new(AssetDataWithCounter {
669                data: AssetData::new(obj, data),
670                counter: Default::default(),
671            }),
672        }
673    }
674
675    pub(crate) fn to_raw_ptr(self) -> *const som_asset_t {
676        let ptr = Box::into_raw(self.boxed);
677        ptr.cast()
678    }
679
680    pub fn as_ref(&self) -> AssetRef<T> {
681        let ptr = &self.boxed.as_ref().data as *const AssetData<T>;
682        unsafe { AssetRef::new(ptr.cast()) }
683    }
684}
685
686#[cfg(test)]
687mod tests {
688    use super::*;
689
690    #[test]
691    fn test_atom() {
692        let atom = Atom::new(c"name").unwrap();
693        let name = atom.name().unwrap();
694        assert_eq!(name, "name");
695    }
696}