1 | use cfg_if::cfg_if; |
2 | |
3 | |
4 | cfg_if! { |
5 | if #[cfg(feature = "enable-interning" )] { |
6 | use std::thread_local; |
7 | use std::string::String; |
8 | use std::borrow::ToOwned; |
9 | use std::cell::RefCell; |
10 | use std::collections::HashMap; |
11 | use crate::JsValue; |
12 | |
13 | struct Cache { |
14 | entries: RefCell<HashMap<String, JsValue>>, |
15 | } |
16 | |
17 | thread_local! { |
18 | static CACHE: Cache = Cache { |
19 | entries: RefCell::new(HashMap::new()), |
20 | }; |
21 | } |
22 | |
23 | /// This returns the raw index of the cached JsValue, so you must take care |
24 | /// so that you don't use it after it is freed. |
25 | pub(crate) fn unsafe_get_str(s: &str) -> Option<u32> { |
26 | CACHE.with(|cache| { |
27 | let cache = cache.entries.borrow(); |
28 | |
29 | cache.get(s).map(|x| x.idx) |
30 | }) |
31 | } |
32 | |
33 | fn intern_str(key: &str) { |
34 | CACHE.with(|cache| { |
35 | let mut cache = cache.entries.borrow_mut(); |
36 | |
37 | // Can't use `entry` because `entry` requires a `String` |
38 | if !cache.contains_key(key) { |
39 | cache.insert(key.to_owned(), JsValue::from(key)); |
40 | } |
41 | }) |
42 | } |
43 | |
44 | fn unintern_str(key: &str) { |
45 | CACHE.with(|cache| { |
46 | let mut cache = cache.entries.borrow_mut(); |
47 | |
48 | cache.remove(key); |
49 | }) |
50 | } |
51 | } |
52 | } |
53 | |
54 | |
55 | /// Interns Rust strings so that it's much faster to send them to JS. |
56 | /// |
57 | /// Sending strings from Rust to JS is slow, because it has to do a full `O(n)` |
58 | /// copy and *also* encode from UTF-8 to UTF-16. This must be done every single |
59 | /// time a string is sent to JS. |
60 | /// |
61 | /// If you are sending the same string multiple times, you can call this `intern` |
62 | /// function, which simply returns its argument unchanged: |
63 | /// |
64 | /// ```rust |
65 | /// # use wasm_bindgen::intern; |
66 | /// intern("foo" ) // returns "foo" |
67 | /// # ; |
68 | /// ``` |
69 | /// |
70 | /// However, if you enable the `"enable-interning"` feature for wasm-bindgen, |
71 | /// then it will add the string into an internal cache. |
72 | /// |
73 | /// When you send that cached string to JS, it will look it up in the cache, |
74 | /// which completely avoids the `O(n)` copy and encoding. This has a significant |
75 | /// speed boost (as high as 783%)! |
76 | /// |
77 | /// However, there is a small cost to this caching, so you shouldn't cache every |
78 | /// string. Only cache strings which have a high likelihood of being sent |
79 | /// to JS multiple times. |
80 | /// |
81 | /// Also, keep in mind that this function is a *performance hint*: it's not |
82 | /// *guaranteed* that the string will be cached, and the caching strategy |
83 | /// might change at any time, so don't rely upon it. |
84 | #[inline ] |
85 | pub fn intern(s: &str) -> &str { |
86 | #[cfg (feature = "enable-interning" )] |
87 | intern_str(s); |
88 | |
89 | s |
90 | } |
91 | |
92 | |
93 | /// Removes a Rust string from the intern cache. |
94 | /// |
95 | /// This does the opposite of the [`intern`](fn.intern.html) function. |
96 | /// |
97 | /// If the [`intern`](fn.intern.html) function is called again then it will re-intern the string. |
98 | #[allow (unused_variables)] |
99 | #[inline ] |
100 | pub fn unintern(s: &str) { |
101 | #[cfg (feature = "enable-interning" )] |
102 | unintern_str(s); |
103 | } |
104 | |