Skip to main content

once_list2/
cache_mode.rs

1// Copyright 2021 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use ::allocator_api2::alloc::Allocator;
16use ::allocator_api2::boxed::Box;
17use ::std::cell::Cell;
18use ::std::ptr::NonNull;
19
20use crate::cons::Cons;
21use crate::oncecell_ext::OnceCellExt;
22use crate::OnceCell;
23
24mod sealed {
25    pub trait Sealed {}
26}
27
28/// A "next node" slot (a thin wrapper around an internal `OnceCell`).
29///
30/// This type is used for:
31/// - The list's **head slot** (a slot that points to the first node)
32/// - Each node's **next slot** (`node.next`)
33/// - Optional **tail insertion caching** (caching a pointer to some node's `next` slot)
34///
35/// Caching focuses on the tail insertion hot path, but the slot itself is conceptually "the next slot"
36/// in a singly-linked list.
37#[doc(hidden)]
38#[derive(Clone)]
39pub struct NextSlot<T: ?Sized, A: Allocator> {
40    cell: OnceCell<Box<Cons<T, T, A>, A>>,
41}
42
43impl<T: ?Sized, A: Allocator> NextSlot<T, A> {
44    pub(crate) fn new() -> Self {
45        Self {
46            cell: OnceCell::new(),
47        }
48    }
49
50    pub(crate) fn get(&self) -> Option<&Box<Cons<T, T, A>, A>> {
51        self.cell.get()
52    }
53
54    pub(crate) fn get_mut(&mut self) -> Option<&mut Box<Cons<T, T, A>, A>> {
55        self.cell.get_mut()
56    }
57
58    pub(crate) fn set(&self, value: Box<Cons<T, T, A>, A>) -> Result<(), Box<Cons<T, T, A>, A>> {
59        self.cell.set(value)
60    }
61
62    pub(crate) fn take(&mut self) -> Option<Box<Cons<T, T, A>, A>> {
63        self.cell.take()
64    }
65
66    pub(crate) fn try_insert2(
67        &self,
68        value: Box<Cons<T, T, A>, A>,
69    ) -> Result<&Box<Cons<T, T, A>, A>, (&Box<Cons<T, T, A>, A>, Box<Cons<T, T, A>, A>)> {
70        self.cell.try_insert2(value)
71    }
72}
73
74/// Cache mode for `OnceList` (e.g. tail cache, len cache).
75///
76/// This trait is **sealed**: downstream crates cannot implement it.
77#[doc(hidden)]
78pub trait CacheMode<T: ?Sized, A: Allocator>: sealed::Sealed + Clone {
79    /// Returns cached length if available.
80    fn cached_len(&self) -> Option<usize> {
81        None
82    }
83
84    /// Returns a cached tail insertion slot, if available.
85    ///
86    /// Returning `None` means the caller should fall back to scanning from the head.
87    fn tail_slot_opt<'a>(&'a self) -> Option<&'a NextSlot<T, A>> {
88        None
89    }
90
91    /// Called after a push successfully inserted a node.
92    ///
93    /// Cache modes that track tail insertion slots and/or length should override this.
94    fn on_push_success(&self, _next_slot: &NextSlot<T, A>) {}
95
96    /// Called after a remove successfully removed a node.
97    fn on_remove_success(&self) {}
98
99    /// Called when the list is cleared.
100    fn on_clear(&self) {}
101
102    /// Called when list structure may change via `&mut self` methods (e.g. remove).
103    ///
104    /// Implementations should drop any cached pointers/slots that could become stale.
105    fn on_structure_change(&self);
106}
107
108/// No caching. This is the original behavior.
109#[derive(Clone, Copy, Default)]
110pub struct NoCache;
111
112impl sealed::Sealed for NoCache {}
113
114impl<T: ?Sized, A: Allocator> CacheMode<T, A> for NoCache {
115    fn on_structure_change(&self) {}
116}
117
118/// Tail caching mode (single-thread oriented).
119///
120/// This caches the *next insertion slot* (`node.next`), not the tail node itself.
121///
122/// IMPORTANT: This never caches `&head` (which would become dangling after a move),
123/// it only caches pointers into heap-allocated nodes.
124pub struct WithTail<T: ?Sized, A: Allocator> {
125    next_slot: Cell<Option<SlotPtr<T, A>>>,
126}
127
128type SlotPtr<T, A> = NonNull<NextSlot<T, A>>;
129
130impl<T: ?Sized, A: Allocator> Clone for WithTail<T, A> {
131    fn clone(&self) -> Self {
132        // Do NOT clone the pointer; it would point into the other list.
133        Self::new()
134    }
135}
136
137impl<T: ?Sized, A: Allocator> sealed::Sealed for WithTail<T, A> {}
138
139impl<T: ?Sized, A: Allocator> CacheMode<T, A> for WithTail<T, A> {
140    fn tail_slot_opt<'a>(&'a self) -> Option<&'a NextSlot<T, A>> {
141        if let Some(p) = self.next_slot.get() {
142            let slot = unsafe { p.as_ref() };
143            // Fast-path: if the cached slot is still empty, use it.
144            if slot.get().is_none() {
145                return Some(slot);
146            }
147        }
148        None
149    }
150
151    fn on_push_success(&self, next_slot: &NextSlot<T, A>) {
152        self.next_slot.set(Some(NonNull::from(next_slot)));
153    }
154
155    fn on_structure_change(&self) {
156        self.next_slot.set(None);
157    }
158}
159
160impl<T: ?Sized, A: Allocator> WithTail<T, A> {
161    pub(crate) fn new() -> Self {
162        Self {
163            next_slot: Cell::new(None),
164        }
165    }
166}
167
168impl<T: ?Sized, A: Allocator> Default for WithTail<T, A> {
169    fn default() -> Self {
170        Self::new()
171    }
172}
173
174/// Len-only caching mode (single-thread oriented).
175pub struct WithLen<T: ?Sized, A: Allocator> {
176    len: Cell<usize>,
177    _phantom: ::std::marker::PhantomData<fn(&T, &A)>,
178}
179
180impl<T: ?Sized, A: Allocator> Clone for WithLen<T, A> {
181    fn clone(&self) -> Self {
182        Self {
183            len: Cell::new(self.len.get()),
184            _phantom: ::std::marker::PhantomData,
185        }
186    }
187}
188
189impl<T: ?Sized, A: Allocator> sealed::Sealed for WithLen<T, A> {}
190
191impl<T: ?Sized, A: Allocator> CacheMode<T, A> for WithLen<T, A> {
192    fn cached_len(&self) -> Option<usize> {
193        Some(self.len.get())
194    }
195
196    fn on_push_success(&self, _next_slot: &NextSlot<T, A>) {
197        self.len.set(self.len.get() + 1);
198    }
199
200    fn on_remove_success(&self) {
201        self.len.set(self.len.get() - 1);
202    }
203
204    fn on_clear(&self) {
205        self.len.set(0);
206    }
207
208    fn on_structure_change(&self) {
209        // Nothing to invalidate.
210    }
211}
212
213impl<T: ?Sized, A: Allocator> WithLen<T, A> {
214    pub(crate) fn new() -> Self {
215        Self {
216            len: Cell::new(0),
217            _phantom: ::std::marker::PhantomData,
218        }
219    }
220}
221
222impl<T: ?Sized, A: Allocator> Default for WithLen<T, A> {
223    fn default() -> Self {
224        Self::new()
225    }
226}
227
228/// Tail + len caching mode (single-thread oriented).
229pub struct WithTailLen<T: ?Sized, A: Allocator> {
230    next_slot: Cell<Option<SlotPtr<T, A>>>,
231    len: Cell<usize>,
232}
233
234impl<T: ?Sized, A: Allocator> Clone for WithTailLen<T, A> {
235    fn clone(&self) -> Self {
236        // Do NOT clone the pointer; it would point into the other list.
237        // Cloning len is fine (it's a value).
238        Self {
239            next_slot: Cell::new(None),
240            len: Cell::new(self.len.get()),
241        }
242    }
243}
244
245impl<T: ?Sized, A: Allocator> sealed::Sealed for WithTailLen<T, A> {}
246
247impl<T: ?Sized, A: Allocator> CacheMode<T, A> for WithTailLen<T, A> {
248    fn cached_len(&self) -> Option<usize> {
249        Some(self.len.get())
250    }
251
252    fn tail_slot_opt<'a>(&'a self) -> Option<&'a NextSlot<T, A>> {
253        if let Some(p) = self.next_slot.get() {
254            let slot = unsafe { p.as_ref() };
255            if slot.get().is_none() {
256                return Some(slot);
257            }
258        }
259        None
260    }
261
262    fn on_push_success(&self, next_slot: &NextSlot<T, A>) {
263        self.len.set(self.len.get() + 1);
264        self.next_slot.set(Some(NonNull::from(next_slot)));
265    }
266
267    fn on_remove_success(&self) {
268        self.len.set(self.len.get() - 1);
269    }
270
271    fn on_clear(&self) {
272        self.len.set(0);
273        self.next_slot.set(None);
274    }
275
276    fn on_structure_change(&self) {
277        // Keep `len` (it is still correct); only invalidate tail slot.
278        self.next_slot.set(None);
279    }
280}
281
282impl<T: ?Sized, A: Allocator> WithTailLen<T, A> {
283    pub(crate) fn new() -> Self {
284        Self {
285            next_slot: Cell::new(None),
286            len: Cell::new(0),
287        }
288    }
289}
290
291impl<T: ?Sized, A: Allocator> Default for WithTailLen<T, A> {
292    fn default() -> Self {
293        Self::new()
294    }
295}