//! Handling store mutation use super::*; use std::borrow::Borrow; impl Store { /// Insert this entry into the data table, overwriting any identically hashed one and returning it. pub fn insert_overwrite(&mut self, ent: Entry) -> Option { let old = self.remove(ent.hash()); let hash_idx = self.data_hashes.insert(*ent.hash()); for tag in ent.tags.iter() { self.insert_tag_for_idx(tag, hash_idx); } self.data.insert(ent); old } /// Insert this entry then return a reference to it. pub fn insert<'a, 'b>(&'a mut self, ent: Entry) -> &'b Entry where 'a: 'b { let ffd = *ent.hash(); self.insert_overwrite(ent); self.data.get(&ffd).unwrap() } /// Mutate this entry in place if it exists. /// /// See [`map_entry`]. pub fn mutate_entry_in_place(&mut self, ent_id: &EntryKey, f: F) -> Option where F: FnOnce(&mut Entry) -> T { if let Some(mut ent) = self.data.take(ent_id) { let update = ent.prepare_for_refresh(); let out = f(&mut ent); let new = ent; self.refresh_for_entry(update, &new); self.data.insert(new); Some(out) } else { None } } /// Update an entry that may have been modified. /// /// The entries old hash and tags are passed, and it updates the store to reflect the new entry mutation. /// You must still insert the new entry after this. fn refresh_for_entry(&mut self, (ohash, otags): (impl Borrow, impl AsRef<[String]>), new: &Entry) { let ohash = ohash.borrow(); let iidx = if new.hash() != ohash { // We need to update `data_hashes`. for (_, hash) in self.data_hashes.iter_mut() { if hash == ohash { *hash = *new.hash(); break; } } self.reverse_index_lookup(new.hash()).unwrap() } else { self.reverse_index_lookup(ohash).unwrap() }; let otags =otags.as_ref(); if &new.tags[..] != &otags[..] { // We need to update tag mappings let ntags: HashSet<_> = new.tags.iter().collect(); let otags: HashSet<_> = otags.iter().collect(); // Find the ones that were removed and added in parallel. for (t, u) in ntags.iter().zip(otags.iter()) { if !otags.contains(t) { // It was added self.insert_tag_for_idx(t, iidx); } if !ntags.contains(u) { // It was removed self.remove_tag_for_idx(t, iidx); } } } } /// Map the entry with this function, updating references to it if needed. /// /// If the hash of the entry if modified by this map, then the hashes indecies are updated to the new hash. pub fn map_entry(&mut self, ent_id: &EntryKey, f: F) where F: FnOnce(Entry) -> Entry { if let Some(ent) = self.data.take(ent_id) { let update = ent.prepare_for_refresh(); let new = f(ent); self.refresh_for_entry(update, &new); self.data.insert(new); } } /// Remove this entry, and return it, if it was set. pub fn remove(&mut self, key: &EntryKey) -> Option { if let Some(entry) = self.data.take(key) { Some(self.cleanup_remove_entry(entry.with_no_cache())) } else { None } } /// Preform cleanup on an entry *already removed* from `data`. fn cleanup_remove_entry(&mut self, ent: Entry) -> Entry { // Remove any unused tags if let Some(hash_idx) = self.reverse_index_lookup(ent.hash()) { for tag in ent.tags.iter() { self.remove_tag_for_idx(tag, hash_idx); } } // Remove from data hashes can be deferred self.purge_if_needed(); ent } /// Remove dead mappings from `data_hashes` to `data`. #[inline] fn purge_data_hash_mappings(&mut self) { let data = &self.data; self.data_hashes.retain(move |_, hash| data.get(hash).is_some()); } /// Purge the arena mapping if threshold of dead entries is reached, otherwise defer it. #[inline] fn purge_if_needed(&mut self) { if self.purge_track.should_purge() { self.purge_data_hash_mappings(); self.purge_track.num_removals = 0; } else { self.purge_track.num_removals += 1; } } } // Tag specific stuff impl Store { /// Remove a mapping for this tag string to this specific hash index, cleaning up the tag mappings if needed. #[inline] fn remove_tag_for_idx(&mut self, tag: impl AsRef, hash_idx: ArenaIndex) { let tag = tag.as_ref(); if let Some(&ti) = self.tags.get(tag) { match self.tag_mappings.get_mut(ti).map(|x| {x.remove(&hash_idx); x.len()}) { Some(0) => no_op!(self.tag_mappings.remove(ti)), // there is only 1 mapping, remove it and then remove the tag (TODO: Should we keep the tag in the btree as cache? TODO: Add this to `PurgeTrack`) None => (), // there is no mapping, just remove the tag _ => return, //don't remove the tag, there's other references in the mapping } self.tags.remove(tag); } } /// Insert a mapping for this tag string to this single hash index, creating it if needed #[inline] fn insert_tag_for_idx(&mut self, tag: impl AsRef, hash_idx: ArenaIndex) { let tag = tag.as_ref(); if let Some(&ti) = self.tags.get(tag) { // This tag has an entry already, append to it self.tag_mappings.get_mut(ti).unwrap().insert(hash_idx); } else { // This tag has no entry, create it let ti = self.tag_mappings.insert(iter![hash_idx].collect()); self.tags.insert(tag.to_owned(), ti); } } }