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
use std::collections::{HashMap};
use wasm_bindgen::prelude::*;
use std::cell::RefCell;
use std::mem::transmute;
use std::hash::{Hash};

thread_local!(
	static FLOAT_CACHE: Cacher<BitwiseFloat> = Cacher::new();
	static STRING_CACHE: Cacher<&'static str> = Cacher::new();
	static BOOL_CACHE: Cacher<bool> = Cacher::new(); // TODO: This is a bit overkill.
	// TODO: Include None. The first thought would be for Option<!> if that compiles with a simple js_intern!(None). wasm-bindgen treats this as undefined rather than null, so then should we.
);

#[doc(hidden)]
/// This is a private trait and not meant to be used.
pub trait CacheJsIntern__ {
	fn cache_js_intern__(self) -> *mut JsValue;
}

impl CacheJsIntern__ for f64 {
	fn cache_js_intern__(self) -> *mut JsValue {
		FLOAT_CACHE.with(|c| {
			c.cache(self.into())
		})
	}
}

impl CacheJsIntern__ for &'static str {
	fn cache_js_intern__(self) -> *mut JsValue {
		STRING_CACHE.with(|c| {
			c.cache(self)
		})
	}
}

impl CacheJsIntern__ for bool {
	fn cache_js_intern__(self) -> *mut JsValue {
		BOOL_CACHE.with(|c| {
			c.cache(self)
		})
	}
}

macro_rules! CacheForT64 {
	($t:ty) => {
		impl CacheJsIntern__ for $t {
			fn cache_js_intern__(self) -> *mut JsValue {
				(self as f64).cache_js_intern__()
			}
		}
	};
}

CacheForT64!(i8);
CacheForT64!(i16);
CacheForT64!(i32);
CacheForT64!(u8);
CacheForT64!(u16);
CacheForT64!(u32);
CacheForT64!(f32);

struct Cacher<T: Eq + Hash> {
	inner: RefCell<HashMap<T, *mut JsValue>>
}

impl<T: Eq + Hash> Cacher<T> {
	fn new() -> Cacher<T> {
		Cacher {
			inner: RefCell::default()
		}
	}
}

// Implementing Drop is probably overkill, since in eg: a browser, there
// is only one JavaScript engine. But, I can imagine a system that had
// multiple JavaScript engines. Perhaps one per thread.
impl<T: Eq + Hash> Drop for Cacher<T> {
	fn drop(&mut self) {
		// Ensure we free all the heap allocations from our boxes,
		// and drop the js values contained in them.
		for (_key, value) in self.inner.borrow_mut().drain() {
			unsafe { Box::from_raw(value); }
		}
	}
}

impl<T: Into<JsValue> + Eq + Hash + Copy> Cacher<T> {
	fn cache(&self, value: T) -> *mut JsValue {
		let mut map = self.inner.borrow_mut();
		// Note that if Cacher is ever used outside this crate, we would need to make
		// this function re-entrant, since T::into<JsValue> could execute arbitrary
		// code, this could get called by it, and the borrow_mut() would panic.
		// For now, it's only used on types for which this is not a problem.

		*map.entry(value).or_insert_with(move || {
			let js_value: JsValue = value.into();
			Box::into_raw(Box::new(js_value))
		})
	}
}


/// For the purposes of this crate, floats are equal if and only if their bit patterns
/// are equal, since we are only responsible for the idea of caching the transfer of
/// the float into JavaScript and not what float semantics mean.
#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)]
#[repr(transparent)]
struct BitwiseFloat(u64);

impl From<BitwiseFloat> for JsValue {
	fn from(value: BitwiseFloat) -> JsValue {
		JsValue::from_f64(value.into())
	}
}

impl From<f64> for BitwiseFloat {
	fn from(value: f64) -> Self {
		unsafe { transmute(value) }
	}
}

impl From<BitwiseFloat> for f64 {
	fn from(value: BitwiseFloat) -> Self {
		unsafe { transmute(value) }
	}
}

/// Stores one copy of each distinct JavaScript primitive.
/// For example, ```js_intern!("string")``` evaluates to a ```&JsValue``` but only
/// does the translation from the utf-8 Rust string to the utf-16 JavaScript
/// string the first time the expression is evaluated. Furthermore, strings
/// are de-duplicated across the program. So, any time ```js_intern!("string")```
/// is used in the program, the same instance of the JavaScript string is used.
///
/// # Supported types
/// * ```&'static str``` Eg: ```js_intern!("str")```
/// * ```f64```, ```f32```, ```u8```, ```u16```, ```u32```, ```i8```, ```i16```, ```i32``` Eg: ```js_intern(1.0)```
/// * ```bool``` Eg: ```js_intern(true)```
///
/// # Warning: This is intended to work for literals only. It may presently work on expressions,
/// but this is not an intended part of the API and will break in a future release.
#[macro_export]
macro_rules! js_intern {
	($value:expr) => {
		{
			use wasm_bindgen::JsValue;
			use $crate::CacheJsIntern__;
			thread_local!(
				static INTERN: *mut JsValue = $value.cache_js_intern__();
			);

			// A word about the safety here. We are dereferencing a pointer
			// of type *mut JsValue. At the address of the pointer is a JsValue
			// instance that is boxed. The JsValue is only freed when the thread
			// goes out of scope. JsValue does not implement Send, so we know
			// that the value cannot be used anywhere it's invalid.
			unsafe { &*INTERN.with(|i| i.clone()) }
		}
	};
}