Skip to main content

fyrox_core/
sstorage.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! Immutable string + immutable string storage. See docs of [`ImmutableString`] and
22//! [`ImmutableStringStorage`] for more info.
23
24#![warn(missing_docs)]
25
26use crate::{
27    parking_lot::Mutex,
28    uuid_provider,
29    visitor::{Visit, VisitResult, Visitor},
30    SafeLock,
31};
32use fxhash::{FxHashMap, FxHasher};
33pub use fyrox_core_derive::TypeUuidProvider;
34use serde::{Deserialize, Serialize};
35use std::{
36    fmt::{Debug, Display, Formatter},
37    hash::{Hash, Hasher},
38    ops::Deref,
39    sync::{Arc, LazyLock},
40};
41
42#[derive(Clone, Debug)]
43struct State {
44    string: String,
45    hash: u64,
46}
47
48/// Immutable string is a string with constant content. Immutability gives some nice properties:
49///
50/// - Address of the string could be used as a hash, which improves hashing performance dramatically
51/// and basically making it constant in terms of complexity (O(1))
52/// - Equality comparison becomes constant in terms of complexity.
53/// - Uniqueness guarantees - means that calling multiple times will allocate memory only once
54/// `ImmutableString::new("foo")` and in consecutive calls existing string will be used.
55///
56/// # Use cases
57///
58/// Most common use case for immutable strings is hash map keys in performance-critical places.
59#[derive(Clone)]
60pub struct ImmutableString(Arc<State>);
61
62uuid_provider!(ImmutableString = "452caac1-19f7-43d6-9e33-92c2c9163332");
63
64impl Display for ImmutableString {
65    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
66        f.write_str(self.0.string.as_ref())
67    }
68}
69
70impl Debug for ImmutableString {
71    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
72        Debug::fmt(&self.0.string, f)
73    }
74}
75
76impl From<ImmutableString> for String {
77    fn from(value: ImmutableString) -> Self {
78        value.0.string.clone()
79    }
80}
81
82impl Visit for ImmutableString {
83    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
84        // Serialize/deserialize as ordinary string.
85        let mut string = self.0.string.clone();
86        string.visit(name, visitor)?;
87
88        // Deduplicate on deserialization.
89        if visitor.is_reading() {
90            *self = SSTORAGE.safe_lock().insert(string);
91        }
92
93        Ok(())
94    }
95}
96
97impl Serialize for ImmutableString {
98    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
99    where
100        S: serde::Serializer,
101    {
102        serializer.serialize_str(self.as_str())
103    }
104}
105
106impl<'de> Deserialize<'de> for ImmutableString {
107    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
108    where
109        D: serde::Deserializer<'de>,
110    {
111        Ok(ImmutableString::new(
112            deserializer.deserialize_string(ImmutableStringVisitor {})?,
113        ))
114    }
115}
116
117struct ImmutableStringVisitor {}
118
119impl serde::de::Visitor<'_> for ImmutableStringVisitor {
120    type Value = ImmutableString;
121
122    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
123        write!(formatter, "a string")
124    }
125
126    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
127    where
128        E: serde::de::Error,
129    {
130        Ok(ImmutableString::new(v))
131    }
132
133    fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
134    where
135        E: serde::de::Error,
136    {
137        Ok(v.into())
138    }
139}
140
141impl Default for ImmutableString {
142    fn default() -> Self {
143        Self::new("")
144    }
145}
146
147impl AsRef<str> for ImmutableString {
148    fn as_ref(&self) -> &str {
149        self.deref()
150    }
151}
152
153impl ImmutableString {
154    /// Creates new immutable string from given string slice.
155    ///
156    /// # Performance
157    ///
158    /// This method has amortized O(1) complexity, in worst case (when there is no such string
159    /// in backing storage) it allocates memory which could lead to complexity defined by current
160    /// memory allocator.
161    #[inline]
162    pub fn new<S: AsRef<str>>(string: S) -> ImmutableString {
163        SSTORAGE.safe_lock().insert(string)
164    }
165
166    /// Returns unique identifier of the string. Keep in mind that uniqueness is guaranteed only
167    /// for a single session, uniqueness is not preserved between application runs.
168    #[inline]
169    pub fn cached_hash(&self) -> u64 {
170        self.0.hash
171    }
172
173    /// Clones content of inner immutable string to a mutable string.
174    #[inline]
175    pub fn to_mutable(&self) -> String {
176        self.0.string.clone()
177    }
178
179    /// Get a reference to the inner str.
180    pub fn as_str(&self) -> &str {
181        self.deref()
182    }
183}
184
185impl From<&str> for ImmutableString {
186    fn from(value: &str) -> Self {
187        Self::new(value)
188    }
189}
190
191impl From<String> for ImmutableString {
192    fn from(value: String) -> Self {
193        SSTORAGE.safe_lock().insert_owned(value)
194    }
195}
196
197impl From<&String> for ImmutableString {
198    fn from(value: &String) -> Self {
199        SSTORAGE.safe_lock().insert(value)
200    }
201}
202
203impl Deref for ImmutableString {
204    type Target = str;
205
206    #[inline]
207    fn deref(&self) -> &Self::Target {
208        self.0.string.as_ref()
209    }
210}
211
212impl Hash for ImmutableString {
213    #[inline]
214    fn hash<H: Hasher>(&self, state: &mut H) {
215        state.write_u64(self.cached_hash())
216    }
217}
218
219impl PartialEq for ImmutableString {
220    #[inline]
221    fn eq(&self, other: &Self) -> bool {
222        self.cached_hash() == other.cached_hash()
223    }
224}
225
226impl Eq for ImmutableString {}
227
228/// Immutable string storage is a backing storage for every immutable string in the application,
229/// storage is a singleton. In normal circumstances you should never use it directly.
230#[derive(Default)]
231pub struct ImmutableStringStorage {
232    vec: FxHashMap<u64, Arc<State>>,
233}
234
235impl ImmutableStringStorage {
236    #[inline]
237    fn insert<S: AsRef<str>>(&mut self, string: S) -> ImmutableString {
238        let mut hasher = FxHasher::default();
239        string.as_ref().hash(&mut hasher);
240        let hash = hasher.finish();
241
242        if let Some(existing) = self.vec.get(&hash) {
243            ImmutableString(existing.clone())
244        } else {
245            let immutable = Arc::new(State {
246                string: string.as_ref().to_owned(),
247                hash,
248            });
249            self.vec.insert(hash, immutable.clone());
250            ImmutableString(immutable)
251        }
252    }
253    /// Insert without copying the given String.
254    #[inline]
255    fn insert_owned(&mut self, string: String) -> ImmutableString {
256        let mut hasher = FxHasher::default();
257        string.hash(&mut hasher);
258        let hash = hasher.finish();
259
260        if let Some(existing) = self.vec.get(&hash) {
261            ImmutableString(existing.clone())
262        } else {
263            let immutable = Arc::new(State { string, hash });
264            self.vec.insert(hash, immutable.clone());
265            ImmutableString(immutable)
266        }
267    }
268}
269
270impl ImmutableStringStorage {
271    /// Returns total amount of immutable strings in the storage.
272    pub fn entry_count() -> usize {
273        SSTORAGE.safe_lock().vec.len()
274    }
275}
276
277static SSTORAGE: LazyLock<Arc<Mutex<ImmutableStringStorage>>> =
278    LazyLock::new(|| Arc::new(Mutex::new(ImmutableStringStorage::default())));
279
280#[cfg(test)]
281mod test {
282    use super::*;
283
284    #[test]
285    fn test_immutable_string_distinctness() {
286        let a = ImmutableString::new("Foobar");
287        let b = ImmutableString::new("rabooF");
288
289        assert_ne!(a.cached_hash(), b.cached_hash())
290    }
291
292    #[test]
293    fn test_immutable_string_uniqueness() {
294        let a = ImmutableString::new("Foobar");
295        let b = ImmutableString::new("Foobar");
296
297        // All tests share the same ImmutableStringStorage, so there is no way
298        // to know what this value should be. It depends on the order the test
299        // are run.
300        // assert_eq!(ImmutableStringStorage::entry_count(), 2);
301        assert_eq!(a.cached_hash(), b.cached_hash())
302    }
303
304    #[test]
305    fn test_immutable_string_uniqueness_from_owned() {
306        let a = ImmutableString::new("Foobar");
307        let b = ImmutableString::from("Foobar".to_owned());
308
309        assert_eq!(a.cached_hash(), b.cached_hash())
310    }
311
312    #[test]
313    fn visit_for_immutable_string() {
314        let mut a = ImmutableString::new("Foobar");
315        let mut visitor = Visitor::default();
316
317        assert!(a.visit("name", &mut visitor).is_ok());
318    }
319
320    #[test]
321    fn debug_for_immutable_string() {
322        let a = ImmutableString::new("Foobar");
323
324        assert_eq!(format!("{a:?}"), "\"Foobar\"");
325    }
326
327    #[test]
328    fn debug_for_immutable_string_from_owned() {
329        let a = ImmutableString::from("Foobar".to_owned());
330
331        assert_eq!(format!("{a:?}"), "\"Foobar\"");
332    }
333
334    #[test]
335    fn default_for_immutable_string() {
336        let a = ImmutableString::default();
337
338        assert_eq!(a.0.string, "");
339    }
340
341    #[test]
342    fn immutable_string_to_mutable() {
343        let a = ImmutableString::new("Foobar");
344
345        assert_eq!(a.to_mutable(), String::from("Foobar"));
346    }
347
348    #[test]
349    fn deref_for_immutable_string() {
350        let s = "Foobar";
351        let a = ImmutableString::new(s);
352
353        assert_eq!(a.deref(), s);
354    }
355
356    #[test]
357    fn eq_for_immutable_string() {
358        let a = ImmutableString::new("Foobar");
359        let b = ImmutableString::new("Foobar");
360
361        assert!(a == b);
362    }
363}