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 9eead719b0
157 changed files with 47270 additions and 2 deletions
+16
View File
@@ -0,0 +1,16 @@
[package]
name = "oxide-engine-derive"
description = "Derive macros for Oxide's reflection system (#[derive(Reflect)])"
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
rust-version.workspace = true
[lib]
proc-macro = true
[dependencies]
syn.workspace = true
quote.workspace = true
proc-macro2.workspace = true
+257
View File
@@ -0,0 +1,257 @@
//! Derive macros for Oxide's reflection system.
//!
//! This crate exists for exactly one job: `#[derive(Reflect)]`. It is the
//! compile-time half of the engine's **dual-editable types** principle —
//! every component's public fields should be editable from the editor
//! inspector and from scripts through *one* representation, with no
//! hand-written per-type code. The runtime half (the `Reflect` trait, the
//! `FieldInfo` descriptor, and the `TypeRegistry`) lives in
//! `oxide_engine::reflect`; this crate only generates the trait impl.
//!
//! ## What the derive generates
//!
//! For a struct with named fields, `#[derive(Reflect)]` emits an
//! `oxide_engine::reflect::Reflect` impl that exposes each **public**,
//! non-skipped field as:
//!
//! - a static [`FieldInfo`] entry (`name` + syntactic `type_name`), so a
//! generic inspector can enumerate fields and pick a widget per type, and
//! - per-field RON get/set, so a single field can be read or written without
//! touching the rest of the component (the unit an inspector edits).
//!
//! Only `pub` fields are reflected — this matches the Unity/Godot convention
//! that *public* fields are the editable surface. Use `#[reflect(skip)]` to
//! exclude a public field.
//!
//! ```ignore
//! use oxide_engine::reflect::Reflect;
//!
//! #[derive(Reflect, serde::Serialize, serde::Deserialize)]
//! struct Timer {
//! pub repeating: bool,
//! pub duration: f32,
//! #[reflect(skip)]
//! pub elapsed: f32, // runtime state — not an authored field
//! }
//! ```
//!
//! Every reflected field must itself be `serde`-serializable, since get/set
//! round-trip through RON.
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Fields, Visibility};
/// Derives `oxide_engine::reflect::Reflect` for a struct with named fields.
///
/// See the [crate-level docs](crate) for the field-selection rules
/// (public-only, `#[reflect(skip)]`).
#[proc_macro_derive(Reflect, attributes(reflect))]
pub fn derive_reflect(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
// Named-field structs and tuple structs are both supported. Tuple-struct
// fields are addressed by their positional index ("0", "1", …), matching
// Rust's own `self.0` / `self.1` syntax — this lets one-field newtype
// components like `Layers(pub LayerMask)` reflect without a wrapper.
let raw_fields = match &input.data {
Data::Struct(data) => match &data.fields {
Fields::Named(named) => named.named.iter().collect::<Vec<_>>(),
Fields::Unnamed(unnamed) => unnamed.unnamed.iter().collect::<Vec<_>>(),
Fields::Unit => {
return compile_error(name, "Reflect cannot be derived for unit structs")
}
},
_ => return compile_error(name, "Reflect can only be derived for structs"),
};
let mut infos = Vec::new();
let mut get_arms = Vec::new();
let mut set_arms = Vec::new();
for (index, field) in raw_fields.iter().enumerate() {
// Public-only: private fields are implementation detail, not the
// authored/editable surface.
if !matches!(field.vis, Visibility::Public(_)) {
continue;
}
let attrs = parse_field_attrs(field);
if attrs.skip {
continue;
}
// For named structs the field name + accessor is the ident; for tuple
// structs the name is the index as a string and the accessor is the
// syn::Index token (which renders as `0`, `1`, ...).
let (field_name, accessor) = match &field.ident {
Some(ident) => (ident.to_string(), quote!(#ident)),
None => {
let idx = syn::Index::from(index);
(index.to_string(), quote!(#idx))
}
};
let ty = &field.ty;
// Syntactic type text, e.g. "f32", "bool", "Vec3", "Handle < Font >".
// The inspector dispatches a widget on this; unknown types fall back to
// a raw RON editor.
let type_name = quote!(#ty).to_string();
let range_tokens = match attrs.range {
Some((min, max)) => quote! {
::core::option::Option::Some((#min, #max))
},
None => quote! { ::core::option::Option::None },
};
infos.push(quote! {
::oxide_engine::reflect::FieldInfo {
name: #field_name,
type_name: #type_name,
range: #range_tokens,
}
});
get_arms.push(quote! {
#field_name => ::oxide_engine::reflect::__reflect_to_ron(&self.#accessor),
});
set_arms.push(quote! {
#field_name => {
self.#accessor = ::oxide_engine::reflect::__reflect_from_ron(#field_name, value)?;
::core::result::Result::Ok(())
}
});
}
let field_count = infos.len();
quote! {
impl #impl_generics ::oxide_engine::reflect::Reflect for #name #ty_generics #where_clause {
fn fields(&self) -> &'static [::oxide_engine::reflect::FieldInfo] {
static FIELDS: [::oxide_engine::reflect::FieldInfo; #field_count] = [
#(#infos),*
];
&FIELDS
}
fn get_field(&self, name: &str) -> ::core::option::Option<::std::string::String> {
match name {
#(#get_arms)*
_ => ::core::option::Option::None,
}
}
fn set_field(
&mut self,
name: &str,
value: &str,
) -> ::core::result::Result<(), ::oxide_engine::reflect::ReflectError> {
match name {
#(#set_arms)*
_ => ::core::result::Result::Err(
::oxide_engine::reflect::ReflectError::UnknownField(
::std::string::ToString::to_string(name),
),
),
}
}
}
}
.into()
}
/// Parsed `#[reflect(...)]` attributes on a single field.
#[derive(Default)]
struct FieldAttrs {
/// `#[reflect(skip)]` — exclude this public field from reflection.
skip: bool,
/// `#[reflect(min = X, max = Y)]` — numeric bounds passed to inspector
/// widgets so a normalized `f32` field becomes a slider instead of a drag.
/// Both must be present for a range to be recorded.
range: Option<(f32, f32)>,
}
fn parse_field_attrs(field: &syn::Field) -> FieldAttrs {
let mut out = FieldAttrs::default();
let mut min: Option<f32> = None;
let mut max: Option<f32> = None;
for attr in &field.attrs {
if !attr.path().is_ident("reflect") {
continue;
}
let _ = attr.parse_nested_meta(|meta| {
if meta.path.is_ident("skip") {
out.skip = true;
} else if meta.path.is_ident("min") {
let lit: syn::LitFloat = meta.value()?.parse()?;
min = Some(lit.base10_parse::<f32>()?);
} else if meta.path.is_ident("max") {
let lit: syn::LitFloat = meta.value()?.parse()?;
max = Some(lit.base10_parse::<f32>()?);
}
Ok(())
});
}
if let (Some(mn), Some(mx)) = (min, max) {
out.range = Some((mn, mx));
}
out
}
/// Derives `oxide_engine::reflect::ReflectEnum` for a fieldless (C-like) enum,
/// exposing its variant names so a generic inspector can render a dropdown for
/// fields of that enum type.
///
/// Only **unit** variants are supported — a variant carrying data has no single
/// "pick from a list" representation. Variant names round-trip as RON (a unit
/// variant `Foo::Bar` serializes as `Bar`), which is exactly what `set_field`
/// consumes.
///
/// ```ignore
/// use oxide_engine::reflect::ReflectEnum;
///
/// #[derive(ReflectEnum, serde::Serialize, serde::Deserialize)]
/// enum Facing { North, East, South, West }
/// assert_eq!(Facing::variants(), &["North", "East", "South", "West"]);
/// ```
#[proc_macro_derive(ReflectEnum)]
pub fn derive_reflect_enum(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let data = match &input.data {
Data::Enum(data) => data,
_ => return compile_error(name, "ReflectEnum can only be derived for enums"),
};
let mut variant_names = Vec::new();
for variant in &data.variants {
if !matches!(variant.fields, Fields::Unit) {
return compile_error(
&variant.ident,
"ReflectEnum requires unit (fieldless) variants",
);
}
variant_names.push(variant.ident.to_string());
}
let count = variant_names.len();
quote! {
impl #impl_generics ::oxide_engine::reflect::ReflectEnum for #name #ty_generics #where_clause {
fn variants() -> &'static [&'static str] {
static VARIANTS: [&str; #count] = [ #(#variant_names),* ];
&VARIANTS
}
}
}
.into()
}
/// Emit a `compile_error!` at the derived type so the message is attributed
/// to the user's struct, not somewhere inside the generated impl.
fn compile_error(name: &syn::Ident, message: &str) -> TokenStream {
syn::Error::new(name.span(), message)
.to_compile_error()
.into()
}