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:
Homer Simpson
2026-07-05 20:41:02 +02:00
parent 2afb56b329
commit f56a1eea3b
128 changed files with 40493 additions and 2 deletions
+101
View File
@@ -0,0 +1,101 @@
//! The [`ScriptAsset`] — a loaded script's source — and its [`ScriptLoader`].
//!
//! A script lives on disk as a `.rhai` file under the project's `assets/scripts/`
//! folder. Loading one yields a [`ScriptAsset`], which is just the source text
//! plus its origin path; turning that text into something executable (a compiled
//! `rhai` AST) is the job of the [`ScriptEngine`](crate::ScriptEngine), done at
//! run time so a live edit can recompile without touching the asset plumbing.
//!
//! Keeping the asset as plain source (rather than a pre-compiled AST) is what
//! makes hot-reload cheap: the file watcher swaps in fresh source on change and
//! the host recompiles, with no engine-specific data baked into the asset cache.
use std::path::Path;
use oxide_engine::asset::{AssetError, AssetLoader};
/// A loaded script: its source text and the path it came from.
///
/// This is the *asset* a [`Script`](crate::Script) component points at via an
/// [`AssetRef<ScriptAsset>`](oxide_engine::asset::AssetRef). It is deliberately
/// inert — holding source, not behaviour — so the same file can be recompiled on
/// every live reload.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ScriptAsset {
/// The script's source code.
pub source: String,
/// A human-readable name for diagnostics (the file stem, when loaded from
/// disk), used in error messages and the script console.
pub name: String,
}
impl ScriptAsset {
/// Builds an asset from in-memory source with the given diagnostic `name`.
pub fn from_source(name: impl Into<String>, source: impl Into<String>) -> Self {
Self {
source: source.into(),
name: name.into(),
}
}
}
/// The [`AssetServer`](oxide_engine::asset::AssetServer) loader for `.rhai`
/// scripts.
///
/// Registered by the [`ScriptModule`](crate::ScriptModule) (handles `.rhai`), so
/// `assets.load::<ScriptAsset>("scripts/spin.rhai")` works once the module is
/// added. It reads the file as UTF-8 and records the file stem as the asset's
/// diagnostic name.
pub struct ScriptLoader;
impl AssetLoader for ScriptLoader {
type Asset = ScriptAsset;
fn extensions(&self) -> &'static [&'static str] {
&["rhai"]
}
fn load(&self, path: &Path) -> Result<ScriptAsset, AssetError> {
let source = std::fs::read_to_string(path).map_err(|err| AssetError::Load {
path: path.to_path_buf(),
message: err.to_string(),
})?;
let name = path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("script")
.to_string();
Ok(ScriptAsset { source, name })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_source_keeps_name_and_text() {
let a = ScriptAsset::from_source("spin", "let x = 1;");
assert_eq!(a.name, "spin");
assert_eq!(a.source, "let x = 1;");
}
#[test]
fn loader_reads_a_file_and_uses_the_stem_as_name() {
let dir = std::env::temp_dir().join("oxide-script-loader-test");
std::fs::create_dir_all(&dir).unwrap();
let path = dir.join("hello.rhai");
std::fs::write(&path, "print(\"hi\");").unwrap();
let asset = ScriptLoader.load(&path).unwrap();
assert_eq!(asset.name, "hello");
assert!(asset.source.contains("print"));
std::fs::remove_file(&path).ok();
}
#[test]
fn loader_claims_the_rhai_extension() {
assert_eq!(ScriptLoader.extensions(), &["rhai"]);
}
}