forked from mirrors/jj
repo: reimplement DirtyCell without using unsafe
While the safe implementation is a bit more complex (and probably more branchy), I don't think the runtime overhead would matter here. Let's remove one more unsafe for better code maintainability.
This commit is contained in:
parent
57d7aeab5e
commit
6ff3a4f3df
1 changed files with 33 additions and 19 deletions
|
@ -1340,53 +1340,67 @@ pub enum CheckOutCommitError {
|
||||||
}
|
}
|
||||||
|
|
||||||
mod dirty_cell {
|
mod dirty_cell {
|
||||||
use std::cell::{Cell, RefCell};
|
use std::cell::{OnceCell, RefCell};
|
||||||
|
|
||||||
/// Cell that lazily updates the value after `mark_dirty()`.
|
/// Cell that lazily updates the value after `mark_dirty()`.
|
||||||
|
///
|
||||||
|
/// A clean value can be immutably borrowed within the `self` lifetime.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct DirtyCell<T> {
|
pub struct DirtyCell<T> {
|
||||||
value: RefCell<T>,
|
// Either clean or dirty value is set. The value is boxed to reduce stack space
|
||||||
dirty: Cell<bool>,
|
// and memcopy overhead.
|
||||||
|
clean: OnceCell<Box<T>>,
|
||||||
|
dirty: RefCell<Option<Box<T>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> DirtyCell<T> {
|
impl<T> DirtyCell<T> {
|
||||||
pub fn with_clean(value: T) -> Self {
|
pub fn with_clean(value: T) -> Self {
|
||||||
DirtyCell {
|
DirtyCell {
|
||||||
value: RefCell::new(value),
|
clean: OnceCell::from(Box::new(value)),
|
||||||
dirty: Cell::new(false),
|
dirty: RefCell::new(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_or_ensure_clean(&self, f: impl FnOnce(&mut T)) -> &T {
|
pub fn get_or_ensure_clean(&self, f: impl FnOnce(&mut T)) -> &T {
|
||||||
// SAFETY: get_mut/mark_dirty(&mut self) should invalidate any previously-clean
|
self.clean.get_or_init(|| {
|
||||||
// references leaked by this method. Clean value never changes until then.
|
// Panics if ensure_clean() is invoked from with_ref() callback for example.
|
||||||
self.ensure_clean(f);
|
let mut value = self.dirty.borrow_mut().take().unwrap();
|
||||||
unsafe { &*self.value.as_ptr() }
|
f(&mut value);
|
||||||
|
value
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ensure_clean(&self, f: impl FnOnce(&mut T)) {
|
pub fn ensure_clean(&self, f: impl FnOnce(&mut T)) {
|
||||||
if self.dirty.get() {
|
self.get_or_ensure_clean(f);
|
||||||
// This borrow_mut() ensures that there is no dirty temporary reference.
|
|
||||||
// Panics if ensure_clean() is invoked from with_ref() callback for example.
|
|
||||||
f(&mut self.value.borrow_mut());
|
|
||||||
self.dirty.set(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_inner(self) -> T {
|
pub fn into_inner(self) -> T {
|
||||||
self.value.into_inner()
|
*self
|
||||||
|
.clean
|
||||||
|
.into_inner()
|
||||||
|
.or_else(|| self.dirty.into_inner())
|
||||||
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_ref<R>(&self, f: impl FnOnce(&T) -> R) -> R {
|
pub fn with_ref<R>(&self, f: impl FnOnce(&T) -> R) -> R {
|
||||||
f(&self.value.borrow())
|
if let Some(value) = self.clean.get() {
|
||||||
|
f(value)
|
||||||
|
} else {
|
||||||
|
f(self.dirty.borrow().as_ref().unwrap())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_mut(&mut self) -> &mut T {
|
pub fn get_mut(&mut self) -> &mut T {
|
||||||
self.value.get_mut()
|
self.clean
|
||||||
|
.get_mut()
|
||||||
|
.or_else(|| self.dirty.get_mut().as_mut())
|
||||||
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mark_dirty(&mut self) {
|
pub fn mark_dirty(&mut self) {
|
||||||
*self.dirty.get_mut() = true;
|
if let Some(value) = self.clean.take() {
|
||||||
|
*self.dirty.get_mut() = Some(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue