I broke everything, but we're less cooked.

This commit is contained in:
2026-02-05 22:28:41 +00:00
parent ae91008c59
commit 6aa2003145
9 changed files with 1255 additions and 569 deletions

1565
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,11 +4,12 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
bevy = { version = "0.16", features = ["file_watcher"] } bevy = { version = "0.18.0", features = ["file_watcher"] }
include_dir = "0.7.4" include_dir = "0.7.4"
mlua = { version = "0.11.5", features = ["luau","luau-jit","luau-vector4","serde","send"] } mlua = { version = "0.11.5", features = ["luau","luau-jit","luau-vector4","serde","send","async"] }
nameof = "1.3.0" nameof = "1.3.0"
thiserror = "2.0.18" thiserror = "2.0.18"
serde = { version = "1.0.228", features = ["derive"] }
[profile.dev.package."*"] [profile.dev.package."*"]
opt-level = 3 opt-level = 3

View File

@@ -1,4 +1,2 @@
print("wsg") print("wsg")
for index,item in pairs(_ENV) do print("global",_G)
print(index,item)
end

View File

@@ -1,59 +1,92 @@
use std::fmt::Formatter;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use std::sync::mpsc::{channel, Sender};
use bevy::ecs::relationship::Relationship;
use bevy::prelude::*; use bevy::prelude::*;
use mlua::AppDataRefMut;
use mlua::prelude::*; use mlua::prelude::*;
use mlua::{FromLua,Value};
use serde::Serialize;
use crate::duck::part::*; use crate::duck::part::*;
use crate::duck::script::*; use crate::duck::script::*;
#[derive(Debug)]
struct Error(String);
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl core::error::Error for Error {}
#[derive(Component,Resource)] #[derive(Component,Resource)]
pub struct DefaultAsset<S: Asset>(Handle<S>); pub struct DefaultAsset<S: Asset>(Handle<S>);
impl<S: Asset> DefaultAsset<S> { impl<S: Asset> DefaultAsset<S> {
pub fn clone(world: &World) -> Handle<S> { pub fn clone(&self) -> Handle<S> { self.0.clone() }
world.get_resource::<DefaultAsset<S>>().unwrap().0.clone()
}
pub fn new(commands: &mut Commands, handle: Handle<S>) { pub fn new(commands: &mut Commands, handle: Handle<S>) {
commands.insert_resource(DefaultAsset(handle)); commands.insert_resource(DefaultAsset(handle));
} }
} }
fn world<'w>(lua: &Lua) -> std::result::Result<&'w mut World, mlua::Error> {
pub trait Class { lua.app_data_mut::<&World>()
fn new(entity: &mut EntityWorldMut) -> AnyInstance; .ok_or_else(Error("Direct world access not available here!".into()))
fn load(entity: &mut EntityWorldMut) -> AnyInstance { Self::new(entity) } .map_err(LuaError::external).deref_mut()
fn reload(entity: &mut EntityWorldMut) {}
fn add_fields<T, F: LuaUserDataFields<T>>(_fields: &mut F) {}
fn add_methods<T, F: LuaUserDataMethods<T>>(_methods: &mut F) {}
fn class_name() -> &'static str {"Instance"}
} }
pub struct Instance<S: Class>(Entity,PhantomData<S>); impl LuaUserData for Transform {
impl<S: Class> LuaUserData for Instance<S> { }
pub struct Instance(Entity);
impl FromLua for Instance {
fn from_lua(value: mlua::Value, _: &Lua) -> mlua::Result<Self> {
match value {
mlua::Value::UserData(ud) => Ok(*ud.borrow::<Self>()?),
_ => unreachable!(),
}
}
}
impl LuaUserData for Instance {
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) { fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
S::add_fields(fields) fields.add_field_method_get("parent",|lua: Lua, this| {
Ok(world(&lua)?.get::<ChildOf>(this.0).map(|parent| {Instance(parent.0)}))
});
fields.add_field_method_set("parent",|lua: Lua, this, other: Option<Instance>| {
let world = world(&lua)?;
if let Some(other) = other {
let mut entity = world.get_entity_mut(other.0).map_err(LuaError::external)?;
entity.add_child(this.0);
} else {
let entity = world.get_entity_mut();
}
Ok(())
});
fields.add_field_method_get("transform",|lua: Lua, this| {
Ok(world(&lua)?.get::<Transform>(this.0).unwrap())
});
fields.add_field_method_set("transform",|lua: Lua, this| {
Ok()
})
crate::script::add_fields(fields);
crate::part::add_fields(fields);
} }
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) { fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
S::add_methods(methods) methods.add_method("despawn",|lua: Lua, this| {
let world = world(lua).map_err(LuaError::external)?;
world.get_entity(this).map_err(LuaError::external)?;
Ok(())
});
crate::script::add_methods(methods);
crate::part::add_fields(methods);
} }
} }
impl Instance {
impl<S: Class> Instance<S> { fn world_access(&self, lua: &Lua) -> std::result::Result<EntityWorldMut, mlua::Error> {
fn new_entity(world: &mut World) -> Entity { world(lua)?.get_entity_mut(self.0).map_err(LuaError::external)
let mut entity = world.spawn_empty();
entity.entry().or_insert(Name::new("Instance"));
let instance = S::new(&mut entity);
entity.entry().or_insert(instance);
entity.id()
} }
pub(crate) fn new(entity: &mut EntityWorldMut) -> Instance<S> {
Instance(entity.id(),PhantomData)
}
}
#[derive(Component)]
pub enum AnyInstance {
Script(Instance<Script>),
Part(Instance<Located<Part>>)
} }
pub fn setup(app: &mut App) { pub fn setup(app: &mut App) {

View File

@@ -1,20 +1,8 @@
use mlua::UserData;
use mlua::Lua;
use mlua::UserDataFields;
use bevy::prelude::*; use bevy::prelude::*;
use mlua::UserDataMethods;
use nameof::name_of_type;
use std::ops::Deref;
use std::ops::DerefMut;
use thiserror::Error;
mod part; pub mod part;
mod script; pub mod script;
mod instance; pub mod instance;
use include_dir::{include_dir, Dir};
static DATA_DIR: Dir<'_> = include_dir!("data");
pub struct DuckPlugin; pub struct DuckPlugin;

View File

@@ -1,25 +1,24 @@
use std::marker::PhantomData;
use bevy::prelude::*; use bevy::prelude::*;
use mlua::{UserDataFields, UserDataMethods};
use mlua::prelude::{LuaUserDataFields, LuaUserDataMethods};
use crate::duck::instance::*; use crate::duck::instance::*;
pub struct Located<S: Class>(S); pub struct Part;
impl<S: Class> Class for Located<S> {
fn new(entity: &mut EntityWorldMut) -> AnyInstance {
entity.insert(Transform::IDENTITY);
S::new(entity)
}
}
pub struct Part();
impl Class for Part { impl Class for Part {
fn new(entity: &mut EntityWorldMut) -> AnyInstance { fn add_to(entity: &mut EntityCommands) -> Instance {
let world = entity.world(); let id = entity.id();
let mesh = DefaultAsset::<Mesh>::clone(world); entity.commands().run_system_cached_with(move |
let material = DefaultAsset::<StandardMaterial>::clone(world); In(id): In<Entity>,
entity.insert(( mut commands: Commands,
Mesh3d(mesh), mesh: Res<DefaultAsset<Mesh>>,
MeshMaterial3d(material), mat: Res<DefaultAsset<StandardMaterial>>
)); | {
AnyInstance::Part(Instance::new(entity)) if let Ok(mut entity) = commands.get_entity(id) {
entity.insert((Mesh3d(mesh.clone()),MeshMaterial3d(mat.clone())));
}
},id);
Instance::Part(Object::<Part>::of(entity.id()))
} }
} }
@@ -34,4 +33,16 @@ pub fn startup(
pub fn setup(app: &mut App) { pub fn setup(app: &mut App) {
app.add_systems(Startup,startup); app.add_systems(Startup,startup);
}
pub(crate) fn add_fields<F: LuaUserDataFields<Instance>>(fields: &mut F) {
}
pub(crate) fn add_methods<M: LuaUserDataMethods<Instance>>(methods: &mut M) {
}
pub fn make(entity: &mut EntityCommands) {
entity.insert(Transform::IDENTITY);
} }

View File

@@ -1,15 +1,15 @@
use std::ops::Deref;
use bevy::asset::AsyncReadExt; use bevy::asset::AsyncReadExt;
use bevy::prelude::*; use bevy::prelude::*;
use bevy::asset::{io::Reader, AssetLoader, LoadContext}; use bevy::asset::{io::Reader, AssetLoader, LoadContext};
use bevy::ecs::system::SystemId; use mlua::prelude::*;
use mlua::Lua; use mlua::Value;
use thiserror::Error; use thiserror::Error;
use crate::duck::instance::*; use crate::duck::instance::*;
#[derive(Asset, TypePath, Debug)] #[derive(Asset, TypePath, Debug)]
pub struct ScriptAsset { pub struct ScriptAsset {
source: String pub source: String,
pub path: String
} }
#[non_exhaustive] #[non_exhaustive]
@@ -35,29 +35,56 @@ impl AssetLoader for ScriptAssetLoader {
info!("Loading Script..."); info!("Loading Script...");
let mut raw = String::new(); let mut raw = String::new();
reader.read_to_string(&mut raw).await?; reader.read_to_string(&mut raw).await?;
Ok(ScriptAsset { source: raw }) Ok(ScriptAsset { source: raw, path: "unknown".into() })
} }
} }
#[derive(Component)] #[derive(Component)]
pub struct Script(Handle<ScriptAsset>); struct ScriptHandle(Handle<ScriptAsset>);
pub struct Script;
impl Class for Script { impl Class for Script {
fn new(entity: &mut EntityWorldMut) -> AnyInstance { fn add_to(entity: &mut EntityCommands) -> Instance {
let handle = DefaultAsset::<ScriptAsset>::clone(entity.world()); let id = entity.id();
entity.entry().or_insert(Script(handle)); entity.commands().run_system_cached_with(| // Get commands -> defer a system
let world = entity.world(); In(id): In<Entity>,
mut commands: Commands, // Commands to add the component
script: Res<DefaultAsset<ScriptAsset>> // The default script asset to load it with
| {
if let Ok(mut entity) = commands.get_entity(id) {
entity.insert(ScriptHandle(script.clone()));
}
},id);
Self::run(entity); Self::run(entity);
AnyInstance::Script(Instance::new(entity)) Instance::Script(Object::<Script>::of(entity.id()))
}
fn fields<S: Class, F: LuaUserDataFields<Object<S>>>(fields: &mut F) {
}
fn methods<S: Class, F: LuaUserDataMethods<Object<S>>>(methods: &mut F) {
} }
} }
impl Script {
fn run(entity: &mut EntityWorldMut) { fn run(entity: &mut EntityCommands) {
let lua = &entity.world().get_resource::<DefaultScriptContext>().unwrap().0; let id= entity.id();
let script_assets = entity.get_resource::<Assets<ScriptAsset>>().unwrap(); entity.commands().run_system_cached_with(| // Get commands -> defer a system:
let script_asset_handle = &entity.get::<Script>().unwrap().0; In(id): In<Entity>,
let script = script_assets.get(script_asset_handle).unwrap(); query: Query<&ScriptHandle>, // A query to find it again
lua.load(script.source.clone()).exec().unwrap(); context: Res<DefaultScriptContext>, // A resource for our script context
} scripts: Res<Assets<ScriptAsset>> // The assets resource for all our scripts
| {
if let Ok(script) = query.get(id) { // If it's there
let asset = scripts.get(script.0.id()).unwrap(); // Get the asset (!)
let lua = &context.0;
let chunk = lua.load(asset.source.clone()).set_environment(lua.globals());
chunk.environment().unwrap().set("script",Instance(id)).unwrap();
match chunk.exec() {
Ok(()) => {},
Err(e) => { println!("Error: {:?}", e); }
} // Execute the script (!)
}
},id);
} }
#[derive(Resource)] #[derive(Resource)]
@@ -68,9 +95,12 @@ pub fn startup(
mut script_assets: ResMut<Assets<ScriptAsset>>, mut script_assets: ResMut<Assets<ScriptAsset>>,
) { ) {
DefaultAsset::new(&mut commands,script_assets.add(ScriptAsset { DefaultAsset::new(&mut commands,script_assets.add(ScriptAsset {
source: "print(\"Hello world!\")".into() source: "print(\"Hello world!\"); print(script)".into(),
path: "default.lua".into(),
})); }));
commands.insert_resource(DefaultScriptContext(Lua::new())); let lua = Lua::new();
lua.sandbox(true).unwrap();
commands.insert_resource(DefaultScriptContext(lua));
} }
pub fn setup(app: &mut App) { pub fn setup(app: &mut App) {
@@ -78,3 +108,11 @@ pub fn setup(app: &mut App) {
app.init_asset_loader::<ScriptAssetLoader>(); app.init_asset_loader::<ScriptAssetLoader>();
app.add_systems(Startup,startup); app.add_systems(Startup,startup);
} }
pub(crate) fn add_fields<F: LuaUserDataFields<Instance>>(fields: &mut F) {
}
pub(crate) fn add_methods<M: LuaUserDataMethods<Instance>>(methods: &mut M) {
}

View File

@@ -1,7 +1,7 @@
use bevy::prelude::*; use bevy::prelude::*;
mod duck; mod duck;
use duck::DuckPlugin; use duck::*;
fn main() { fn main() {
App::new() App::new()
@@ -41,4 +41,6 @@ fn setup(
Camera3d::default(), Camera3d::default(),
Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y), Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),
)); ));
commands.new::<Script>();
commands.new::<Script>();
} }