Skip to content

Commit

Permalink
feat: add misc. scripting updates needed for Jumpy. (#266)
Browse files Browse the repository at this point in the history
- add an `eq` function for comparing schemas in lua
- add a `schema_of` function to get the schema of a value in lua
- add a `schema:without()` function to power the ability to exclude
components from `entities:iter_with()`
- allow assigning lua and rust strings to `Ustr` refs
  • Loading branch information
zicklag committed Nov 24, 2023
1 parent a415b82 commit d939072
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 56 deletions.
4 changes: 3 additions & 1 deletion demos/scripting/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::sync::Arc;

use bones_bevy_renderer::BonesBevyRenderer;
use bones_framework::prelude::*;

Expand Down Expand Up @@ -42,7 +44,7 @@ fn launch_game_session(
// Install the plugin that will load our lua plugins and run them in the game session
.install_plugin(LuaPluginLoaderSessionPlugin(
// Tell it to install the lua plugins specified in our game meta
meta.plugins.iter().copied().collect(),
Arc::new(meta.plugins.iter().copied().collect()),
))
.add_startup_system(game_startup);
}
Expand Down
2 changes: 2 additions & 0 deletions framework_crates/bones_framework/src/render/sprite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::prelude::*;
/// Sprite session plugin.
pub fn sprite_plugin(session: &mut Session) {
Sprite::register_schema();
AtlasSprite::register_schema();
session.world.init_param::<Comp<Sprite>>();
}

Expand Down Expand Up @@ -87,6 +88,7 @@ pub struct Sprite {
/// Represents one or more [`Atlas`]s stacked on top of each other, and possibly animated through a
/// range of frames out of the atlas.
#[derive(Debug, Default, Clone, HasSchema)]
#[repr(C)]
pub struct AtlasSprite {
/// The sprite's color tint
pub color: Color,
Expand Down
6 changes: 3 additions & 3 deletions framework_crates/bones_scripting/src/lua.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,15 @@ pub fn lua_game_plugin(game: &mut Game) {
}

/// A [`SessionPlugin] that will run the provided lua plugins
pub struct LuaPluginLoaderSessionPlugin(pub Vec<Handle<LuaPlugin>>);
pub struct LuaPluginLoaderSessionPlugin(pub Arc<Vec<Handle<LuaPlugin>>>);

/// Resource containing the lua plugins that have been installed in this session.
#[derive(HasSchema, Deref, DerefMut, Default, Clone)]
pub struct LuaPlugins(Arc<Vec<Handle<LuaPlugin>>>);
pub struct LuaPlugins(pub Arc<Vec<Handle<LuaPlugin>>>);

impl SessionPlugin for LuaPluginLoaderSessionPlugin {
fn install(self, session: &mut Session) {
session.world.insert_resource(LuaPlugins(Arc::new(self.0)));
session.world.insert_resource(LuaPlugins(self.0));

for lua_stage in [
CoreStage::First,
Expand Down
31 changes: 3 additions & 28 deletions framework_crates/bones_scripting/src/lua/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,36 +38,11 @@ pub fn env(ctx: Context) -> Table {
env.set(ctx, "math", ctx.state.globals.get(ctx, "math"))
.unwrap();

let schema_fn = AnyCallback::from_fn(&ctx, move |ctx, _fuel, mut stack| {
let singletons = ctx.singletons();
let schema_metatable = singletons.get(ctx, schema::metatable);

let schema_name = stack.pop_front();
let Value::String(schema_name) = schema_name else {
return Err(anyhow::format_err!("Type error: expected string schema name").into());
};
let mut matches = SCHEMA_REGISTRY.schemas.iter().filter(|schema| {
schema.name.as_bytes() == schema_name.as_bytes()
|| schema.full_name.as_bytes() == schema_name.as_bytes()
});

if let Some(next_match) = matches.next() {
if matches.next().is_some() {
return Err(anyhow::format_err!("Found multiple schemas matching name.").into());
}

// TODO: setup `toString` implementation so that printing schemas gives more information.
let schema = AnyUserData::new_static(&ctx, next_match);
schema.set_metatable(&ctx, Some(schema_metatable));
stack.push_front(schema.into());
} else {
return Err(anyhow::format_err!("Schema not found: {schema_name}").into());
}

Ok(CallbackReturn::Return)
});
let schema_fn = ctx.singletons().get(ctx, schema::schema_fn);
env.set(ctx, "schema", schema_fn).unwrap();
env.set(ctx, "s", schema_fn).unwrap(); // Alias for schema
let schema_of_fn = ctx.singletons().get(ctx, schema::schema_of_fn);
env.set(ctx, "schema_of", schema_of_fn).unwrap();

WorldRef::default().add_to_env(ctx, env);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,16 @@ pub fn metatable(ctx: Context) -> Table {
let b = entity_ecsref.borrow();
let entity = *b.schema_ref()?.try_cast::<Entity>()?;

let b = value_ecsref.borrow();
let value = b.schema_ref()?;
let value = {
let b = value_ecsref.borrow();
let value = b.schema_ref()?;
value.clone_into_box()
};

world.with(|world| {
let store = world.components.get_by_schema(value.schema());
let mut store = store.borrow_mut();
store.insert_box(entity, value.clone_into_box());
store.insert_box(entity, value);
Ok::<_, anyhow::Error>(())
})?;

Expand Down
23 changes: 20 additions & 3 deletions framework_crates/bones_scripting/src/lua/bindings/ecsref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ pub fn metatable(ctx: Context) -> Table {
ctx,
"__index",
AnyCallback::from_fn(&ctx, move |ctx, _fuel, mut stack| {
let (this, key): (&EcsRef, lua::String) = stack.consume(ctx)?;
let (this, key): (&EcsRef, lua::Value) = stack.consume(ctx)?;

let mut newref = this.clone();
newref.path = ustr(&format!("{}.{key}", this.path));
Expand Down Expand Up @@ -434,8 +434,25 @@ pub fn metatable(ctx: Context) -> Table {
);
}
}
(PrimitiveRefMut::Opaque { .. }, Value::UserData(_)) => {
todo!("Opaque type assignment")
(PrimitiveRefMut::Opaque { mut schema_ref, .. }, value) => {
// Special handling for `Ustr`
if let Ok(ustr) = schema_ref.reborrow().try_cast_mut::<Ustr>() {
if let Value::String(s) = value {
*ustr = s.to_str()?.into()
} else if let Value::UserData(data) = value {
let ecsref = data.downcast_static::<EcsRef>()?;
let b = ecsref.borrow();
let value_ref = b.schema_ref()?;

if let Ok(value) = value_ref.try_cast::<Ustr>() {
*ustr = *value;
} else if let Ok(value) = value_ref.try_cast::<String>() {
*ustr = value.as_str().into();
}
}
} else {
todo!("Opaque type assignment")
}
}
_ => return Err(anyhow::format_err!("Invalid type").into()),
},
Expand Down
29 changes: 20 additions & 9 deletions framework_crates/bones_scripting/src/lua/bindings/entities.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use lua::Variadic;

use crate::prelude::bindings::schema::WithoutSchema;

use super::*;

pub fn entities_metatable(ctx: Context) -> Table {
Expand Down Expand Up @@ -57,25 +59,34 @@ pub fn entities_metatable(ctx: Context) -> Table {
let iter_with_callback = ctx.state.registry.stash(
&ctx,
AnyCallback::from_fn(&ctx, move |ctx, _fuel, mut stack| {
let (this, schemas): (&EcsRef, Variadic<Vec<AnyUserData>>) = stack.consume(ctx)?;
let (this, schema_args): (&EcsRef, Variadic<Vec<AnyUserData>>) = stack.consume(ctx)?;
let mut b = this.borrow_mut();
let entities = b.schema_ref_mut()?.cast_into_mut::<Entities>();
let schemas = schemas
.into_iter()
.map(|x| x.downcast_static::<&Schema>().map(|x| *x))
.collect::<Result<Vec<_>, _>>()?;
let world = ctx
.state
.globals
.get(ctx, "world")
.as_static_user_data::<WorldRef>()?;
let mut bitset = entities.bitset().clone();

let mut schemas = Vec::with_capacity(schema_args.len());
world.with(|world| {
for schema in &schemas {
let components = world.components.get_by_schema(schema);
let components = components.borrow();
bitset.bit_and(components.bitset());
for schema_arg in &schema_args {
if let Ok(schema) = schema_arg.downcast_static::<&Schema>() {
let components = world.components.get_by_schema(schema);
let components = components.borrow();
bitset.bit_and(components.bitset());
schemas.push(*schema);
} else if let Ok(without_schema) = schema_arg.downcast_static::<WithoutSchema>()
{
let components = world.components.get_by_schema(without_schema.0);
let components = components.borrow();
bitset.bit_and(components.bitset().clone().bit_not());
} else {
return Err(anyhow::format_err!(
"Invalid type for argument to `entities:iter_with()`: {schema_arg:?}"
));
}
}
Ok::<_, anyhow::Error>(())
})?;
Expand Down
98 changes: 89 additions & 9 deletions framework_crates/bones_scripting/src/lua/bindings/schema.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,56 @@
use super::*;

/// A wrapper around [`Schema`] that indicates that it should be excluded from, for example,
/// an `entities:iter_with()` lua call.
pub(super) struct WithoutSchema(pub &'static Schema);

pub fn schema_fn(ctx: Context) -> AnyCallback {
AnyCallback::from_fn(&ctx, move |ctx, _fuel, mut stack| {
let singletons = ctx.singletons();
let schema_metatable = singletons.get(ctx, schema::metatable);

let schema_name = stack.pop_front();
let Value::String(schema_name) = schema_name else {
return Err(anyhow::format_err!("Type error: expected string schema name").into());
};
let mut matches = SCHEMA_REGISTRY.schemas.iter().filter(|schema| {
schema.name.as_bytes() == schema_name.as_bytes()
|| schema.full_name.as_bytes() == schema_name.as_bytes()
});

if let Some(next_match) = matches.next() {
if matches.next().is_some() {
return Err(anyhow::format_err!("Found multiple schemas matching name.").into());
}

// TODO: setup `toString` implementation so that printing schemas gives more information.
let schema = AnyUserData::new_static(&ctx, next_match);
schema.set_metatable(&ctx, Some(schema_metatable));
stack.push_front(schema.into());
} else {
return Err(anyhow::format_err!("Schema not found: {schema_name}").into());
}

Ok(CallbackReturn::Return)
})
}

pub fn schema_of_fn(ctx: Context) -> AnyCallback {
AnyCallback::from_fn(&ctx, move |ctx, _fuel, mut stack| {
let singletons = ctx.singletons();
let schema_metatable = singletons.get(ctx, schema::metatable);

let ecsref: &EcsRef = stack.consume(ctx)?;
let schema = ecsref.borrow().schema_ref()?.schema();

let schema = AnyUserData::new_static(&ctx, schema);
schema.set_metatable(&ctx, Some(schema_metatable));
stack.replace(ctx, schema);

Ok(CallbackReturn::Return)
})
}

pub fn metatable(ctx: Context) -> Table {
let metatable = Table::new(&ctx);
metatable
Expand Down Expand Up @@ -32,6 +83,30 @@ pub fn metatable(ctx: Context) -> Table {
Ok(CallbackReturn::Return)
}),
);

let without_fn = ctx.state.registry.stash(
&ctx,
AnyCallback::from_fn(&ctx, move |ctx, _fuel, mut stack| {
let this: AnyUserData = stack.consume(ctx)?;
let this = this.downcast_static::<&Schema>()?;
stack.replace(ctx, AnyUserData::new_static(&ctx, WithoutSchema(this)));
Ok(CallbackReturn::Return)
}),
);

let eq_fn = ctx.state.registry.stash(
&ctx,
AnyCallback::from_fn(&ctx, move |ctx, _fuel, mut stack| {
let (this, other): (AnyUserData, AnyUserData) = stack.consume(ctx)?;
let (this, other) = (
this.downcast_static::<&Schema>()?,
other.downcast_static::<&Schema>()?,
);
stack.replace(ctx, this.id() == other.id());
Ok(CallbackReturn::Return)
}),
);

metatable
.set(
ctx,
Expand All @@ -41,19 +116,24 @@ pub fn metatable(ctx: Context) -> Table {
let this = this.downcast_static::<&Schema>()?;

match key.as_bytes() {
b"eq" => stack.replace(ctx, ctx.state.registry.fetch(&eq_fn)),
b"name" => {
stack.push_front(Value::String(piccolo::String::from_static(
&ctx,
this.name.as_bytes(),
)));
stack.replace(
ctx,
Value::String(piccolo::String::from_static(&ctx, this.name.as_bytes())),
);
}
b"full_name" => {
stack.push_front(Value::String(piccolo::String::from_static(
&ctx,
this.full_name.as_bytes(),
)));
stack.replace(
ctx,
Value::String(piccolo::String::from_static(
&ctx,
this.full_name.as_bytes(),
)),
);
}
b"create" => stack.push_front(ctx.state.registry.fetch(&create_fn).into()),
b"create" => stack.replace(ctx, ctx.state.registry.fetch(&create_fn)),
b"without" => stack.replace(ctx, ctx.state.registry.fetch(&without_fn)),
_ => (),
}

Expand Down

0 comments on commit d939072

Please sign in to comment.