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