Skip to main content

yog_data/
atom.rs

1//! Atoms: names that repeat across messages (`minecraft:stone`, a live
2//! entity's kind) kept once a process and sent on the bus as their number.
3//! An [`Atom`] is a `u32`: copied, compared and packed as one; its text is
4//! the table's, alive as long as the process. A frame carries the number,
5//! every other form (text, the tagged binary form, files) the text — the
6//! numbers mean nothing outside the process.
7//!
8//! The table is the runtime's: a mod's copy of this crate is pointed at it
9//! ([`install`]) so every mod and the host agree on the numbers. Without
10//! one (tools, tests) the crate keeps its own.
11//!
12//! ```
13//! use yog_data::Atom;
14//!
15//! let stone = Atom::new("minecraft:stone");
16//! assert_eq!(stone, Atom::new("minecraft:stone"));
17//! assert_eq!(stone.as_str(), "minecraft:stone");
18//! ```
19
20use std::collections::HashMap;
21use std::fmt;
22use yog_sync::{OnceLock, RwLock};
23
24/// A name kept once a process; see the module.
25#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
26pub struct Atom(u32);
27
28/// The table's two functions: a name's number (made the first time), and
29/// a number's name (`None`: no such atom).
30#[derive(Clone, Copy)]
31pub struct Table {
32    pub intern: fn(&str) -> u32,
33    pub name: fn(u32) -> Option<&'static str>,
34}
35
36static TABLE: OnceLock<Table> = OnceLock::new();
37
38/// Points this copy of the crate at the process's table (the runtime's);
39/// before any atom is made. False if it has one already.
40pub fn install(table: Table) -> bool {
41    TABLE.set(table).is_ok()
42}
43
44fn table() -> &'static Table {
45    TABLE.get_or_init(|| Table {
46        intern: local::intern,
47        name: local::name,
48    })
49}
50
51impl Atom {
52    /// `name`'s atom.
53    pub fn new(name: &str) -> Atom {
54        Atom((table().intern)(name))
55    }
56
57    /// The atom numbered `n`, if there's one.
58    pub fn from_number(n: u32) -> Option<Atom> {
59        (table().name)(n).map(|_| Atom(n))
60    }
61
62    pub fn number(self) -> u32 {
63        self.0
64    }
65
66    pub fn as_str(self) -> &'static str {
67        (table().name)(self.0).unwrap_or_default()
68    }
69}
70
71impl fmt::Debug for Atom {
72    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73        write!(f, "Atom({:?})", self.as_str())
74    }
75}
76
77impl fmt::Display for Atom {
78    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79        f.write_str(self.as_str())
80    }
81}
82
83impl From<&str> for Atom {
84    fn from(name: &str) -> Atom {
85        Atom::new(name)
86    }
87}
88
89impl From<String> for Atom {
90    fn from(name: String) -> Atom {
91        Atom::new(&name)
92    }
93}
94
95impl From<&String> for Atom {
96    fn from(name: &String) -> Atom {
97        Atom::new(name)
98    }
99}
100
101impl PartialEq<str> for Atom {
102    fn eq(&self, other: &str) -> bool {
103        self.as_str() == other
104    }
105}
106
107impl PartialEq<&str> for Atom {
108    fn eq(&self, other: &&str) -> bool {
109        self.as_str() == *other
110    }
111}
112
113impl std::ops::Deref for Atom {
114    type Target = str;
115    fn deref(&self) -> &str {
116        self.as_str()
117    }
118}
119
120impl Default for Atom {
121    /// The empty name's atom.
122    fn default() -> Atom {
123        Atom::new("")
124    }
125}
126
127/// The table a process keeps itself: the runtime's, or a tool's.
128pub mod local;
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133
134    #[test]
135    fn a_name_is_one_atom() {
136        let a = Atom::new("test:atom_a");
137        assert_eq!(a, Atom::new("test:atom_a"));
138        assert_ne!(a, Atom::new("test:atom_b"));
139        assert_eq!(Atom::from_number(a.number()), Some(a));
140        assert_eq!(Atom::from_number(u32::MAX), None);
141        assert_eq!(a.to_string(), "test:atom_a");
142    }
143}