Compare commits

4 Commits

14 changed files with 1463 additions and 505 deletions

8
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

11
.idea/bevy2.iml generated Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="EMPTY_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/bevy2.iml" filepath="$PROJECT_DIR$/.idea/bevy2.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

1566
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,10 +4,12 @@ version = "0.1.0"
edition = "2024"
[dependencies]
bevy = { version = "0.16", features = ["file_watcher"] }
bevy = { version = "0.18.0", features = ["file_watcher"] }
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"
thiserror = "2.0.18"
serde = { version = "1.0.228", features = ["derive"] }
[profile.dev.package."*"]
opt-level = 3

View File

@@ -1,3 +1,4 @@
use bevy::ecs::resource;
use mlua::UserData;
use mlua::Lua;
use bevy::prelude::*;
@@ -11,16 +12,23 @@ use include_dir::{include_dir, Dir};
static DATA_DIR: Dir<'_> = include_dir!("data");
struct BevyContext<'a> {
commands: Commands<'a, 'a>
struct Context<'a> {
commands: Commands<'a, 'a>,
resource: Option<&'a Resource>
}
impl<'a> BevyContext<'a> {
fn from_raw(commands: Commands<'a,'a>) -> Self {
BevyContext { commands: commands }
#[derive(Resource)]
struct Resource {
default_script_context: Entity
}
impl Resource {
fn new_from_world(world: &mut World) -> Self {
let mut context = Context { commands: world.commands(), resource: Option::None };
let (entity_commands,_) = ScriptContext::new_entity( &mut context );
Resource {
default_script_context: entity_commands.id()
}
}
/*fn get_instance<T>(&self, entity: Entity) -> &T {
self.commands.entity(entity).
}*/
}
// Id for this object to the server
@@ -31,7 +39,7 @@ trait Class {
// Class name for filtering
fn class_name() -> String { name_of_type!(Self).into() }
// Create a default object
fn new_entity<'a>(context: &BevyContext) -> (EntityCommands<'a>, Self);
fn new_entity<'a>(context: &'a mut Context) -> (EntityCommands<'a>, Self) where Self: Sized;
// fn clone(&self) -> Self;
fn add_fields<T, F: UserDataFields<T>>(_fields: &mut F) {}
fn add_methods<T, F: UserDataMethods<T>>(_methods: &mut F) {}
@@ -53,7 +61,7 @@ impl<S: Class> UserData for Instance<S> {
}
}
impl<S: Class> Instance<S> {
fn new(context: &BevyContext) -> Self {
fn new_entity(context: &'_ mut Context) -> Self {
let (entity,sub) = S::new_entity(context);
Instance {
name: S::class_name(),
@@ -68,9 +76,11 @@ struct Constructor;
impl UserData for Constructor {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("part", |lua, this, ()| {
let part = IPart::new()
methods.add_method("part",|lua, this: &Constructor, ()| {
Ok(IPart::new_entity(lua.app_data_mut::<Context>().unwrap().deref_mut()))
});
methods.add_method("script",|lua, this: &Constructor, ()| {
Ok(IScript::new_entity(lua.app_data_mut::<Context>().unwrap().deref_mut()))
});
}
}
@@ -80,7 +90,7 @@ struct ScriptContext {
lua: Lua
}
impl Class for ScriptContext {
fn new_entity(context: &BevyContext) -> (EntityCommands<'_>,Self) {
fn new_entity<'a>(context: &'a mut Context) -> (EntityCommands<'a>,Self) {
( context.commands.spawn_empty(), ScriptContext::new() )
}
}
@@ -98,21 +108,30 @@ struct Script {
context: Entity
}
impl Class for Script {
fn new_entity(context: &BevyContext) -> (Entity, Self) {
( context.)
fn new_entity<'a>(context: &'a mut Context) -> (EntityCommands<'a>, Self) {
(
context.commands.spawn_empty(),
Script {
context: context.resource.as_ref().unwrap().default_script_context
}
)
}
}
// Attachment
struct Node;
impl Class for Node {}
impl Class for Node {
fn new_entity<'a>(context: &'a mut Context) -> (EntityCommands<'a>, Self) {
( context.commands.spawn_empty(), Node {} )
}
}
struct Part {
}
impl Class for Part {
fn new_entity(context: &BevyContext) -> Self {
commands.spawn()
fn new_entity<'a>(context: &'a mut Context) -> (EntityCommands<'a>, Self) {
( context.commands.spawn_empty(), Part {} )
}
}
impl UserData for Part {}
@@ -129,9 +148,9 @@ impl<S: Class> UserData for Located<S> {
}
}
impl<S: Class> Class for Located<S> {
fn new_entity(context: &BevyContext) -> Self {
fn new_entity<'a>(context: &'a mut Context) -> (EntityCommands<'a>,Self) {
let (entity,sub) = S::new_entity(context);
Located { class: S::new_entity(context), }
( entity, Located { class: sub } )
}
}
@@ -152,15 +171,27 @@ enum AnyInstance {
Part(IPart)
}
fn script_executor() {
fn script_executor(mut commands: Commands, resource: Res<Resource>) {
}
fn script_test(commands: Commands, resource: Res<Resource>) {
let mut context = Context {commands: commands, resource: Some(resource.as_ref())};
IScript::new_entity(&mut context);
}
fn setup(world: &mut World) {
let res = Resource::new_from_world(world);
world.insert_resource(res);
}
pub struct DuckPlugin;
impl Plugin for DuckPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update,(script_executor));
app.insert_resource(ScriptContext::new_entity())
app.add_systems(Startup,setup);
app.add_systems(Update,script_executor);
app.add_systems(Startup,script_test);
app.insert_resource(ScriptContext::new());
}
}

View File

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

94
src/duck/instance.rs Normal file
View File

@@ -0,0 +1,94 @@
use std::fmt::Formatter;
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use std::sync::mpsc::{channel, Sender};
use bevy::ecs::relationship::Relationship;
use bevy::prelude::*;
use mlua::AppDataRefMut;
use mlua::prelude::*;
use mlua::{FromLua,Value};
use serde::Serialize;
use crate::duck::part::*;
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)]
pub struct DefaultAsset<S: Asset>(Handle<S>);
impl<S: Asset> DefaultAsset<S> {
pub fn clone(&self) -> Handle<S> { self.0.clone() }
pub fn new(commands: &mut Commands, handle: Handle<S>) {
commands.insert_resource(DefaultAsset(handle));
}
}
fn world<'w>(lua: &Lua) -> std::result::Result<&'w mut World, mlua::Error> {
lua.app_data_mut::<&World>()
.ok_or_else(Error("Direct world access not available here!".into()))
.map_err(LuaError::external).deref_mut()
}
impl LuaUserData for Transform {
}
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) {
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) {
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 {
fn world_access(&self, lua: &Lua) -> std::result::Result<EntityWorldMut, mlua::Error> {
world(lua)?.get_entity_mut(self.0).map_err(LuaError::external)
}
}
pub fn setup(app: &mut App) {
}

16
src/duck/mod.rs Normal file
View File

@@ -0,0 +1,16 @@
use bevy::prelude::*;
pub mod part;
pub mod script;
pub mod instance;
pub struct DuckPlugin;
impl Plugin for DuckPlugin {
fn build(&self, app: &mut App) {
instance::setup(app);
part::setup(app);
script::setup(app);
instance::setup(app);
}
}

48
src/duck/part.rs Normal file
View File

@@ -0,0 +1,48 @@
use std::marker::PhantomData;
use bevy::prelude::*;
use mlua::{UserDataFields, UserDataMethods};
use mlua::prelude::{LuaUserDataFields, LuaUserDataMethods};
use crate::duck::instance::*;
pub struct Part;
impl Class for Part {
fn add_to(entity: &mut EntityCommands) -> Instance {
let id = entity.id();
entity.commands().run_system_cached_with(move |
In(id): In<Entity>,
mut commands: Commands,
mesh: Res<DefaultAsset<Mesh>>,
mat: Res<DefaultAsset<StandardMaterial>>
| {
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()))
}
}
pub fn startup(
mut commands: Commands,
mut materials: ResMut<Assets<StandardMaterial>>,
mut mesh: ResMut<Assets<Mesh>>,
) {
DefaultAsset::new(&mut commands,mesh.add(Cuboid::new(2.,1.,4.)));
DefaultAsset::new(&mut commands,materials.add(StandardMaterial::from(Color::NONE)));
}
pub fn setup(app: &mut App) {
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);
}

118
src/duck/script.rs Normal file
View File

@@ -0,0 +1,118 @@
use bevy::asset::AsyncReadExt;
use bevy::prelude::*;
use bevy::asset::{io::Reader, AssetLoader, LoadContext};
use mlua::prelude::*;
use mlua::Value;
use thiserror::Error;
use crate::duck::instance::*;
#[derive(Asset, TypePath, Debug)]
pub struct ScriptAsset {
pub source: String,
pub path: String
}
#[non_exhaustive]
#[derive(Debug, Error)]
enum ScriptAssetLoaderError {
#[error("Could not load script file: {0}")]
Io(#[from] std::io::Error)
}
#[derive(Default, TypePath)]
struct ScriptAssetLoader;
impl AssetLoader for ScriptAssetLoader {
type Asset = ScriptAsset;
type Settings = ();
type Error = ScriptAssetLoaderError;
async fn load(
&self,
reader: &mut dyn Reader,
_settings: &(),
_load_context: &mut LoadContext<'_>
) -> Result<Self::Asset, Self::Error> {
info!("Loading Script...");
let mut raw = String::new();
reader.read_to_string(&mut raw).await?;
Ok(ScriptAsset { source: raw, path: "unknown".into() })
}
}
#[derive(Component)]
struct ScriptHandle(Handle<ScriptAsset>);
pub struct Script;
impl Class for Script {
fn add_to(entity: &mut EntityCommands) -> Instance {
let id = entity.id();
entity.commands().run_system_cached_with(| // Get commands -> defer a system
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);
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) {
}
}
fn run(entity: &mut EntityCommands) {
let id= entity.id();
entity.commands().run_system_cached_with(| // Get commands -> defer a system:
In(id): In<Entity>,
query: Query<&ScriptHandle>, // A query to find it again
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)]
struct DefaultScriptContext(Lua);
pub fn startup(
mut commands: Commands,
mut script_assets: ResMut<Assets<ScriptAsset>>,
) {
DefaultAsset::new(&mut commands,script_assets.add(ScriptAsset {
source: "print(\"Hello world!\"); print(script)".into(),
path: "default.lua".into(),
}));
let lua = Lua::new();
lua.sandbox(true).unwrap();
commands.insert_resource(DefaultScriptContext(lua));
}
pub fn setup(app: &mut App) {
app.init_asset::<ScriptAsset>();
app.init_asset_loader::<ScriptAssetLoader>();
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::*;
mod duck;
use duck::DuckPlugin;
use duck::*;
fn main() {
App::new()
@@ -41,4 +41,6 @@ fn setup(
Camera3d::default(),
Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),
));
commands.new::<Script>();
commands.new::<Script>();
}