sky_ecs 0.1.2

High-performance typed chunk-based ECS for Rust
Documentation
use super::stage::SystemAccessDiagnostics;
use crate::ecs::{ComponentType, World};
use std::any::TypeId;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum AccessMode {
    Read,
    Write,
}

#[derive(Clone, Debug)]
struct ComponentAccess {
    id: usize,
    name: String,
    mode: AccessMode,
}

#[derive(Clone, Debug)]
struct ResourceAccess {
    id: TypeId,
    name: &'static str,
    mode: AccessMode,
}

#[derive(Clone, Debug, Default)]
pub(crate) struct AccessSet {
    components: Vec<ComponentAccess>,
    resources: Vec<ResourceAccess>,
    commands: bool,
}

impl AccessSet {
    pub(crate) fn add_component(&mut self, component: ComponentType, mutable: bool) {
        let mode = if mutable {
            AccessMode::Write
        } else {
            AccessMode::Read
        };
        if let Some(existing) = self
            .components
            .iter()
            .find(|access| access.id == component.id())
        {
            assert!(
                existing.mode == AccessMode::Read && mode == AccessMode::Read,
                "system parameter conflict: component `{}` is requested as both {} and {}",
                component.name,
                mode_name(existing.mode),
                mode_name(mode),
            );
            return;
        }
        self.components.push(ComponentAccess {
            id: component.id(),
            name: component.name.clone(),
            mode,
        });
    }

    pub(crate) fn add_resource<R: 'static>(&mut self, mutable: bool) {
        let id = TypeId::of::<R>();
        let name = std::any::type_name::<R>();
        let mode = if mutable {
            AccessMode::Write
        } else {
            AccessMode::Read
        };
        if let Some(existing) = self.resources.iter().find(|access| access.id == id) {
            assert!(
                existing.mode == AccessMode::Read && mode == AccessMode::Read,
                "system parameter conflict: resource `{name}` is requested as both {} and {}",
                mode_name(existing.mode),
                mode_name(mode),
            );
            return;
        }
        self.resources.push(ResourceAccess { id, name, mode });
    }

    pub(crate) fn add_commands(&mut self) {
        assert!(
            !self.commands,
            "system parameter conflict: Commands may appear only once"
        );
        self.commands = true;
    }

    pub(crate) fn conflicts(&self, other: &Self) -> bool {
        for left in &self.components {
            for right in &other.components {
                if left.id == right.id
                    && (left.mode == AccessMode::Write || right.mode == AccessMode::Write)
                {
                    return true;
                }
            }
        }
        for left in &self.resources {
            for right in &other.resources {
                if left.id == right.id
                    && (left.mode == AccessMode::Write || right.mode == AccessMode::Write)
                {
                    return true;
                }
            }
        }
        false
    }

    pub(crate) fn first_missing_resource(&self, world: &World) -> Option<&'static str> {
        self.resources
            .iter()
            .find(|access| !world.contains_resource_id(access.id))
            .map(|access| access.name)
    }

    pub(crate) fn diagnostics(&self) -> SystemAccessDiagnostics {
        let mut diagnostics = SystemAccessDiagnostics {
            uses_commands: self.commands,
            ..SystemAccessDiagnostics::default()
        };
        for access in &self.components {
            match access.mode {
                AccessMode::Read => {
                    diagnostics.component_reads.push(access.name.clone());
                    diagnostics.component_read_ids.push(access.id);
                }
                AccessMode::Write => {
                    diagnostics.component_writes.push(access.name.clone());
                    diagnostics.component_write_ids.push(access.id);
                }
            }
        }
        for access in &self.resources {
            match access.mode {
                AccessMode::Read => {
                    diagnostics.resource_reads.push(access.name.to_owned());
                    diagnostics.resource_read_ids.push(access.id);
                }
                AccessMode::Write => {
                    diagnostics.resource_writes.push(access.name.to_owned());
                    diagnostics.resource_write_ids.push(access.id);
                }
            }
        }
        diagnostics
    }
}

fn mode_name(mode: AccessMode) -> &'static str {
    match mode {
        AccessMode::Read => "read",
        AccessMode::Write => "write",
    }
}