sciter/
om.rs

1/*! Sciter Object Model (SOM passport).
2
3See [Native code exposure to script](http://sciter.com/native-code-exposure-to-script/)
4and [Sciter Object Model](http://sciter.com/developers/for-native-gui-programmers/sciter-object-model/) blog articles.
5
6*/
7use std::sync::atomic::{AtomicI32, Ordering};
8use capi::sctypes::{LPVOID, LPCSTR};
9pub use capi::scom::*;
10
11
12/// Get the index of an interned string.
13pub fn atom(name: &str) -> som_atom_t {
14	let s = s2u!(name);
15	(crate::_API.SciterAtomValue)(s.as_ptr())
16}
17
18/// Get the value of an interned string.
19pub fn atom_name(id: som_atom_t) -> Option<String> {
20	let mut s = String::new();
21	let ok = (crate::_API.SciterAtomNameCB)(id, crate::utf::store_astr, &mut s as *mut _ as LPVOID);
22	if ok != 0 {
23		Some(s)
24	} else {
25		None
26	}
27}
28
29
30/// Something that has a SOM passport.
31///
32/// However, since we can't call extern functions in static object initialization,
33/// in order to use [`atom("name")`](fn.atom.html) we have to initializa the passport in run time
34/// and return a reference to it via [`Box::leak()`](https://doc.rust-lang.org/stable/std/boxed/struct.Box.html#method.leak).
35pub trait Passport {
36	/// A static reference to the passport that describes an asset.
37	fn get_passport(&self) -> &'static som_passport_t;
38}
39
40
41/// A non-owning pointer to a native object.
42pub struct IAssetRef<T> {
43	asset: *mut som_asset_t,
44	ty: std::marker::PhantomData<T>,
45}
46
47impl<T> Clone for IAssetRef<T> {
48	fn clone(&self) -> Self {
49		self.add_ref();
50		Self {
51			asset: self.asset,
52			ty: self.ty,
53		}
54	}
55}
56
57impl<T> Drop for IAssetRef<T> {
58	fn drop(&mut self) {
59		self.release();
60	}
61}
62
63impl<T> std::fmt::Debug for IAssetRef<T> {
64	fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
65		// the current reference count
66		self.add_ref();
67		let rc = self.release();
68
69		let name = self::atom_name(self.get_passport().name);
70		write!(f, "Asset({}):{}", name.unwrap_or_default(), rc)
71	}
72}
73
74/// Construct a reference from a managed asset.
75impl<T> From<Box<IAsset<T>>> for IAssetRef<T> {
76	fn from(asset: Box<IAsset<T>>) -> Self {
77		Self::from_raw(IAsset::into_raw(asset))
78	}
79}
80
81impl<T> IAssetRef<T> {
82	/// Get the vtable of an asset.
83	fn isa(&self) -> &'static som_asset_class_t {
84		unsafe { (*self.asset).isa }
85	}
86
87	/// Increment the reference count of an asset and returns the new value.
88	fn add_ref(&self) -> i32 {
89		(self.isa().add_ref)(self.asset)
90	}
91
92	/// Decrement the reference count of an asset and returns the new value.
93	fn release(&self) -> i32 {
94		(self.isa().release)(self.asset)
95	}
96}
97
98impl<T> IAssetRef<T> {
99	/// Construct from a raw pointer, incrementing the reference count.
100	pub fn from_raw(asset: *mut som_asset_t) -> Self {
101		eprintln!("IAssetRef<{}>::from({:?})", std::any::type_name::<T>(), asset);
102		assert!(!asset.is_null());
103		let me = Self {
104			asset,
105			ty: std::marker::PhantomData,
106		};
107		me.add_ref();
108		me
109	}
110
111	/// Return the raw pointer, releasing the reference count.
112	pub fn into_raw(asset: IAssetRef<T>) -> *mut som_asset_t {
113		// decrement reference count
114		asset.release();
115
116		// get the pointer and forget about this wrapper
117		let ptr = asset.asset;
118		std::mem::forget(asset);
119
120		ptr
121	}
122
123	/// Get the underlaying pointer.
124	pub fn as_ptr(&self) -> *mut som_asset_t {
125		self.asset
126	}
127
128	/// Get a reference to the underlaying pointer.
129	pub fn as_asset(&self) -> &som_asset_t {
130		unsafe { & *self.asset }
131	}
132
133	/// Get the passport of the asset.
134	pub fn get_passport(&self) -> &som_passport_t {
135		// TODO: do we need this?
136		let ptr = (self.isa().get_passport)(self.asset);
137		unsafe { & *ptr }
138	}
139}
140
141
142/// An owned pointer to a wrapped native object.
143#[repr(C)]
144pub struct IAsset<T> {
145	// NB: should be the first member here
146	// in order to `*mut IAsset as *mut som_asset_t` work
147	asset: som_asset_t,
148	refc: AtomicI32,
149	passport: Option<&'static som_passport_t>,
150	data: T,
151}
152
153/// Make the object to be accessible as other global objects in TIScript.
154pub fn set_global<T>(asset: IAssetRef<T>) {
155	let ptr = asset.as_ptr();
156	// eprintln!("IAsset<{}>: {:?}", std::any::type_name::<T>(), ptr);
157	(crate::_API.SciterSetGlobalAsset)(ptr);
158}
159
160/// Make the object to be accessible as other global objects in TIScript.
161pub fn into_global<T>(asset: Box<IAsset<T>>) {
162	let ptr = IAsset::into_raw(asset);
163	// eprintln!("IAsset<{}>: {:?}", std::any::type_name::<T>(), ptr);
164	(crate::_API.SciterSetGlobalAsset)(ptr);
165}
166
167impl<T> std::ops::Deref for IAsset<T> {
168	type Target = T;
169	fn deref(&self) -> &Self::Target {
170		&self.data
171	}
172}
173
174impl<T> std::ops::DerefMut for IAsset<T> {
175	fn deref_mut(&mut self) -> &mut Self::Target {
176		&mut self.data
177	}
178}
179
180impl<T> Drop for IAsset<T> {
181	fn drop(&mut self) {
182		let rc = self.refc.load(Ordering::SeqCst);
183		if rc != 0 {
184			eprintln!("asset<{}>::drop with {} references alive", std::any::type_name::<T>(), rc);
185		}
186		assert_eq!(rc, 0);
187		// allocated in `iasset::new()`
188		let ptr = self.asset.isa as *const som_asset_class_t;
189		let ptr = unsafe { Box::from_raw(ptr as *mut som_asset_class_t) };
190		drop(ptr);
191	}
192}
193
194impl<T> IAsset<T> {
195	/// Cast the pointer to a managed asset reference.
196	#[allow(clippy::mut_from_ref)]
197	pub fn from_raw(thing: &*mut som_asset_t) -> &mut IAsset<T> {
198		assert!(!thing.is_null());
199		// clippy complains about "mut_from_ref".
200		// the ref is here just to add a lifetime for our resulting reference
201		// not the best design choice though
202		unsafe { &mut *(*thing as *mut IAsset<T>) }
203	}
204
205	/// Release the pointer.
206	fn into_raw(asset: Box<IAsset<T>>) -> *mut som_asset_t {
207		let p = Box::into_raw(asset);
208		p as *mut som_asset_t
209	}
210}
211
212impl<T: Passport> IAsset<T> {
213	/// Wrap the object into a managed asset.
214	pub fn new(data: T) -> Box<Self> {
215		// will be freed in `iasset<T>::drop()`
216		let isa = Box::new(Self::class());
217
218		let me = Self {
219			asset: som_asset_t { isa: Box::leak(isa) },
220			refc: Default::default(),
221			passport: None,
222			data,
223		};
224		Box::new(me)
225	}
226
227	fn class() -> som_asset_class_t {
228		extern "C" fn asset_add_ref<T>(thing: *mut som_asset_t) -> i32 {
229			{
230				let me = IAsset::<T>::from_raw(&thing);
231				let t = me.refc.fetch_add(1, Ordering::SeqCst) + 1;
232				// eprintln!("iasset<T>::add_ref() -> {}", t);
233				return t;
234			}
235		}
236		extern "C" fn asset_release<T>(thing: *mut som_asset_t) -> i32 {
237			let t = {
238				let me = IAsset::<T>::from_raw(&thing);
239				me.refc.fetch_sub(1, Ordering::SeqCst) - 1
240			};
241			// eprintln!("iasset<T>::release() -> {}", t);
242			if t == 0 {
243				// eprintln!("iasset<T>::drop()");
244				let me = unsafe { Box::from_raw(thing as *mut IAsset<T>) };
245				drop(me);
246			}
247			return t;
248		}
249		extern "C" fn asset_get_interface<T>(_thing: *mut som_asset_t, name: LPCSTR, _out: *mut *mut som_asset_t) -> bool {
250			let name = u2s!(name);
251			eprintln!("iasset<T>::get_interface({}) is not implemented.", name);
252			return false;
253		}
254		extern "C" fn asset_get_passport<T: Passport>(thing: *mut som_asset_t) -> *const som_passport_t
255		{
256			// here we cache the returned reference in order not to allocate things again
257			let me = IAsset::<T>::from_raw(&thing);
258			if me.passport.is_none() {
259				// eprintln!("asset_get_passport<{}>: {:?}", std::any::type_name::<T>(), thing);
260				me.passport = Some(me.data.get_passport());
261			}
262			let ps = me.passport.as_ref().unwrap();
263			return *ps;
264		}
265
266		som_asset_class_t {
267			add_ref: asset_add_ref::<T>,
268			release: asset_release::<T>,
269			get_interface: asset_get_interface::<T>,
270			get_passport: asset_get_passport::<T>,
271		}
272	}
273}