use super::Component;
use core::any::TypeId;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AccessMode {
Read,
Write,
}
#[derive(Debug, Clone, Default)]
pub struct SystemAccess {
entries: Vec<(TypeId, AccessMode)>,
}
impl SystemAccess {
#[must_use]
pub const fn new() -> Self {
Self {
entries: Vec::new(),
}
}
pub fn read<T: Component>(&mut self) -> &mut Self {
self.declare::<T>(AccessMode::Read)
}
pub fn write<T: Component>(&mut self) -> &mut Self {
self.declare::<T>(AccessMode::Write)
}
fn declare<T: Component>(&mut self, mode: AccessMode) -> &mut Self {
let type_id = TypeId::of::<T>();
if let Some((_, current)) = self.entries.iter_mut().find(|(id, _)| *id == type_id) {
if mode == AccessMode::Write {
*current = AccessMode::Write;
}
} else {
self.entries.push((type_id, mode));
}
self
}
#[must_use]
pub fn mode<T: Component>(&self) -> Option<AccessMode> {
self.entries
.iter()
.find_map(|(id, mode)| (*id == TypeId::of::<T>()).then_some(*mode))
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
#[must_use]
pub fn conflicts_with(&self, other: &Self) -> bool {
self.entries.iter().any(|(type_id, mode)| {
let Some(other_mode) = other
.entries
.iter()
.find_map(|(other_id, other_mode)| (other_id == type_id).then_some(other_mode))
else {
return false;
};
*mode == AccessMode::Write || *other_mode == AccessMode::Write
})
}
}
#[cfg(test)]
mod tests {
use super::{AccessMode, SystemAccess};
use std::cell::Cell;
use std::rc::Rc;
struct Position;
struct Velocity;
#[test]
fn read_read_of_same_type_is_compatible() {
let mut first = SystemAccess::new();
first.read::<Position>();
let mut second = SystemAccess::new();
second.read::<Position>();
assert!(!first.conflicts_with(&second));
}
#[test]
fn read_write_and_write_write_of_same_type_conflict() {
let mut reader = SystemAccess::new();
reader.read::<Position>();
let mut writer = SystemAccess::new();
writer.write::<Position>();
assert!(reader.conflicts_with(&writer));
assert!(writer.conflicts_with(&reader));
assert!(writer.conflicts_with(&writer));
}
#[test]
fn distinct_types_do_not_conflict() {
let mut position = SystemAccess::new();
position.write::<Position>();
let mut velocity = SystemAccess::new();
velocity.write::<Velocity>();
assert!(!position.conflicts_with(&velocity));
}
#[test]
fn repeated_declarations_are_deduplicated_and_write_wins() {
let mut access = SystemAccess::new();
access.read::<Position>().read::<Position>();
assert_eq!(access.mode::<Position>(), Some(AccessMode::Read));
access.write::<Position>().read::<Position>();
assert_eq!(access.mode::<Position>(), Some(AccessMode::Write));
assert!(access.conflicts_with(&{
let mut reader = SystemAccess::new();
reader.read::<Position>();
reader
}));
}
#[test]
fn empty_access_sets_are_compatible_and_non_send_components_are_describable() {
let empty = SystemAccess::new();
assert!(empty.is_empty());
assert!(!empty.conflicts_with(&empty));
let mut local_only = SystemAccess::new();
local_only.write::<Rc<Cell<u32>>>();
assert_eq!(local_only.mode::<Rc<Cell<u32>>>(), Some(AccessMode::Write));
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct SystemPlan {
batches: Vec<Vec<usize>>,
}
impl SystemPlan {
#[must_use]
pub fn from_accesses(accesses: &[SystemAccess]) -> Self {
let mut batches: Vec<Vec<usize>> = Vec::new();
let mut batch_accesses: Vec<Vec<usize>> = Vec::new();
for (system_index, access) in accesses.iter().enumerate() {
let can_append = batch_accesses.last().is_some_and(|indices| {
indices
.iter()
.all(|&prior| !access.conflicts_with(&accesses[prior]))
});
if can_append {
let last = batches.len() - 1;
batches[last].push(system_index);
batch_accesses[last].push(system_index);
} else {
batches.push(vec![system_index]);
batch_accesses.push(vec![system_index]);
}
}
Self { batches }
}
#[must_use]
pub fn batches(&self) -> &[Vec<usize>] {
&self.batches
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.batches.is_empty()
}
#[must_use]
pub fn len(&self) -> usize {
self.batches.len()
}
}
#[cfg(test)]
mod plan_tests {
use super::{SystemAccess, SystemPlan};
struct Position;
struct Velocity;
#[test]
fn empty_input_produces_no_batches() {
let plan = SystemPlan::from_accesses(&[]);
assert!(plan.is_empty());
assert_eq!(plan.len(), 0);
assert!(plan.batches().is_empty());
}
#[test]
fn compatible_reads_and_distinct_writes_share_a_batch() {
let mut first = SystemAccess::new();
first.read::<Position>();
let mut second = SystemAccess::new();
second.read::<Position>().write::<Velocity>();
let mut third = SystemAccess::new();
third.write::<Position>();
let plan = SystemPlan::from_accesses(&[first, second, third]);
assert_eq!(plan.batches(), &[vec![0, 1], vec![2]]);
}
#[test]
fn conflicts_keep_input_order_while_compatible_systems_share_batches() {
let mut first = SystemAccess::new();
first.write::<Position>();
let mut independent = SystemAccess::new();
independent.write::<Velocity>();
let mut third = SystemAccess::new();
third.read::<Position>();
let mut fourth = SystemAccess::new();
fourth.write::<Velocity>();
let plan = SystemPlan::from_accesses(&[first, independent, third, fourth]);
assert_eq!(plan.batches(), &[vec![0, 1], vec![2, 3]]);
}
#[test]
fn planning_is_repeatable_and_each_system_appears_once() {
let mut first = SystemAccess::new();
first.read::<Position>();
let mut second = SystemAccess::new();
second.write::<Position>();
let mut third = SystemAccess::new();
third.read::<Velocity>();
let accesses = [first, second, third];
let expected = SystemPlan::from_accesses(&accesses);
for _ in 0..32 {
assert_eq!(SystemPlan::from_accesses(&accesses), expected);
}
let indices: Vec<_> = expected.batches().iter().flatten().copied().collect();
assert_eq!(indices, vec![0, 1, 2]);
}
}