use std::ops::Range;
use std::sync::Arc;
use crate::diagnostic::Diagnostic;
use crate::entity::Entity;
use crate::header::Header;
use crate::lazy::{EntitySource, Slot};
use crate::value::EntityId;
use ahash::AHashMap;
#[derive(Debug, Clone, Default)]
pub struct Model {
header: Header,
entities: AHashMap<EntityId, Slot>,
order: Vec<EntityId>,
by_type: AHashMap<String, Vec<EntityId>>,
max_id: u64,
revision: u64,
diagnostics: Vec<Diagnostic>,
source: Option<Arc<dyn EntitySource>>,
}
impl Model {
pub fn new() -> Self {
Self::default()
}
pub fn with_source(source: Arc<dyn EntitySource>) -> Self {
Self {
source: Some(source),
..Self::default()
}
}
pub fn header(&self) -> &Header {
&self.header
}
pub fn header_mut(&mut self) -> &mut Header {
&mut self.header
}
pub fn diagnostics(&self) -> &[Diagnostic] {
&self.diagnostics
}
pub fn is_complete(&self) -> bool {
self.diagnostics.is_empty()
}
pub fn push_diagnostic(&mut self, diagnostic: Diagnostic) {
self.diagnostics.push(diagnostic);
}
pub fn insert(&mut self, id: EntityId, entity: Entity) {
#[cfg(feature = "authored-dump")]
crate::authored_dump::record(&entity.type_name, "insert");
let type_name = std::sync::Arc::clone(&entity.type_name);
self.place(id, &type_name, Slot::decoded(entity));
}
pub fn insert_lazy(&mut self, id: EntityId, type_name: &str, span: Range<usize>) {
assert!(
self.source.is_some(),
"insert_lazy needs a model built with Model::with_source"
);
#[cfg(feature = "authored-dump")]
crate::authored_dump::record(type_name, "insert");
self.place(id, type_name, Slot::lazy(span));
}
pub fn reserve(&mut self, additional: usize) {
self.entities.reserve(additional);
self.order.reserve(additional);
}
fn place(&mut self, id: EntityId, type_name: &str, slot: Slot) {
let upper;
let key = if type_name.bytes().any(|byte| byte.is_ascii_lowercase()) {
upper = type_name.to_ascii_uppercase();
upper.as_str()
} else {
type_name
};
match self.entities.insert(id, slot) {
None => self.order.push(id),
Some(previous) => {
let previous = previous.into_entity(self.source.as_deref());
let previous_key = previous.type_name.to_ascii_uppercase();
if previous_key != key {
if let Some(ids) = self.by_type.get_mut(&previous_key) {
ids.retain(|existing| *existing != id);
}
} else {
self.max_id = self.max_id.max(id.0);
self.revision += 1;
return;
}
}
}
match self.by_type.get_mut(key) {
Some(ids) => ids.push(id),
None => {
self.by_type.insert(key.to_owned(), vec![id]);
}
}
self.max_id = self.max_id.max(id.0);
self.revision += 1;
}
pub fn push(&mut self, entity: Entity) -> EntityId {
let id = EntityId(self.max_id + 1);
self.insert(id, entity);
id
}
pub fn revision(&self) -> u64 {
self.revision
}
pub(crate) fn bump_revision(&mut self) {
self.revision += 1;
}
pub fn next_id(&self) -> EntityId {
EntityId(self.max_id + 1)
}
pub fn get(&self, id: EntityId) -> Option<&Entity> {
self.entities
.get(&id)
.map(|slot| slot.get(self.source.as_deref()))
}
pub fn contains(&self, id: EntityId) -> bool {
self.entities.contains_key(&id)
}
pub fn decoded_len(&self) -> usize {
self.entities
.values()
.filter(|slot| slot.is_decoded())
.count()
}
pub fn decode_all(&self, threads: usize) {
let pending: Vec<&Slot> = self
.entities
.values()
.filter(|slot| !slot.is_decoded())
.collect();
if pending.is_empty() {
return;
}
let source = self.source.as_deref();
let threads = threads.clamp(1, pending.len());
if threads == 1 {
for slot in pending {
slot.get(source);
}
return;
}
let chunk = pending.len().div_ceil(threads);
std::thread::scope(|scope| {
for part in pending.chunks(chunk) {
scope.spawn(move || {
for slot in part {
slot.get(source);
}
});
}
});
}
pub fn len(&self) -> usize {
self.entities.len()
}
pub fn is_empty(&self) -> bool {
self.entities.is_empty()
}
pub fn ids(&self) -> impl Iterator<Item = EntityId> + '_ {
self.order.iter().copied()
}
pub fn iter(&self) -> impl Iterator<Item = (EntityId, &Entity)> + '_ {
self.order
.iter()
.filter_map(move |id| self.get(*id).map(|e| (*id, e)))
}
pub fn ids_of_type(&self, type_name: &str) -> &[EntityId] {
self.by_type
.get(&type_name.to_ascii_uppercase())
.map(|v| v.as_slice())
.unwrap_or(&[])
}
pub fn of_type<'a>(&'a self, type_name: &str) -> impl Iterator<Item = (EntityId, &'a Entity)> {
self.ids_of_type(type_name)
.iter()
.filter_map(move |id| self.get(*id).map(|e| (*id, e)))
.collect::<Vec<_>>()
.into_iter()
}
pub fn type_histogram(&self) -> Vec<(&str, usize)> {
let mut v: Vec<_> = self
.by_type
.iter()
.map(|(k, ids)| (k.as_str(), ids.len()))
.collect();
v.sort_unstable_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(b.0)));
v
}
pub fn dangling_references(&self) -> Vec<(EntityId, EntityId)> {
let mut out = Vec::new();
for (id, entity) in self.iter() {
for target in entity.references() {
if !self.entities.contains_key(&target) {
out.push((id, target));
}
}
}
out
}
pub(crate) fn entity_mut(&mut self, id: EntityId) -> Option<&mut Entity> {
let source = self.source.clone();
Some(self.entities.get_mut(&id)?.get_mut(source.as_deref()))
}
pub(crate) fn take_entity(&mut self, id: EntityId) -> Option<Entity> {
let slot = self.entities.remove(&id)?;
Some(slot.into_entity(self.source.as_deref()))
}
pub(crate) fn by_type_mut(&mut self) -> &mut AHashMap<String, Vec<EntityId>> {
&mut self.by_type
}
pub(crate) fn order_mut(&mut self) -> &mut Vec<EntityId> {
&mut self.order
}
}