logo
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
/*! Sciter Object Model (SOM passport).

See [Native code exposure to script](http://sciter.com/native-code-exposure-to-script/)
and [Sciter Object Model](http://sciter.com/developers/for-native-gui-programmers/sciter-object-model/) blog articles.

*/
use std::sync::atomic::{AtomicI32, Ordering};
use capi::sctypes::{LPVOID, LPCSTR};
pub use capi::scom::*;


/// Get the index of an interned string.
pub fn atom(name: &str) -> som_atom_t {
	let s = s2u!(name);
	(crate::_API.SciterAtomValue)(s.as_ptr())
}

/// Get the value of an interned string.
pub fn atom_name(id: som_atom_t) -> Option<String> {
	let mut s = String::new();
	let ok = (crate::_API.SciterAtomNameCB)(id, crate::utf::store_astr, &mut s as *mut _ as LPVOID);
	if ok != 0 {
		Some(s)
	} else {
		None
	}
}


/// Something that has a SOM passport.
///
/// However, since we can't call extern functions in static object initialization,
/// in order to use [`atom("name")`](fn.atom.html) we have to initializa the passport in run time
/// and return a reference to it via [`Box::leak()`](https://doc.rust-lang.org/stable/std/boxed/struct.Box.html#method.leak).
pub trait Passport {
	/// A static reference to the passport that describes an asset.
	fn get_passport(&self) -> &'static som_passport_t;
}


/// A non-owning pointer to a native object.
pub struct IAssetRef<T> {
	asset: *mut som_asset_t,
	ty: std::marker::PhantomData<T>,
}

impl<T> Clone for IAssetRef<T> {
	fn clone(&self) -> Self {
		self.add_ref();
		Self {
			asset: self.asset,
			ty: self.ty,
		}
	}
}

impl<T> Drop for IAssetRef<T> {
	fn drop(&mut self) {
		self.release();
	}
}

impl<T> std::fmt::Debug for IAssetRef<T> {
	fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
		// the current reference count
		self.add_ref();
		let rc = self.release();

		let name = self::atom_name(self.get_passport().name);
		write!(f, "Asset({}):{}", name.unwrap_or_default(), rc)
	}
}

/// Construct a reference from a managed asset.
impl<T> From<Box<IAsset<T>>> for IAssetRef<T> {
	fn from(asset: Box<IAsset<T>>) -> Self {
		Self::from_raw(IAsset::into_raw(asset))
	}
}

impl<T> IAssetRef<T> {
	/// Get the vtable of an asset.
	fn isa(&self) -> &'static som_asset_class_t {
		unsafe { (*self.asset).isa }
	}

	/// Increment the reference count of an asset and returns the new value.
	fn add_ref(&self) -> i32 {
		(self.isa().add_ref)(self.asset)
	}

	/// Decrement the reference count of an asset and returns the new value.
	fn release(&self) -> i32 {
		(self.isa().release)(self.asset)
	}
}

impl<T> IAssetRef<T> {
	/// Construct from a raw pointer, incrementing the reference count.
	pub fn from_raw(asset: *mut som_asset_t) -> Self {
		eprintln!("IAssetRef<{}>::from({:?})", std::any::type_name::<T>(), asset);
		assert!(!asset.is_null());
		let me = Self {
			asset,
			ty: std::marker::PhantomData,
		};
		me.add_ref();
		me
	}

	/// Return the raw pointer, releasing the reference count.
	pub fn into_raw(asset: IAssetRef<T>) -> *mut som_asset_t {
		// decrement reference count
		asset.release();

		// get the pointer and forget about this wrapper
		let ptr = asset.asset;
		std::mem::forget(asset);

		ptr
	}

	/// Get the underlaying pointer.
	pub fn as_ptr(&self) -> *mut som_asset_t {
		self.asset
	}

	/// Get a reference to the underlaying pointer.
	pub fn as_asset(&self) -> &som_asset_t {
		unsafe { & *self.asset }
	}

	/// Get the passport of the asset.
	pub fn get_passport(&self) -> &som_passport_t {
		// TODO: do we need this?
		let ptr = (self.isa().get_passport)(self.asset);
		unsafe { & *ptr }
	}
}


/// An owned pointer to a wrapped native object.
#[repr(C)]
pub struct IAsset<T> {
	// NB: should be the first member here
	// in order to `*mut IAsset as *mut som_asset_t` work
	asset: som_asset_t,
	refc: AtomicI32,
	passport: Option<&'static som_passport_t>,
	data: T,
}

/// Make the object to be accessible as other global objects in TIScript.
pub fn set_global<T>(asset: IAssetRef<T>) {
	let ptr = asset.as_ptr();
	// eprintln!("IAsset<{}>: {:?}", std::any::type_name::<T>(), ptr);
	(crate::_API.SciterSetGlobalAsset)(ptr);
}

/// Make the object to be accessible as other global objects in TIScript.
pub fn into_global<T>(asset: Box<IAsset<T>>) {
	let ptr = IAsset::into_raw(asset);
	// eprintln!("IAsset<{}>: {:?}", std::any::type_name::<T>(), ptr);
	(crate::_API.SciterSetGlobalAsset)(ptr);
}

impl<T> std::ops::Deref for IAsset<T> {
	type Target = T;
	fn deref(&self) -> &Self::Target {
		&self.data
	}
}

impl<T> std::ops::DerefMut for IAsset<T> {
	fn deref_mut(&mut self) -> &mut Self::Target {
		&mut self.data
	}
}

impl<T> Drop for IAsset<T> {
	fn drop(&mut self) {
		let rc = self.refc.load(Ordering::SeqCst);
		if rc != 0 {
			eprintln!("asset<{}>::drop with {} references alive", std::any::type_name::<T>(), rc);
		}
		assert_eq!(rc, 0);
		// allocated in `iasset::new()`
		let ptr = self.asset.isa as *const som_asset_class_t;
		let ptr = unsafe { Box::from_raw(ptr as *mut som_asset_class_t) };
		drop(ptr);
	}
}

impl<T> IAsset<T> {
	/// Cast the pointer to a managed asset reference.
	#[allow(clippy::mut_from_ref)]
	pub fn from_raw(thing: &*mut som_asset_t) -> &mut IAsset<T> {
		assert!(!thing.is_null());
		// clippy complains about "mut_from_ref".
		// the ref is here just to add a lifetime for our resulting reference
		// not the best design choice though
		unsafe { &mut *(*thing as *mut IAsset<T>) }
	}

	/// Release the pointer.
	fn into_raw(asset: Box<IAsset<T>>) -> *mut som_asset_t {
		let p = Box::into_raw(asset);
		p as *mut som_asset_t
	}
}

impl<T: Passport> IAsset<T> {
	/// Wrap the object into a managed asset.
	pub fn new(data: T) -> Box<Self> {
		// will be freed in `iasset<T>::drop()`
		let isa = Box::new(Self::class());

		let me = Self {
			asset: som_asset_t { isa: Box::leak(isa) },
			refc: Default::default(),
			passport: None,
			data,
		};
		Box::new(me)
	}

	fn class() -> som_asset_class_t {
		extern "C" fn asset_add_ref<T>(thing: *mut som_asset_t) -> i32 {
			{
				let me = IAsset::<T>::from_raw(&thing);
				let t = me.refc.fetch_add(1, Ordering::SeqCst) + 1;
				// eprintln!("iasset<T>::add_ref() -> {}", t);
				return t;
			}
		}
		extern "C" fn asset_release<T>(thing: *mut som_asset_t) -> i32 {
			let t = {
				let me = IAsset::<T>::from_raw(&thing);
				me.refc.fetch_sub(1, Ordering::SeqCst) - 1
			};
			// eprintln!("iasset<T>::release() -> {}", t);
			if t == 0 {
				// eprintln!("iasset<T>::drop()");
				let me = unsafe { Box::from_raw(thing as *mut IAsset<T>) };
				drop(me);
			}
			return t;
		}
		extern "C" fn asset_get_interface<T>(_thing: *mut som_asset_t, name: LPCSTR, _out: *mut *mut som_asset_t) -> bool {
			let name = u2s!(name);
			eprintln!("iasset<T>::get_interface({}) is not implemented.", name);
			return false;
		}
		extern "C" fn asset_get_passport<T: Passport>(thing: *mut som_asset_t) -> *const som_passport_t
		{
			// here we cache the returned reference in order not to allocate things again
			let me = IAsset::<T>::from_raw(&thing);
			if me.passport.is_none() {
				// eprintln!("asset_get_passport<{}>: {:?}", std::any::type_name::<T>(), thing);
				me.passport = Some(me.data.get_passport());
			}
			let ps = me.passport.as_ref().unwrap();
			return *ps;
		}

		som_asset_class_t {
			add_ref: asset_add_ref::<T>,
			release: asset_release::<T>,
			get_interface: asset_get_interface::<T>,
			get_passport: asset_get_passport::<T>,
		}
	}
}