Skip to main content

ferroui_base/utilities/
read_only_memory.rs

1//! `ReadOnlyMemory<T>`: a cheaply cloneable, sliceable, immutable view over a
2//! shared buffer (the counterpart of .NET's `ReadOnlyMemory<T>`).
3//!
4//! Text in the text formatting subsystem is `ReadOnlyMemory<u16>` (UTF-16 code
5//! units, see `media/text_formatting/mod.rs` for the index-unit decision) and
6//! font tables are `ReadOnlyMemory<u8>`.
7
8use std::fmt;
9use std::rc::Rc;
10
11/// An immutable, reference counted slice of a shared buffer.
12///
13/// Cloning and slicing never copy the elements; they bump one reference count.
14pub struct ReadOnlyMemory<T> {
15    owner: Option<Rc<[T]>>,
16    start: usize,
17    length: usize,
18}
19
20impl<T> ReadOnlyMemory<T> {
21    /// The empty memory (C# `default` / `ReadOnlyMemory<T>.Empty`).
22    pub const fn empty() -> Self {
23        Self { owner: None, start: 0, length: 0 }
24    }
25
26    /// Wraps a whole shared buffer.
27    pub fn new(owner: Rc<[T]>) -> Self {
28        let length = owner.len();
29        Self { owner: Some(owner), start: 0, length }
30    }
31
32    /// Wraps `length` elements of a shared buffer starting at `start`.
33    ///
34    /// Panics when the range is outside the buffer.
35    pub fn with_range(owner: Rc<[T]>, start: usize, length: usize) -> Self {
36        assert!(start <= owner.len() && length <= owner.len() - start, "range outside of the buffer");
37        Self { owner: Some(owner), start, length }
38    }
39
40    /// Takes ownership of a vector.
41    pub fn from_vec(values: Vec<T>) -> Self {
42        Self::new(Rc::from(values))
43    }
44
45    /// The number of elements (C# `Length`).
46    #[inline]
47    pub fn len(&self) -> usize {
48        self.length
49    }
50
51    /// Whether the memory has no elements (C# `IsEmpty`).
52    #[inline]
53    pub fn is_empty(&self) -> bool {
54        self.length == 0
55    }
56
57    /// The elements (C# `Span`).
58    #[inline]
59    pub fn span(&self) -> &[T] {
60        match &self.owner {
61            Some(owner) => &owner[self.start..self.start + self.length],
62            None => &[],
63        }
64    }
65
66    /// The elements from `start` to the end (C# `Slice(start)`).
67    ///
68    /// Panics when `start` is outside the memory.
69    pub fn slice_from(&self, start: usize) -> Self {
70        assert!(start <= self.length, "start outside of the memory");
71        self.slice(start, self.length - start)
72    }
73
74    /// `length` elements starting at `start` (C# `Slice(start, length)`).
75    ///
76    /// Panics when the range is outside the memory.
77    pub fn slice(&self, start: usize, length: usize) -> Self {
78        assert!(start <= self.length && length <= self.length - start, "range outside of the memory");
79        Self { owner: self.owner.clone(), start: self.start + start, length }
80    }
81
82    /// The offset of this memory inside its underlying buffer.
83    #[inline]
84    pub fn offset_in_owner(&self) -> usize {
85        self.start
86    }
87
88    /// The underlying buffer, when there is one.
89    #[inline]
90    pub fn owner(&self) -> Option<&Rc<[T]>> {
91        self.owner.as_ref()
92    }
93
94    /// Whether both memories view the same buffer (regardless of range).
95    pub fn shares_owner_with(&self, other: &Self) -> bool {
96        match (&self.owner, &other.owner) {
97            (Some(a), Some(b)) => Rc::ptr_eq(a, b),
98            (None, None) => true,
99            _ => false,
100        }
101    }
102}
103
104impl<T: Clone> ReadOnlyMemory<T> {
105    /// Copies a slice into a new shared buffer.
106    pub fn from_slice(values: &[T]) -> Self {
107        Self::new(Rc::from(values))
108    }
109
110    /// Copies the elements out (C# `ToArray`).
111    pub fn to_vec(&self) -> Vec<T> {
112        self.span().to_vec()
113    }
114}
115
116impl ReadOnlyMemory<u16> {
117    /// Encodes a string as UTF-16 code units (C# `string.AsMemory()`).
118    pub fn from_str(text: &str) -> Self {
119        Self::from_vec(text.encode_utf16().collect())
120    }
121
122    /// Decodes the UTF-16 code units, replacing lone surrogates with U+FFFD.
123    pub fn to_string_lossy(&self) -> String {
124        String::from_utf16_lossy(self.span())
125    }
126}
127
128impl<T> Clone for ReadOnlyMemory<T> {
129    #[inline]
130    fn clone(&self) -> Self {
131        Self { owner: self.owner.clone(), start: self.start, length: self.length }
132    }
133}
134
135impl<T> Default for ReadOnlyMemory<T> {
136    fn default() -> Self {
137        Self::empty()
138    }
139}
140
141impl<T: fmt::Debug> fmt::Debug for ReadOnlyMemory<T> {
142    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143        f.debug_list().entries(self.span()).finish()
144    }
145}
146
147/// Element-wise equality (unlike C#, where `ReadOnlyMemory<T>.Equals` compares
148/// the underlying object and range; use [`ReadOnlyMemory::shares_owner_with`]
149/// for that).
150impl<T: PartialEq> PartialEq for ReadOnlyMemory<T> {
151    fn eq(&self, other: &Self) -> bool {
152        self.span() == other.span()
153    }
154}
155
156impl<T> From<Vec<T>> for ReadOnlyMemory<T> {
157    fn from(values: Vec<T>) -> Self {
158        Self::from_vec(values)
159    }
160}
161
162impl<T> From<Rc<[T]>> for ReadOnlyMemory<T> {
163    fn from(values: Rc<[T]>) -> Self {
164        Self::new(values)
165    }
166}
167
168impl From<&str> for ReadOnlyMemory<u16> {
169    fn from(text: &str) -> Self {
170        Self::from_str(text)
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177
178    #[test]
179    fn slices_share_the_buffer() {
180        let memory = ReadOnlyMemory::from_vec(vec![1, 2, 3, 4, 5]);
181        let slice = memory.slice(1, 3);
182        assert_eq!(slice.span(), &[2, 3, 4]);
183        assert_eq!(slice.slice_from(1).span(), &[3, 4]);
184        assert_eq!(slice.offset_in_owner(), 1);
185        assert!(slice.shares_owner_with(&memory));
186        assert!(ReadOnlyMemory::<i32>::empty().is_empty());
187    }
188
189    #[test]
190    fn utf16_round_trip() {
191        let memory = ReadOnlyMemory::<u16>::from_str("a\u{1F600}b");
192        assert_eq!(memory.len(), 4);
193        assert_eq!(memory.to_string_lossy(), "a\u{1F600}b");
194    }
195}