use std::cell::{Cell, RefCell};
use energyml_domain::package_index::{IndexBuilder, PackageIndex, Progress};
use energyml_storage::EpcStore;
use energyml_storage::epc::revision::Changed;
#[derive(Default)]
pub struct IndexSlot {
index: RefCell<Option<PackageIndex>>,
builder: RefCell<Option<IndexBuilder>>,
partial: Cell<bool>,
disturbed: [RefCell<Disturbed>; 2],
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Consumer {
Colours,
Attachments,
}
const DISTURBED_CAP: usize = 50_000;
#[derive(Default, Clone, Debug, PartialEq, Eq)]
pub struct Disturbed {
pub everything: bool,
pub uuids: Vec<String>,
}
enum Plan {
Keep,
Build,
Update(Vec<String>),
}
impl IndexSlot {
pub fn set_partial(&self, partial: bool) {
self.partial.set(partial);
if partial
&& let Ok(mut slot) = self.index.try_borrow_mut()
&& let Some(index) = slot.as_mut()
{
index.mark_incomplete();
}
}
pub fn forget(&self) {
if let Ok(mut slot) = self.index.try_borrow_mut() {
*slot = None;
}
self.note(|dirty| dirty.everything = true);
if let Ok(mut building) = self.builder.try_borrow_mut() {
*building = None;
}
}
pub fn revision(&self) -> Option<u64> {
self.index.try_borrow().ok()?.as_ref().map(PackageIndex::revision)
}
pub fn node_count(&self) -> Option<usize> {
self.index.try_borrow().ok()?.as_ref().map(PackageIndex::len)
}
pub fn is_complete(&self) -> Option<bool> {
self.index
.try_borrow()
.ok()?
.as_ref()
.map(PackageIndex::is_complete)
}
pub fn take_disturbed(&self, consumer: Consumer) -> Disturbed {
std::mem::take(&mut *self.disturbed[consumer as usize].borrow_mut())
}
fn note(&self, say: impl Fn(&mut Disturbed)) {
for account in &self.disturbed {
let mut account = account.borrow_mut();
say(&mut account);
if account.uuids.len() > DISTURBED_CAP {
account.uuids.sort_unstable();
account.uuids.dedup();
if account.uuids.len() > DISTURBED_CAP {
account.uuids.clear();
account.everything = true;
}
}
}
}
pub fn with<R>(&self, store: &EpcStore, f: impl FnOnce(&PackageIndex) -> R) -> R {
self.settle(store);
match self.index.try_borrow() {
Ok(slot) => match slot.as_ref() {
Some(index) => f(index),
None => f(&PackageIndex::build(store, store.revision())),
},
Err(_) => f(&PackageIndex::build(store, store.revision())),
}
}
pub fn step(&self, store: &EpcStore, n: usize) -> Progress {
let Ok(mut building) = self.builder.try_borrow_mut() else {
return Progress { done: 0, total: 0 };
};
if building.is_none() {
let begin = match self.index.try_borrow() {
Ok(slot) => match slot.as_ref() {
None => true,
Some(index) => matches!(store.changed_since(index.revision()), Changed::Everything),
},
Err(_) => false,
};
if begin {
*building = Some(IndexBuilder::new(store.resources(), store.revision()));
} else {
drop(building);
self.settle(store);
let held = self
.index
.try_borrow()
.ok()
.and_then(|slot| slot.as_ref().map(PackageIndex::len))
.unwrap_or(0);
return Progress {
done: held,
total: held,
};
}
}
let Some(builder) = building.as_mut() else {
return Progress { done: 0, total: 0 };
};
let progress = builder.step(store, n);
if progress.is_done()
&& let Some(done) = building.take()
{
self.install(done.finish());
}
progress
}
#[doc(hidden)]
pub fn tamper<R>(&self, store: &EpcStore, f: impl FnOnce(&mut PackageIndex) -> R) -> Option<R> {
self.settle(store);
let mut slot = self.index.try_borrow_mut().ok()?;
slot.as_mut().map(f)
}
fn install(&self, mut index: PackageIndex) {
self.note(|dirty| dirty.everything = true);
if self.partial.get() {
index.mark_incomplete();
}
if let Ok(mut slot) = self.index.try_borrow_mut() {
*slot = Some(index);
}
}
fn settle(&self, store: &EpcStore) {
let unfinished = match self.builder.try_borrow_mut() {
Ok(mut building) => building.take(),
Err(_) => None,
};
if let Some(mut builder) = unfinished {
let total = builder.total();
builder.step(store, total);
self.install(builder.finish());
}
let plan = match self.index.try_borrow() {
Ok(slot) => match slot.as_ref() {
None => Plan::Build,
Some(index) => match store.changed_since(index.revision()) {
Changed::Nothing => Plan::Keep,
Changed::Some(uuids) => Plan::Update(uuids.into_iter().collect()),
Changed::Everything => Plan::Build,
},
},
Err(_) => Plan::Keep,
};
match plan {
Plan::Keep => {}
Plan::Build => self.install(PackageIndex::build(store, store.revision())),
Plan::Update(uuids) => {
if let Ok(mut slot) = self.index.try_borrow_mut()
&& let Some(index) = slot.as_mut()
{
let disturbed = index.update(store, &uuids, store.revision());
self.note(|dirty| dirty.uuids.extend(disturbed.iter().cloned()));
}
}
}
}
}