pebble-engine 0.5.11

A modular, ECS-style graphics/app framework for Rust.
Documentation
use std::{any::TypeId, collections::HashSet};

/// Tracks which resources are *provided* by plugins and which are *required*,
/// validating at startup that every requirement has a corresponding provider.
///
/// Plugins call [`provides`] for each resource they insert and [`required`] for
/// each resource they depend on. [`App::build`](crate::app::App::build) calls
/// [`validate`] after all plugins have been built, panicking with a clear
/// message if any requirement is unmet.
pub(crate) struct RequiredResources {
    provided: HashSet<TypeId>,
    required: Vec<(std::any::TypeId, &'static str)>,
}

impl RequiredResources {
    pub fn new() -> Self {
        Self {
            provided: Default::default(),
            required: Vec::new(),
        }
    }

    /// Mark resource type `T` as provided by the calling plugin.
    pub fn provides<T: 'static>(&mut self) {
        self.provided.insert(TypeId::of::<T>());
    }

    /// Returns `true` if some plugin has declared (via [`provides`](Self::provides))
    /// that it eventually inserts this resource type.
    ///
    /// Used by [`App`](crate::app::App) to distinguish "this resource is
    /// legitimately still being constructed" (an async GPU backend, a
    /// [`LazyResource`](crate::assets::singleton_asset::LazyResource)) —
    /// where a system missing it should just wait — from a genuine
    /// oversight, where nothing was ever going to insert it.
    pub fn is_provided(&self, ty: TypeId) -> bool {
        self.provided.contains(&ty)
    }

    /// Declare that resource type `T` is required, with `label` used in the
    /// error message if it is absent at startup.
    pub fn required<T: 'static>(&mut self, label: &'static str) {
        self.required.push((TypeId::of::<T>(), label));
    }

    /// Panic if any declared requirement has no matching provider.
    pub fn validate(&self) {
        let missing: Vec<_> = self
            .required
            .iter()
            .filter(|(ty, _)| !self.provided.contains(ty))
            .map(|(_, label)| *label)
            .collect();

        if !missing.is_empty() {
            panic!(
                "Pebble startup resource validation failed - required resources with no provider registered:\n{}\n\nThis means a plugin depends on a resource that no other registered plugin provides.",
                missing
                    .iter()
                    .map(|m| format!(" - {m}"))
                    .collect::<Vec<_>>()
                    .join("\n")
            );
        }
    }
}