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