use std::any::{TypeId, type_name};
use std::fmt;
use pragma_core::graph::TaskConfig;
use crate::event::EventType;
use crate::lend::{Columns, Lender};
use crate::{Component, EcsError, Event, Resource};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum AccessKind {
Read,
Write,
}
#[derive(Clone, Copy)]
struct Entry {
ty: TypeId,
name: &'static str,
kind: AccessKind,
declare: fn(TaskConfig, AccessKind) -> TaskConfig,
lender: Option<Lender>,
}
impl PartialEq for Entry {
fn eq(&self, other: &Self) -> bool {
(self.ty, self.kind) == (other.ty, other.kind)
}
}
impl Eq for Entry {}
#[derive(Clone, Default, PartialEq, Eq)]
pub struct Access {
components: Vec<Entry>,
resources: Vec<Entry>,
events: Vec<EventType>,
}
impl fmt::Debug for Access {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut map = f.debug_map();
map.entries(self.components.iter().map(|e| (e.name, e.kind)));
if !self.resources.is_empty() {
let resources = self.resources.iter().map(|e| (e.name, e.kind));
map.entry(&"resources", &Resources(resources.collect()));
}
map.finish()
}
}
struct Resources(Vec<(&'static str, AccessKind)>);
impl fmt::Debug for Resources {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_map().entries(self.0.iter().copied()).finish()
}
}
impl Access {
#[must_use]
pub const fn new() -> Self {
Self {
components: Vec::new(),
resources: Vec::new(),
events: Vec::new(),
}
}
#[must_use]
pub fn len(&self) -> usize {
self.components.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.components.is_empty()
}
#[must_use]
pub fn kind(&self, ty: TypeId) -> Option<AccessKind> {
find(&self.components, ty).map(|e| e.kind)
}
#[must_use]
pub fn kind_of<T: Component>(&self) -> Option<AccessKind> {
self.kind(TypeId::of::<T>())
}
pub fn iter(&self) -> impl Iterator<Item = (TypeId, AccessKind)> + '_ {
self.components.iter().map(|e| (e.ty, e.kind))
}
#[must_use]
pub fn resource_kind_of<T: Resource>(&self) -> Option<AccessKind> {
find(&self.resources, TypeId::of::<T>()).map(|e| e.kind)
}
pub fn resources(&self) -> impl Iterator<Item = (TypeId, AccessKind)> + '_ {
self.resources.iter().map(|e| (e.ty, e.kind))
}
#[must_use]
pub fn conflict(&self, other: &Self) -> Option<&'static str> {
conflict(&self.components, &other.components)
.or_else(|| conflict(&self.resources, &other.resources))
}
pub(crate) fn add_component<T: Component>(&mut self, kind: AccessKind) -> Result<(), EcsError> {
let entry = Entry {
ty: TypeId::of::<T>(),
name: type_name::<T>(),
kind,
declare: declare::<Columns<T>>,
lender: Some(Lender::of::<T>()),
};
match add(&mut self.components, entry)? {
Some(component) => Err(EcsError::ConflictingAccess { component }),
None => Ok(()),
}
}
pub(crate) fn add_filter_read<T: Component>(&mut self) -> Result<(), EcsError> {
if find(&self.components, TypeId::of::<T>()).is_some() {
return Ok(());
}
self.add_component::<T>(AccessKind::Read)
}
pub(crate) fn merge(&mut self, other: &Self) -> Result<(), EcsError> {
for entry in &other.components {
if let Some(component) = add(&mut self.components, *entry)? {
return Err(EcsError::ConflictingAccess { component });
}
}
for entry in &other.resources {
if let Some(resource) = add(&mut self.resources, *entry)? {
return Err(EcsError::ConflictingResourceAccess { resource });
}
}
for event in &other.events {
self.add_event_entry(*event)?;
}
Ok(())
}
pub(crate) fn add_event<T: Event>(&mut self) -> Result<(), EcsError> {
self.add_event_entry(EventType::of::<T>())
}
fn add_event_entry(&mut self, event: EventType) -> Result<(), EcsError> {
if let Err(i) = self.events.binary_search_by(|e| e.ty.cmp(&event.ty)) {
self.events
.try_reserve(1)
.map_err(|_| EcsError::OutOfMemory { entities: 0 })?;
self.events.insert(i, event);
}
Ok(())
}
pub(crate) fn events(&self) -> impl Iterator<Item = EventType> + '_ {
self.events.iter().copied()
}
pub(crate) fn add_resource<T: Resource>(&mut self, kind: AccessKind) -> Result<(), EcsError> {
let entry = Entry {
ty: TypeId::of::<T>(),
name: type_name::<T>(),
kind,
declare: declare::<T>,
lender: None,
};
match add(&mut self.resources, entry)? {
Some(resource) => Err(EcsError::ConflictingResourceAccess { resource }),
None => Ok(()),
}
}
pub(crate) fn configure(&self, task: TaskConfig) -> TaskConfig {
self.components
.iter()
.chain(&self.resources)
.fold(task, |task, e| (e.declare)(task, e.kind))
}
pub(crate) fn lenders(&self) -> impl Iterator<Item = Lender> + '_ {
self.components.iter().filter_map(|e| e.lender)
}
}
fn find(entries: &[Entry], ty: TypeId) -> Option<&Entry> {
let i = entries.binary_search_by(|e| e.ty.cmp(&ty)).ok()?;
entries.get(i)
}
fn conflict(ours: &[Entry], theirs: &[Entry]) -> Option<&'static str> {
ours.iter().find_map(|e| {
let theirs = find(theirs, e.ty)?.kind;
(e.kind == AccessKind::Write || theirs == AccessKind::Write).then_some(e.name)
})
}
fn add(entries: &mut Vec<Entry>, entry: Entry) -> Result<Option<&'static str>, EcsError> {
match entries.binary_search_by(|e| e.ty.cmp(&entry.ty)) {
Ok(i) => match entries.get(i) {
Some(e) if e.kind == AccessKind::Read && entry.kind == AccessKind::Read => Ok(None),
_ => Ok(Some(entry.name)),
},
Err(i) => {
entries
.try_reserve(1)
.map_err(|_| EcsError::OutOfMemory { entities: 0 })?;
entries.insert(i, entry);
Ok(None)
}
}
}
fn declare<R: Resource>(task: TaskConfig, kind: AccessKind) -> TaskConfig {
match kind {
AccessKind::Read => task.reads::<R>(),
AccessKind::Write => task.writes::<R>(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reads_merge_and_writes_conflict() {
let mut access = Access::new();
access.add_component::<u8>(AccessKind::Read).unwrap();
access.add_component::<u8>(AccessKind::Read).unwrap();
access.add_component::<u16>(AccessKind::Write).unwrap();
assert_eq!(access.len(), 2);
assert_eq!(
access.add_component::<u8>(AccessKind::Write),
Err(EcsError::ConflictingAccess { component: "u8" })
);
assert_eq!(
access.add_component::<u16>(AccessKind::Read),
Err(EcsError::ConflictingAccess { component: "u16" })
);
assert_eq!(access.kind_of::<u8>(), Some(AccessKind::Read));
assert_eq!(access.kind_of::<u32>(), None);
assert_eq!(
format!("{access:?}").len(),
"{\"u8\": Read, \"u16\": Write}".len()
);
}
#[test]
fn components_and_resources_are_apart() {
let mut access = Access::new();
access.add_component::<u8>(AccessKind::Write).unwrap();
access.add_resource::<u8>(AccessKind::Read).unwrap();
access.add_resource::<u8>(AccessKind::Read).unwrap();
assert_eq!(access.kind_of::<u8>(), Some(AccessKind::Write));
assert_eq!(access.resource_kind_of::<u8>(), Some(AccessKind::Read));
assert_eq!(
access.add_resource::<u8>(AccessKind::Write),
Err(EcsError::ConflictingResourceAccess { resource: "u8" })
);
assert_eq!(access.lenders().count(), 1);
assert!(format!("{access:?}").contains("\"resources\": {\"u8\": Read}"));
let mut writer = Access::new();
writer.add_resource::<u8>(AccessKind::Write).unwrap();
assert_eq!(access.conflict(&writer), Some("u8"));
let mut reader = Access::new();
reader.add_component::<u16>(AccessKind::Read).unwrap();
assert_eq!(access.conflict(&reader), None);
}
#[test]
fn conflict_needs_a_write_on_either_side() {
let mut reads = Access::new();
reads.add_component::<u8>(AccessKind::Read).unwrap();
let mut other_reads = Access::new();
other_reads.add_component::<u8>(AccessKind::Read).unwrap();
let mut writes = Access::new();
writes.add_component::<u8>(AccessKind::Write).unwrap();
assert_eq!(reads.conflict(&other_reads), None);
assert_eq!(reads.conflict(&writes), Some("u8"));
assert_eq!(writes.conflict(&reads), Some("u8"));
assert_eq!(writes.conflict(&Access::new()), None);
}
}