use crate::engine::component::Signature;
use crate::engine::error::{ECSError, ECSResult, ExecutionError, InvalidAccessReason};
use crate::engine::manager::ECSReference;
#[cfg(feature = "gpu")]
use crate::engine::types::GPUResourceID;
use crate::engine::types::{ChannelID, ComponentID, SystemID};
use smallvec::SmallVec;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ChannelSet {
bits: SmallVec<[u64; 2]>,
}
impl ChannelSet {
#[inline]
pub fn new() -> Self {
Self::default()
}
#[inline]
pub fn insert(&mut self, id: ChannelID) {
let word = (id as usize) / 64;
let bit = (id as usize) % 64;
if self.bits.len() <= word {
self.bits.resize(word + 1, 0);
}
self.bits[word] |= 1u64 << bit;
}
#[inline]
pub fn contains(&self, id: ChannelID) -> bool {
let word = (id as usize) / 64;
let bit = (id as usize) % 64;
self.bits
.get(word)
.is_some_and(|w| (w & (1u64 << bit)) != 0)
}
#[inline]
pub fn is_empty(&self) -> bool {
self.bits.iter().all(|&w| w == 0)
}
#[inline]
pub fn intersects(&self, other: &ChannelSet) -> bool {
let n = self.bits.len().min(other.bits.len());
for i in 0..n {
if (self.bits[i] & other.bits[i]) != 0 {
return true;
}
}
false
}
#[inline]
pub fn or_in_place(&mut self, other: &ChannelSet) {
if other.bits.len() > self.bits.len() {
self.bits.resize(other.bits.len(), 0);
}
for (a, &b) in self.bits.iter_mut().zip(other.bits.iter()) {
*a |= b;
}
}
pub fn iter(&self) -> impl Iterator<Item = ChannelID> + '_ {
self.bits.iter().enumerate().flat_map(|(w, &word)| {
debug_assert!(
w <= (u32::MAX as usize) / 64,
"ChannelSet word index exceeds u32 channel-id space"
);
let base = (w as u32).saturating_mul(64);
(0u32..64).filter_map(move |b| {
if (word & (1u64 << b)) != 0 {
Some(base + b)
} else {
None
}
})
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ChannelOrder {
SelfBeforeOther,
OtherBeforeSelf,
}
#[derive(Clone, Debug, Default)]
pub struct AccessSets {
pub read: Signature,
pub write: Signature,
pub produces: ChannelSet,
pub consumes: ChannelSet,
}
impl AccessSets {
#[inline]
pub fn conflicts_with(&self, other: &AccessSets) -> bool {
if self.component_conflict(other) {
return true;
}
if self.produces.intersects(&other.consumes) {
return true;
}
if other.produces.intersects(&self.consumes) {
return true;
}
false
}
#[inline]
pub(crate) fn component_conflict(&self, other: &AccessSets) -> bool {
for ((a_w, a_r), (b_w, b_r)) in self
.write
.components
.iter()
.zip(self.read.components.iter())
.zip(
other
.write
.components
.iter()
.zip(other.read.components.iter()),
)
{
if (a_w & b_w) != 0 {
return true;
} if (a_w & b_r) != 0 {
return true;
} if (a_r & b_w) != 0 {
return true;
} }
false
}
pub fn channel_ordering(&self, other: &AccessSets) -> Option<ChannelOrder> {
if self.produces.intersects(&other.consumes) {
return Some(ChannelOrder::SelfBeforeOther);
}
if other.produces.intersects(&self.consumes) {
return Some(ChannelOrder::OtherBeforeSelf);
}
None
}
pub fn validate(&self) -> ECSResult<()> {
for (i, (rw, ww)) in self
.read
.components
.iter()
.zip(self.write.components.iter())
.enumerate()
{
let overlap = rw & ww;
if overlap != 0 {
let bit = overlap.trailing_zeros();
let cid: ComponentID = ((i as u32) * 64 + bit) as ComponentID;
return Err(ECSError::Execute(ExecutionError::InvalidQueryAccess {
component_id: cid,
reason: InvalidAccessReason::ReadAndWrite,
}));
}
}
if self.produces.intersects(&self.consumes) {
let offender = self
.produces
.iter()
.find(|ch| self.consumes.contains(*ch))
.unwrap_or(0);
return Err(ECSError::Execute(ExecutionError::SelfChannelAlias {
channel_id: offender,
}));
}
Ok(())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SystemBackend {
CPU,
GPU,
}
#[cfg(feature = "gpu")]
pub trait GpuSystem {
fn shader(&self) -> &'static str;
fn entry_point(&self) -> &'static str {
"main"
}
fn workgroup_size(&self) -> u32 {
256
}
fn uses_resources(&self) -> &[GPUResourceID] {
&[]
}
fn writes_resources(&self) -> &[GPUResourceID] {
&[]
}
}
pub trait System: Send + Sync {
#[inline]
fn name(&self) -> &str {
std::any::type_name_of_val(self)
}
fn id(&self) -> SystemID;
fn access(&self) -> &AccessSets;
#[inline]
fn backend(&self) -> SystemBackend {
SystemBackend::CPU
}
fn run(&self, world: ECSReference<'_>) -> ECSResult<()>;
#[cfg(feature = "gpu")]
#[inline]
fn gpu(&self) -> Option<&dyn GpuSystem> {
None
}
}
pub struct FnSystem<F>
where
F: Fn(ECSReference<'_>) -> ECSResult<()> + Send + Sync + 'static,
{
id: SystemID,
name: &'static str,
access: AccessSets,
f: F,
}
impl<F> FnSystem<F>
where
F: Fn(ECSReference<'_>) -> ECSResult<()> + Send + Sync + 'static,
{
pub fn new(id: SystemID, name: &'static str, access: AccessSets, f: F) -> Self {
Self {
id,
name,
access,
f,
}
}
pub fn from_queries(
id: SystemID,
name: &'static str,
queries: &[&crate::engine::query::BuiltQuery],
f: F,
) -> Self {
let mut access = AccessSets::default();
for query in queries {
let derived = query.access_sets();
for (word, other) in access
.read
.components
.iter_mut()
.zip(derived.read.components.iter())
{
*word |= other;
}
for (word, other) in access
.write
.components
.iter_mut()
.zip(derived.write.components.iter())
{
*word |= other;
}
}
for (read_word, write_word) in access
.read
.components
.iter_mut()
.zip(access.write.components.iter())
{
*read_word &= !write_word;
}
Self {
id,
name,
access,
f,
}
}
#[must_use]
pub fn produces(mut self, channel: ChannelID) -> Self {
self.access.produces.insert(channel);
self
}
#[must_use]
pub fn consumes(mut self, channel: ChannelID) -> Self {
self.access.consumes.insert(channel);
self
}
}
impl<F> System for FnSystem<F>
where
F: Fn(ECSReference<'_>) -> ECSResult<()> + Send + Sync + 'static,
{
fn name(&self) -> &str {
self.name
}
fn id(&self) -> SystemID {
self.id
}
fn access(&self) -> &AccessSets {
&self.access
}
fn run(&self, world: ECSReference<'_>) -> ECSResult<()> {
(self.f)(world)
}
}