use super::addr::{GridAddr, VertexAddr};
use super::vertex::{VertexId, VertexKind};
use crate::SheetId;
use std::sync::atomic::{AtomicU8, Ordering};
#[cfg(test)]
mod tests {
use super::*;
use crate::engine::addr::SymbolAddr;
fn grid(row: u32, col: u32) -> VertexAddr {
VertexAddr::grid(GridAddr::new(row, col))
}
#[test]
fn test_vertex_store_allocation() {
let mut store = VertexStore::new();
let id = store.allocate(grid(10, 20), 1, 0x01);
assert_eq!(store.addr(id), grid(10, 20));
assert_eq!(store.sheet_id(id), 1);
assert_eq!(store.flags(id), 0x01);
}
#[test]
fn prepared_batch_vertex_overflow_is_checked_before_mutation() {
let mut store = VertexStore::new();
store.len = u32::MAX as usize - FIRST_NORMAL_VERTEX as usize + 1;
let before = (store.cold_rows(), store.flags.len());
assert_eq!(
store.try_allocate_batch(&[(grid(0, 0), 0, 0)], &[VertexId(FIRST_NORMAL_VERTEX)],),
Err(VertexBatchAllocationError::IdExhausted)
);
assert_eq!(before, (store.cold_rows(), store.flags.len()));
}
#[test]
fn prepared_batch_reserved_id_mismatch_is_checked_before_mutation() {
let mut store = VertexStore::new();
let before = (store.len(), store.cold_rows(), store.flags.len());
assert_eq!(
store.try_allocate_batch(&[(grid(0, 0), 0, 0)], &[VertexId(FIRST_NORMAL_VERTEX + 1)],),
Err(VertexBatchAllocationError::ReservedIdsMismatch)
);
assert_eq!(before, (store.len(), store.cold_rows(), store.flags.len()));
}
#[test]
fn test_vertex_store_grow() {
let mut store = VertexStore::with_capacity(1000);
for i in 0..10_000 {
store.allocate(grid(i, i), 0, 0);
}
assert_eq!(store.len(), 10_000);
}
#[test]
fn test_vertex_store_capacity() {
let store = VertexStore::with_capacity(100);
assert!(store.cold_capacity() >= 100);
assert!(store.flags.capacity() >= 100);
}
#[test]
fn test_vertex_store_accessors() {
let mut store = VertexStore::new();
let id = store.allocate(grid(5, 10), 3, 0x03);
let position = store
.grid_addr(id)
.expect("cell vertices carry a grid position");
assert_eq!(position.row(), 5);
assert_eq!(position.col(), 10);
assert_eq!(store.sheet_id(id), 3);
assert_eq!(store.flags(id), 0x03);
assert!(store.is_dirty(id));
assert!(store.is_volatile(id));
store.set_kind(id, VertexKind::Cell);
assert_eq!(store.kind(id), VertexKind::Cell);
}
#[test]
fn symbol_vertices_have_no_grid_position() {
let mut store = VertexStore::new();
let cell = store.allocate(grid(3, 4), 0, 0);
let symbol = store.allocate(VertexAddr::symbol(SymbolAddr::new(0)), 0, 0);
assert_eq!(store.grid_addr(cell), Some(GridAddr::new(3, 4)));
assert_eq!(store.grid_addr(symbol), None);
assert!(store.addr(symbol).is_symbol());
assert_eq!(store.addr(symbol).as_symbol(), Some(SymbolAddr::new(0)));
}
#[test]
fn stored_address_element_size_is_unchanged() {
assert_eq!(std::mem::size_of::<VertexAddr>(), 8);
assert_eq!(
std::mem::size_of::<VertexAddr>(),
std::mem::size_of::<formualizer_common::Coord>(),
);
}
#[test]
fn member_pages_drop_their_rows_and_come_back() {
let mut store = VertexStore::new();
let first = store.allocate(grid(5, 4), 2, 0);
for r in 6..5 + 3000 {
store.allocate(grid(r, 4), 2, 0);
}
store.allocate(grid(0, 0), 2, 0);
for i in 0..3000 {
let v = VertexId(first.0 + i);
store.set_kind(v, VertexKind::FormulaScalar);
store.set_edge_offset(v, 2);
}
let dense = store.authority_gate_heap_bytes();
assert_eq!(store.virtualize_member_span(first, 3000, 2, 4, 5), 2);
assert_eq!(store.virtual_pages(), 2);
assert!(store.authority_gate_heap_bytes() < dense);
for i in [0u32, 1023, 1024, 2047, 2999] {
let v = VertexId(first.0 + i);
assert_eq!(store.grid_addr(v), Some(GridAddr::new(5 + i, 4)));
assert_eq!(store.sheet_id(v), 2);
assert_eq!(store.kind(v), VertexKind::FormulaScalar);
assert_eq!(store.edge_offset(v), 2);
assert_eq!(store.value_ref(v), 0);
}
store.ensure_dense(VertexId(first.0 + 1500), 1);
assert_eq!(store.virtual_pages(), 1);
store.set_kind(VertexId(first.0 + 1500), VertexKind::Cell);
assert_eq!(store.kind(VertexId(first.0 + 1500)), VertexKind::Cell);
assert_eq!(
store.grid_addr(VertexId(first.0 + 1501)),
Some(GridAddr::new(1506, 4))
);
assert_eq!(store.edge_offset(VertexId(first.0 + 1501)), 2);
assert_eq!(store.virtualize_member_span(first, 3000, 2, 4, 5), 0);
}
#[test]
fn test_reserved_vertex_range() {
let mut store = VertexStore::new();
let id = store.allocate(grid(0, 0), 0, 0);
assert!(id.0 >= FIRST_NORMAL_VERTEX);
}
#[test]
fn test_atomic_flag_operations() {
let mut store = VertexStore::new();
let id = store.allocate(grid(0, 0), 0, 0);
store.set_dirty(id, true);
assert!(store.is_dirty(id));
store.set_dirty(id, false);
assert!(!store.is_dirty(id));
store.set_volatile(id, true);
assert!(store.is_volatile(id));
}
#[test]
fn test_vertex_store_set_addr() {
let mut store = VertexStore::new();
let id = store.allocate(grid(1, 1), 0, 0);
store.set_addr(id, grid(5, 10));
assert_eq!(store.addr(id), grid(5, 10));
}
#[test]
fn test_vertex_store_atomic_flags() {
let mut store = VertexStore::new();
let id = store.allocate(grid(0, 0), 0, 0);
store.set_dirty(id, true);
assert!(store.is_dirty(id));
store.set_volatile(id, true);
assert!(store.is_volatile(id));
store.mark_deleted(id, true);
assert!(store.is_deleted(id));
}
#[test]
fn test_reserved_id_range_preserved() {
let mut store = VertexStore::new();
let id = store.allocate(grid(0, 0), 0, 0);
assert!(id.0 >= FIRST_NORMAL_VERTEX);
store.mark_deleted(id, true);
assert!(store.vertex_exists(id));
assert!(store.is_deleted(id));
}
}
pub const FIRST_NORMAL_VERTEX: u32 = 1024;
pub(crate) const VIRTUAL_FLAG: u8 = 0x20;
pub const MAX_VERTEX_ID: u32 = crate::engine::authority::identity::HOST_SYMBOL_ID_BASE - 1;
pub const RANGE_VERTEX_START: u32 = 0;
pub const EXTERNAL_VERTEX_START: u32 = 256;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum VertexBatchAllocationError {
IdExhausted,
ReservedIdsMismatch,
}
const COLD_PAGE_BITS: usize = 10;
const COLD_PAGE: usize = 1 << COLD_PAGE_BITS;
const COLD_MASK: usize = COLD_PAGE - 1;
#[derive(Debug, Default)]
struct ColdCols {
coords: Vec<VertexAddr>, sheet_kind: Vec<u32>, value_ref: Vec<u32>, edge_offset: Vec<u32>, }
impl ColdCols {
fn with_capacity(n: usize) -> Self {
Self {
coords: Vec::with_capacity(n),
sheet_kind: Vec::with_capacity(n),
value_ref: Vec::with_capacity(n),
edge_offset: Vec::with_capacity(n),
}
}
fn heap_bytes(&self) -> usize {
self.coords.capacity() * size_of::<VertexAddr>()
+ (self.sheet_kind.capacity() + self.value_ref.capacity() + self.edge_offset.capacity())
* 4
}
fn shrink_to_fit(&mut self) {
self.coords.shrink_to_fit();
self.sheet_kind.shrink_to_fit();
self.value_ref.shrink_to_fit();
self.edge_offset.shrink_to_fit();
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct VirtualSpan {
start: u32,
len: u32,
sheet: SheetId,
col: u32,
row0: u32,
edge_offset: u32,
}
#[derive(Debug)]
enum ColdPage {
Dense(ColdCols),
Virtual(Box<[VirtualSpan]>),
}
impl ColdPage {
#[inline]
fn span(spans: &[VirtualSpan], off: usize) -> &VirtualSpan {
let off = off as u32;
spans
.iter()
.find(|s| off >= s.start && off < s.start + s.len)
.expect("a virtual page's spans cover it")
}
#[inline]
fn addr(&self, off: usize) -> VertexAddr {
match self {
ColdPage::Dense(c) => c.coords[off],
ColdPage::Virtual(spans) => {
let s = Self::span(spans, off);
VertexAddr::grid(GridAddr::new(s.row0 + (off as u32 - s.start), s.col))
}
}
}
#[inline]
fn sheet_kind(&self, off: usize) -> u32 {
match self {
ColdPage::Dense(c) => c.sheet_kind[off],
ColdPage::Virtual(spans) => {
let s = Self::span(spans, off);
((s.sheet as u32) << 16) | ((VertexKind::FormulaScalar.to_tag() as u32) << 8)
}
}
}
#[inline]
fn value_ref(&self, off: usize) -> u32 {
match self {
ColdPage::Dense(c) => c.value_ref[off],
ColdPage::Virtual(_) => 0,
}
}
#[inline]
fn edge_offset(&self, off: usize) -> u32 {
match self {
ColdPage::Dense(c) => c.edge_offset[off],
ColdPage::Virtual(spans) => Self::span(spans, off).edge_offset,
}
}
fn heap_bytes(&self) -> usize {
match self {
ColdPage::Dense(c) => c.heap_bytes(),
ColdPage::Virtual(spans) => spans.len() * size_of::<VirtualSpan>(),
}
}
}
#[repr(C, align(64))]
#[derive(Debug)]
pub struct VertexStore {
pages: Vec<ColdPage>,
flags: Vec<AtomicU8>,
len: usize,
dirty_cleared: std::sync::atomic::AtomicBool,
}
impl Default for VertexStore {
fn default() -> Self {
Self::new()
}
}
impl VertexStore {
pub fn new() -> Self {
Self {
pages: Vec::new(),
flags: Vec::new(),
len: 0,
dirty_cleared: std::sync::atomic::AtomicBool::new(false),
}
}
pub fn with_capacity(capacity: usize) -> Self {
let mut store = Self::new();
store.reserve(capacity);
store
}
pub fn reserve(&mut self, additional: usize) {
if additional == 0 {
return;
}
let target = self.len + additional;
if self.flags.capacity() < target {
self.flags.reserve(additional);
}
let page = self.len >> COLD_PAGE_BITS;
let room = (COLD_PAGE - (self.len & COLD_MASK)).min(additional);
if let Some(ColdPage::Dense(c)) = self.pages.get_mut(page) {
let want = (self.len & COLD_MASK) + room;
if c.coords.capacity() < want {
let extra = want - c.coords.len();
c.coords.reserve(extra);
c.sheet_kind.reserve(extra);
c.value_ref.reserve(extra);
c.edge_offset.reserve(extra);
}
} else if page == self.pages.len() {
self.pages
.push(ColdPage::Dense(ColdCols::with_capacity(room)));
}
}
#[inline]
fn push_row(&mut self, addr: VertexAddr, sheet: SheetId, flags: u8) {
let page = self.len >> COLD_PAGE_BITS;
if page == self.pages.len() {
let cap = if page == 0 { 0 } else { COLD_PAGE };
self.pages
.push(ColdPage::Dense(ColdCols::with_capacity(cap)));
}
let ColdPage::Dense(c) = &mut self.pages[page] else {
unreachable!("the page being filled is dense");
};
c.coords.push(addr);
c.sheet_kind.push((u32::from(sheet)) << 16);
c.value_ref.push(0);
c.edge_offset.push(0);
self.flags.push(AtomicU8::new(flags));
self.len += 1;
}
pub fn allocate(&mut self, addr: VertexAddr, sheet: SheetId, flags: u8) -> VertexId {
let id = VertexId(self.len as u32 + FIRST_NORMAL_VERTEX);
debug_assert!(id.0 >= FIRST_NORMAL_VERTEX);
assert!(
id.0 <= MAX_VERTEX_ID,
"vertex ids exhausted (formula ids share the authority's id space below its symbol range)"
);
self.push_row(addr, sheet, flags);
id
}
pub(crate) fn try_allocate_batch(
&mut self,
vertices: &[(VertexAddr, SheetId, u8)],
expected_ids: &[VertexId],
) -> Result<Vec<VertexId>, VertexBatchAllocationError> {
if vertices.len() != expected_ids.len() {
return Err(VertexBatchAllocationError::ReservedIdsMismatch);
}
let start = u32::try_from(self.len)
.map_err(|_| VertexBatchAllocationError::IdExhausted)?
.checked_add(FIRST_NORMAL_VERTEX)
.ok_or(VertexBatchAllocationError::IdExhausted)?;
let count =
u32::try_from(vertices.len()).map_err(|_| VertexBatchAllocationError::IdExhausted)?;
if count != 0 {
let last = start
.checked_add(count - 1)
.ok_or(VertexBatchAllocationError::IdExhausted)?;
if last > MAX_VERTEX_ID {
return Err(VertexBatchAllocationError::IdExhausted);
}
}
let ids: Vec<_> = (0..count).map(|offset| VertexId(start + offset)).collect();
if ids != expected_ids {
return Err(VertexBatchAllocationError::ReservedIdsMismatch);
}
self.reserve(vertices.len());
for &(addr, sheet, flags) in vertices {
self.push_row(addr, sheet, flags);
}
Ok(ids)
}
pub(crate) fn allocate_prevalidated_batch(&mut self, vertices: &[(VertexAddr, SheetId, u8)]) {
self.reserve(vertices.len());
for &(addr, sheet, flags) in vertices {
self.allocate(addr, sheet, flags);
}
}
pub fn allocate_contiguous(
&mut self,
sheet: SheetId,
addrs: &[VertexAddr],
flags: u8,
) -> Vec<VertexId> {
if addrs.is_empty() {
return Vec::new();
}
self.reserve(addrs.len());
let mut ids = Vec::with_capacity(addrs.len());
for &addr in addrs {
ids.push(self.allocate(addr, sheet, flags));
}
ids
}
pub(crate) fn shrink_to_fit(&mut self) {
self.flags.shrink_to_fit();
self.pages.shrink_to_fit();
if let Some(ColdPage::Dense(c)) = self.pages.last_mut() {
c.shrink_to_fit();
}
}
#[inline]
pub fn len(&self) -> usize {
self.len
}
#[inline]
fn vertex_id_to_index(&self, id: VertexId) -> Option<usize> {
if id.0 < FIRST_NORMAL_VERTEX {
return None;
}
let idx = (id.0 - FIRST_NORMAL_VERTEX) as usize;
if idx >= self.len {
return None;
}
Some(idx)
}
#[inline]
fn page(&self, idx: usize) -> &ColdPage {
&self.pages[idx >> COLD_PAGE_BITS]
}
#[inline]
fn dense_mut(&mut self, idx: usize) -> (&mut ColdCols, usize) {
let page = idx >> COLD_PAGE_BITS;
self.densify_page(page);
let ColdPage::Dense(c) = &mut self.pages[page] else {
unreachable!("densified");
};
(c, idx & COLD_MASK)
}
fn densify_page(&mut self, page: usize) {
let ColdPage::Virtual(spans) = &self.pages[page] else {
return;
};
let spans = spans.clone();
let mut c = ColdCols::with_capacity(COLD_PAGE);
let tmp = ColdPage::Virtual(spans);
for off in 0..COLD_PAGE {
c.coords.push(tmp.addr(off));
c.sheet_kind.push(tmp.sheet_kind(off));
c.value_ref.push(0);
c.edge_offset.push(tmp.edge_offset(off));
}
self.pages[page] = ColdPage::Dense(c);
}
pub(crate) fn ensure_dense(&mut self, first: VertexId, len: u32) {
let Some(a) = self.vertex_id_to_index(first) else {
return;
};
let b = (a + len as usize - 1).min(self.len - 1);
for page in (a >> COLD_PAGE_BITS)..=(b >> COLD_PAGE_BITS) {
self.densify_page(page);
}
}
pub(crate) fn virtualize_member_span(
&mut self,
first: VertexId,
len: u32,
sheet: SheetId,
col: u32,
row0: u32,
) -> usize {
let Some(a) = self.vertex_id_to_index(first) else {
return 0;
};
let end = a + len as usize; if end > self.len {
return 0;
}
let formula = ((sheet as u32) << 16) | ((VertexKind::FormulaScalar.to_tag() as u32) << 8);
let mut dropped = 0;
let first_page = a.div_ceil(COLD_PAGE);
let last_page_end = end >> COLD_PAGE_BITS; for page in first_page..last_page_end {
let ColdPage::Dense(c) = &self.pages[page] else {
continue;
};
if c.coords.len() != COLD_PAGE {
continue;
}
let base = page << COLD_PAGE_BITS;
let page_row0 = row0 + (base - a) as u32;
let edges = c.edge_offset[0];
let exact = (0..COLD_PAGE).all(|off| {
c.coords[off] == VertexAddr::grid(GridAddr::new(page_row0 + off as u32, col))
&& c.sheet_kind[off] == formula
&& c.value_ref[off] == 0
&& c.edge_offset[off] == edges
});
if !exact {
continue;
}
self.pages[page] = ColdPage::Virtual(Box::new([VirtualSpan {
start: 0,
len: COLD_PAGE as u32,
sheet,
col,
row0: page_row0,
edge_offset: edges,
}]));
dropped += 1;
}
dropped
}
pub(crate) fn shift_member_addrs(&mut self, first: VertexId, len: u32, dr: i64, dc: i64) {
let Some(a) = self.vertex_id_to_index(first) else {
return;
};
let end = (a + len as usize).min(self.len);
let shift = |g: GridAddr| {
GridAddr::new(
(i64::from(g.row()) + dr) as u32,
(i64::from(g.col()) + dc) as u32,
)
};
let mut idx = a;
while idx < end {
let page = idx >> COLD_PAGE_BITS;
let page_start = page << COLD_PAGE_BITS;
let hi = end.min(page_start + COLD_PAGE);
let whole = idx == page_start && hi == page_start + COLD_PAGE;
match &mut self.pages[page] {
ColdPage::Virtual(spans) if whole => {
for sp in spans.iter_mut() {
let g = shift(GridAddr::new(sp.row0, sp.col));
sp.row0 = g.row();
sp.col = g.col();
}
}
_ => {
self.densify_page(page);
let ColdPage::Dense(c) = &mut self.pages[page] else {
unreachable!("densified");
};
for off in (idx - page_start)..(hi - page_start) {
if let Some(g) = c.coords[off].as_grid() {
c.coords[off] = VertexAddr::grid(shift(g));
}
}
}
}
idx = hi;
}
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len == 0
}
#[inline]
pub fn addr(&self, id: VertexId) -> VertexAddr {
if let Some(idx) = self.vertex_id_to_index(id) {
self.page(idx).addr(idx & COLD_MASK)
} else {
VertexAddr::INVALID }
}
#[inline]
pub fn grid_addr(&self, id: VertexId) -> Option<GridAddr> {
self.addr(id).as_grid()
}
#[inline]
pub fn sheet_id(&self, id: VertexId) -> SheetId {
if let Some(idx) = self.vertex_id_to_index(id) {
(self.page(idx).sheet_kind(idx & COLD_MASK) >> 16) as SheetId
} else {
0 }
}
#[inline]
pub fn kind(&self, id: VertexId) -> VertexKind {
if let Some(idx) = self.vertex_id_to_index(id) {
let tag = ((self.page(idx).sheet_kind(idx & COLD_MASK) >> 8) & 0xFF) as u8;
VertexKind::from_tag(tag)
} else {
VertexKind::Empty }
}
#[inline]
pub fn set_kind(&mut self, id: VertexId, kind: VertexKind) {
debug_assert!(
!self.is_virtual(id),
"virtual family member {id:?} mutated without materializing"
);
if let Some(idx) = self.vertex_id_to_index(id) {
let (c, off) = self.dense_mut(idx);
let sheet_bits = c.sheet_kind[off] & 0xFFFF0000;
c.sheet_kind[off] = sheet_bits | ((kind.to_tag() as u32) << 8);
}
}
#[inline]
pub fn flags(&self, id: VertexId) -> u8 {
if let Some(idx) = self.vertex_id_to_index(id) {
self.flags[idx].load(Ordering::Acquire)
} else {
0 }
}
#[inline]
pub fn is_dirty(&self, id: VertexId) -> bool {
self.flags(id) & 0x01 != 0
}
#[inline]
pub fn is_volatile(&self, id: VertexId) -> bool {
self.flags(id) & 0x02 != 0
}
#[inline]
pub fn is_deleted(&self, id: VertexId) -> bool {
self.flags(id) & 0x04 != 0
}
#[inline]
pub fn is_dynamic(&self, id: VertexId) -> bool {
self.flags(id) & 0x08 != 0
}
#[inline]
pub(crate) fn dirty_ever_cleared(&self) -> bool {
self.dirty_cleared.load(Ordering::Relaxed)
}
pub fn set_dirty(&self, id: VertexId, dirty: bool) {
if id.0 < FIRST_NORMAL_VERTEX {
return; }
let idx = (id.0 - FIRST_NORMAL_VERTEX) as usize;
if idx >= self.flags.len() {
return; }
if dirty {
self.flags[idx].fetch_or(0x01, Ordering::Release);
} else {
let before = self.flags[idx].fetch_and(!0x01, Ordering::Release);
if before & 0x01 != 0 {
self.dirty_cleared.store(true, Ordering::Relaxed);
}
}
}
#[inline]
pub fn set_volatile(&self, id: VertexId, volatile: bool) {
if id.0 < FIRST_NORMAL_VERTEX {
return;
}
if let Some(idx) = self.vertex_id_to_index(id) {
if volatile {
self.flags[idx].fetch_or(0x02, std::sync::atomic::Ordering::Release);
} else {
self.flags[idx].fetch_and(!0x02, std::sync::atomic::Ordering::Release);
}
}
}
#[inline]
pub fn reads_range(&self, id: VertexId) -> bool {
self.flags(id) & 0x10 != 0
}
#[inline]
pub fn set_reads_range(&self, id: VertexId, on: bool) {
if id.0 < FIRST_NORMAL_VERTEX {
return;
}
if let Some(idx) = self.vertex_id_to_index(id) {
if on {
self.flags[idx].fetch_or(0x10, Ordering::Release);
} else {
self.flags[idx].fetch_and(!0x10, Ordering::Release);
}
}
}
#[inline]
pub(crate) fn is_virtual(&self, id: VertexId) -> bool {
self.flags(id) & VIRTUAL_FLAG != 0
}
#[inline]
pub(crate) fn set_virtual(&self, id: VertexId, on: bool) {
if let Some(idx) = self.vertex_id_to_index(id) {
if on {
self.flags[idx].fetch_or(VIRTUAL_FLAG, Ordering::Release);
} else {
self.flags[idx].fetch_and(!VIRTUAL_FLAG, Ordering::Release);
}
}
}
#[inline]
pub fn set_dynamic(&self, id: VertexId, dynamic: bool) {
if id.0 < FIRST_NORMAL_VERTEX {
return;
}
if let Some(idx) = self.vertex_id_to_index(id) {
if dynamic {
self.flags[idx].fetch_or(0x08, std::sync::atomic::Ordering::Release);
} else {
self.flags[idx].fetch_and(!0x08, std::sync::atomic::Ordering::Release);
}
}
}
#[inline]
pub fn value_ref(&self, id: VertexId) -> u32 {
if let Some(idx) = self.vertex_id_to_index(id) {
self.page(idx).value_ref(idx & COLD_MASK)
} else {
0 }
}
#[inline]
pub fn set_value_ref(&mut self, id: VertexId, value_ref: u32) {
if let Some(idx) = self.vertex_id_to_index(id) {
let (c, off) = self.dense_mut(idx);
c.value_ref[off] = value_ref;
}
}
#[inline]
pub fn edge_offset(&self, id: VertexId) -> u32 {
if let Some(idx) = self.vertex_id_to_index(id) {
self.page(idx).edge_offset(idx & COLD_MASK)
} else {
0 }
}
#[inline]
pub fn set_edge_offset(&mut self, id: VertexId, offset: u32) {
if let Some(idx) = self.vertex_id_to_index(id) {
if self.page(idx).edge_offset(idx & COLD_MASK) == offset {
return;
}
let (c, off) = self.dense_mut(idx);
c.edge_offset[off] = offset;
}
}
#[doc(hidden)]
pub fn set_addr(&mut self, id: VertexId, addr: VertexAddr) {
debug_assert!(
!self.is_virtual(id),
"virtual family member {id:?} mutated without materializing"
);
if let Some(idx) = self.vertex_id_to_index(id) {
let (c, off) = self.dense_mut(idx);
c.coords[off] = addr;
}
}
pub fn mark_deleted(&self, id: VertexId, deleted: bool) {
debug_assert!(
!self.is_virtual(id),
"virtual family member {id:?} mutated without materializing"
);
if let Some(idx) = self.vertex_id_to_index(id) {
if deleted {
self.flags[idx].fetch_or(0x04, Ordering::Release);
} else {
self.flags[idx].fetch_and(!0x04, Ordering::Release);
}
}
}
pub fn vertex_exists(&self, id: VertexId) -> bool {
self.vertex_id_to_index(id).is_some()
}
pub fn vertex_exists_active(&self, id: VertexId) -> bool {
self.vertex_id_to_index(id)
.map(|_| !self.is_deleted(id))
.unwrap_or(false)
}
pub fn all_vertices(&self) -> impl Iterator<Item = VertexId> + '_ {
(0..self.len).map(|i| VertexId((i as u32) + FIRST_NORMAL_VERTEX))
}
}
impl VertexStore {
pub(crate) fn authority_gate_heap_bytes(&self) -> usize {
self.pages.capacity() * size_of::<ColdPage>()
+ self.pages.iter().map(ColdPage::heap_bytes).sum::<usize>()
+ self.flags.capacity()
}
#[cfg(test)]
fn cold_rows(&self) -> usize {
self.pages
.iter()
.map(|p| match p {
ColdPage::Dense(c) => c.coords.len(),
ColdPage::Virtual(_) => COLD_PAGE,
})
.sum()
}
#[cfg(test)]
fn cold_capacity(&self) -> usize {
self.pages
.iter()
.map(|p| match p {
ColdPage::Dense(c) => c.coords.capacity(),
ColdPage::Virtual(_) => COLD_PAGE,
})
.sum()
}
pub(crate) fn virtual_pages(&self) -> usize {
self.pages
.iter()
.filter(|p| matches!(p, ColdPage::Virtual(_)))
.count()
}
}