Import Oxide engine (Stages 0–10) under MIT license
Full project snapshot migrated to new Gitea remote without history: engine, editor, physics, script, examples, tests, docs, and assets. Relicensed from GPLv3 to MIT and updated repo URLs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
//! The [`Module`] trait and the engine's built-in modules.
|
||||
//!
|
||||
//! A module is the unit of engine extension: it bundles systems, component
|
||||
//! types, asset loaders, and resources behind one documented entry point, so
|
||||
//! anyone — including AI agents — can add a capability by writing a module, and
|
||||
//! an exported game compiles in only the modules it registers. The editor
|
||||
//! integration half of the trait arrives in Stage 6.
|
||||
|
||||
use super::{App, ModuleBundle, Schedule};
|
||||
use crate::layer::{Layer, Tags};
|
||||
use crate::math::Transform;
|
||||
use crate::render::MeshRenderer;
|
||||
use crate::scene::Node;
|
||||
|
||||
/// A self-contained unit of engine functionality.
|
||||
///
|
||||
/// Implement [`build`](Self::build) to register everything the module provides
|
||||
/// via the [`App`] facade ([`add_system`](App::add_system),
|
||||
/// [`register_type`](App::register_type), [`add_loader`](App::add_loader),
|
||||
/// [`insert_resource`](App::insert_resource)). Everything registered during
|
||||
/// `build` is attributed to the module, so it can be enabled, disabled, or
|
||||
/// removed as a unit.
|
||||
pub trait Module: 'static {
|
||||
/// A stable, unique name (used to enable/disable/remove the module and, in
|
||||
/// later stages, to express dependencies).
|
||||
fn name(&self) -> &'static str;
|
||||
|
||||
/// Registers the module's systems, types, loaders, and resources on `app`.
|
||||
fn build(&self, app: &mut App);
|
||||
}
|
||||
|
||||
/// The core module: registers the always-present scene component types for
|
||||
/// reflection (dual-editability), so the editor and scripts can address them.
|
||||
///
|
||||
/// This is the runtime "wrapper" for the math/scene/layer building blocks that
|
||||
/// already exist as plain library types — it does not add behavior, it exposes
|
||||
/// those types through the [`TypeRegistry`](crate::reflect::TypeRegistry).
|
||||
pub struct CoreModule;
|
||||
|
||||
impl Module for CoreModule {
|
||||
fn name(&self) -> &'static str {
|
||||
"core"
|
||||
}
|
||||
|
||||
fn build(&self, app: &mut App) {
|
||||
app.register_type::<Transform>("Transform");
|
||||
app.register_type::<Node>("Node");
|
||||
app.register_type::<Layer>("Layer");
|
||||
app.register_type::<Tags>("Tags");
|
||||
}
|
||||
}
|
||||
|
||||
/// The render module: registers the renderable scene components for reflection.
|
||||
///
|
||||
/// The forward renderer itself is driven by the editor/host today; this module
|
||||
/// is what makes [`MeshRenderer`] a first-class, dual-editable component. As the
|
||||
/// data-driven render pipeline grows it will register its render-phase systems
|
||||
/// here too.
|
||||
pub struct RenderModule;
|
||||
|
||||
impl Module for RenderModule {
|
||||
fn name(&self) -> &'static str {
|
||||
"render"
|
||||
}
|
||||
|
||||
fn build(&self, app: &mut App) {
|
||||
app.register_type::<MeshRenderer>("MeshRenderer");
|
||||
// Placeholder render-phase system so the phase is exercised; real passes
|
||||
// land with the Stage 5 render pipeline piece.
|
||||
app.add_system(Schedule::Render, |_app| {});
|
||||
}
|
||||
}
|
||||
|
||||
/// The engine's standard set of built-in modules, added with
|
||||
/// [`App::add_modules`](super::App::add_modules).
|
||||
///
|
||||
/// ```
|
||||
/// use oxide_engine::app::{App, DefaultModules};
|
||||
/// let mut app = App::new();
|
||||
/// app.add_modules(DefaultModules);
|
||||
/// assert!(app.has_module("core") && app.has_module("render"));
|
||||
/// ```
|
||||
pub struct DefaultModules;
|
||||
|
||||
impl ModuleBundle for DefaultModules {
|
||||
fn add_to(self, app: &mut App) {
|
||||
app.add_module(CoreModule);
|
||||
app.add_module(RenderModule);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::render::PrimitiveShape;
|
||||
|
||||
#[test]
|
||||
fn default_modules_register_core_types() {
|
||||
let mut app = App::new();
|
||||
app.add_modules(DefaultModules);
|
||||
assert!(app.has_module("core"));
|
||||
assert!(app.has_module("render"));
|
||||
assert!(app.types.is_registered("Transform"));
|
||||
assert!(app.types.is_registered("MeshRenderer"));
|
||||
assert_eq!(app.modules().collect::<Vec<_>>(), vec!["core", "render"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn removing_a_module_removes_its_contributions() {
|
||||
let mut app = App::new();
|
||||
app.add_modules(DefaultModules);
|
||||
assert!(app.types.is_registered("MeshRenderer"));
|
||||
let systems_before = app.system_count();
|
||||
|
||||
assert!(app.remove_module("render"));
|
||||
// Its registered type is gone, its render system is gone, core remains.
|
||||
assert!(!app.has_module("render"));
|
||||
assert!(!app.types.is_registered("MeshRenderer"));
|
||||
assert!(app.types.is_registered("Transform"));
|
||||
assert!(app.system_count() < systems_before);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disabling_a_module_skips_its_systems_without_removing() {
|
||||
use std::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
|
||||
struct Ticker(Rc<Cell<u32>>);
|
||||
impl Module for Ticker {
|
||||
fn name(&self) -> &'static str {
|
||||
"ticker"
|
||||
}
|
||||
fn build(&self, app: &mut App) {
|
||||
let counter = self.0.clone();
|
||||
app.add_system(Schedule::Update, move |_| counter.set(counter.get() + 1));
|
||||
}
|
||||
}
|
||||
|
||||
let count = Rc::new(Cell::new(0u32));
|
||||
let mut app = App::new();
|
||||
app.add_module(Ticker(count.clone()));
|
||||
|
||||
app.update(0.0);
|
||||
assert_eq!(count.get(), 1);
|
||||
|
||||
app.set_module_enabled("ticker", false);
|
||||
app.update(0.0); // skipped
|
||||
assert_eq!(count.get(), 1);
|
||||
|
||||
app.set_module_enabled("ticker", true);
|
||||
app.update(0.0); // runs again
|
||||
assert_eq!(count.get(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_module_can_add_a_loader_removed_with_it() {
|
||||
// A module registers MeshRenderer + uses a primitive, then is removed.
|
||||
let mut app = App::new();
|
||||
app.add_module(RenderModule);
|
||||
// Sanity: the primitive enum the render component references is usable.
|
||||
assert_eq!(PrimitiveShape::ALL.len(), 3);
|
||||
assert!(app.remove_module("render"));
|
||||
assert!(!app.types.is_registered("MeshRenderer"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user