Skip to content

Commit b865f8f

Browse files
authored
feat: add lua scripting support. (#252)
1 parent 642a1fb commit b865f8f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+4970
-1277
lines changed

Cargo.lock

+283-94
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

demos/features/assets/game.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
menu_script: ./menu.lua
12
menu_image: ./menu-image.png
23
sprite_demo: ./sprite/fractal.png
34
atlas_demo: ./atlas/atlas-demo.yaml

demos/features/assets/menu.lua

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
local menuData = world.resources:get(schema("MenuData"))
2+
3+
-- Increment the frame counter
4+
menuData.frame = menuData.frame + 1
5+
6+
if menuData.frame % 30 == 0 then
7+
info(menuData)
8+
end
9+

demos/features/src/main.rs

+31-3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ use bones_framework::prelude::*;
1414
// Allow asset to be loaded from "game.yaml" assets.
1515
#[type_data(metadata_asset("game"))]
1616
struct GameMeta {
17+
/// A lua script that will be run every frame on the menu.
18+
menu_script: Handle<LuaScript>,
1719
/// The image displayed on the menu.
1820
menu_image: Handle<Image>,
1921
/// The image for the sprite demo
@@ -79,6 +81,7 @@ struct TileMeta {
7981
idx: u32,
8082
}
8183

84+
/// Struct containing data that will be persisted with the storage API.
8285
#[derive(HasSchema, Default, Clone)]
8386
#[repr(C)]
8487
struct PersistedTextData(String);
@@ -88,8 +91,16 @@ fn main() {
8891
PersistedTextData::register_schema();
8992

9093
// Create a bones bevy renderer from our bones game
91-
BonesBevyRenderer::new(create_game())
92-
// Get a bevy app for running our game
94+
let mut renderer = BonesBevyRenderer::new(create_game());
95+
// Set the app namespace which will be used by the renderer to decide where to put
96+
// persistent storage files.
97+
renderer.app_namespace = (
98+
"org".into(),
99+
"fishfolk".into(),
100+
"bones.demo_features".into(),
101+
);
102+
// Get a bevy app for running our game
103+
renderer
93104
.app()
94105
// We can add our own bevy plugins now
95106
.add_plugins((FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin::default()))
@@ -119,13 +130,26 @@ pub fn create_game() -> Game {
119130
game
120131
}
121132

133+
/// Resource containing data that we will access from our menu lua script.
134+
#[derive(HasSchema, Default, Clone)]
135+
#[repr(C)]
136+
struct MenuData {
137+
/// The index of the frame that we are on.
138+
pub frame: u32,
139+
}
140+
122141
/// Menu plugin
123142
pub fn menu_plugin(session: &mut Session) {
124143
// Register our menu system
125144
session
126145
// Install the bones_framework default plugins for this session
127146
.install_plugin(DefaultSessionPlugin)
128-
// And add our systems.
147+
.world
148+
// Initialize our menu data resource
149+
.init_resource::<MenuData>();
150+
151+
// And add our systems.
152+
session
129153
.add_system_to_stage(Update, menu_system)
130154
.add_startup_system(menu_startup);
131155
}
@@ -150,7 +174,11 @@ fn menu_system(
150174
// Get the localization field from our `GameMeta`
151175
localization: Localization<GameMeta>,
152176
world: &World,
177+
lua_engine: Res<LuaEngine>,
153178
) {
179+
// Run our menu script.
180+
lua_engine.run_script_system(world, meta.menu_script);
181+
154182
// Render the menu.
155183
egui::CentralPanel::default()
156184
.frame(egui::Frame::none())

demos/scripting/Cargo.toml

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[package]
2+
name = "demo_scripting"
3+
edition.workspace = true
4+
version.workspace = true
5+
license.workspace = true
6+
publish = false
7+
8+
[dependencies]
9+
bones_framework = { path = "../../framework_crates/bones_framework" }
10+
bones_bevy_renderer = { path = "../../framework_crates/bones_bevy_renderer" }

demos/scripting/assets/game.yaml

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
plugins:
2+
- plugin1.plugin.lua
3+
version: 1
4+
info: ./info.yaml
5+
sprite: ./image.png

demos/scripting/assets/image.png

37.9 KB
Loading

demos/scripting/assets/info.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
name: Scripting Demo Game
2+
gravity: -9.8

demos/scripting/assets/pack.yaml

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
root: game.yaml
2+
schemas:
3+
- schemas/DemoSprite.yaml
+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
local Vec3 = s"Vec3"
2+
local Transform = s"Transform"
3+
local Sprite = s"Sprite"
4+
local Time = s"Time"
5+
local Entities = s"Entities"
6+
local DemoSprite = s"DemoSprite"
7+
8+
local function startup()
9+
local meta = assets.root
10+
local entities = resources:get(Entities)
11+
12+
local ent = entities:create()
13+
components:insert(ent, Transform:create())
14+
local sprite = Sprite:create()
15+
sprite.image = meta.sprite
16+
components:insert(ent, sprite)
17+
components:insert(ent, DemoSprite:create())
18+
end
19+
20+
local function update()
21+
local entities = resources:get(Entities)
22+
local time = resources:get(Time)
23+
24+
for ent, t, s in entities:iter_with(Transform, Sprite, DemoSprite) do
25+
t.translation.x = math.sin(time.elapsed_seconds * 2) * 100
26+
t.translation.y = math.sin(time.elapsed_seconds * 1.8) * 100
27+
end
28+
end
29+
30+
session:add_startup_system(startup)
31+
session:add_system_to_stage(CoreStage.Update, update)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# A marker type for our demo sprite.
2+
name: DemoSprite
3+
full_name: demo_scripting::DemoSprite
4+
kind: !Struct

demos/scripting/src/main.rs

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
use bones_bevy_renderer::BonesBevyRenderer;
2+
use bones_framework::prelude::*;
3+
4+
#[derive(HasSchema, Default, Clone)]
5+
#[type_data(metadata_asset("game"))]
6+
#[repr(C)]
7+
struct GameMeta {
8+
plugins: SVec<Handle<LuaPlugin>>,
9+
version: u32,
10+
sprite: Handle<Image>,
11+
info: Handle<GameInfoMeta>,
12+
}
13+
14+
#[derive(HasSchema, Default, Clone)]
15+
#[repr(C)]
16+
#[type_data(metadata_asset("info"))]
17+
struct GameInfoMeta {
18+
name: String,
19+
gravity: f32,
20+
}
21+
22+
#[derive(HasSchema, Default, Clone)]
23+
#[repr(C)]
24+
struct DemoData {
25+
name: String,
26+
age: f32,
27+
favorite_things: SVec<String>,
28+
attributes: SMap<String, f32>,
29+
best_friend: Maybe<String>,
30+
state: DemoState,
31+
}
32+
33+
#[derive(HasSchema, Default, Clone)]
34+
#[repr(C, u8)]
35+
pub enum DemoState {
36+
#[default]
37+
Ready,
38+
Thinking(f32),
39+
Finished {
40+
score: u32,
41+
},
42+
}
43+
44+
fn main() {
45+
let mut game = Game::new();
46+
game.install_plugin(DefaultGamePlugin);
47+
GameMeta::register_schema();
48+
DemoData::register_schema();
49+
50+
game.sessions
51+
.create("launch")
52+
.add_startup_system(launch_game_session);
53+
54+
let mut renderer = BonesBevyRenderer::new(game);
55+
renderer.app_namespace = (
56+
"org".into(),
57+
"fishfolk".into(),
58+
"bones.demo_scripting".into(),
59+
);
60+
renderer.app().run();
61+
}
62+
63+
fn launch_game_session(
64+
meta: Root<GameMeta>,
65+
mut sessions: ResMut<Sessions>,
66+
mut session_ops: ResMut<SessionOptions>,
67+
) {
68+
session_ops.delete = true;
69+
let game_session = sessions.create("game");
70+
game_session
71+
.install_plugin(DefaultSessionPlugin)
72+
// Install the plugin that will load our lua plugins and run them in the game session
73+
.install_plugin(LuaPluginLoaderSessionPlugin(
74+
// Tell it to install the lua plugins specified in our game meta
75+
meta.plugins.iter().copied().collect(),
76+
))
77+
.add_startup_system(game_startup);
78+
79+
game_session.world.insert_resource(DemoData {
80+
name: "default name".into(),
81+
age: 10.0,
82+
favorite_things: ["candy".into(), "rain".into()].into_iter().collect(),
83+
attributes: [("coolness".into(), 50.0), ("friendliness".into(), 10.57)]
84+
.into_iter()
85+
.collect(),
86+
best_friend: Some("Jane".into()).into(),
87+
state: DemoState::Thinking(20.),
88+
});
89+
}
90+
91+
fn game_startup(
92+
mut entities: ResMut<Entities>,
93+
mut transforms: CompMut<Transform>,
94+
mut cameras: CompMut<Camera>,
95+
) {
96+
spawn_default_camera(&mut entities, &mut transforms, &mut cameras);
97+
}

framework_crates/bones_asset/Cargo.toml

+6-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ default = []
1515

1616
[dependencies]
1717
bones_utils = { version = "0.3", path = "../bones_utils", features = ["serde"] }
18-
bones_schema = { version = "0.3", path = "../bones_schema" }
18+
bones_schema = { version = "0.3", path = "../bones_schema", features = ["serde"] }
1919

2020
serde = { version = "1.0", features = ["derive"] }
2121
sha2 = "0.10"
@@ -35,11 +35,15 @@ tracing = "0.1"
3535
bevy_tasks = "0.11"
3636
dashmap = "5.5"
3737
event-listener = "3.0"
38-
append-only-vec = "0.1"
38+
elsa = "1.9"
39+
append-only-vec = "0.1.3"
3940

4041
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
4142
notify = "6.0"
4243

44+
[target.'cfg(target_arch = "wasm32")'.dependencies]
45+
web-sys = { version = "0.3", features = ["console"] }
46+
4347
[dev-dependencies]
4448
bones_schema = { version = "0.3", path = "../bones_schema", features = ["glam"] }
4549
glam = "0.24"

framework_crates/bones_asset/src/asset.rs

+3-5
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,7 @@ pub struct AssetPack {
4343
pub game_version: VersionReq,
4444

4545
/// Schemas provided in the asset pack.
46-
pub schemas: HashMap<String, Schema>,
47-
/// Specify schemas to import from other asset packs.
48-
pub import_schemas: HashMap<String, SchemaPath>,
46+
pub schemas: Vec<&'static Schema>,
4947
/// The root asset for the asset pack.
5048
pub root: UntypedHandle,
5149
}
@@ -328,7 +326,7 @@ pub trait AssetLoader: Sync + Send + 'static {
328326
pub struct SchemaMetaAssetLoader(
329327
pub fn(
330328
ctx: &mut MetaAssetLoadCtx,
331-
ptr: SchemaRefMut<'_, '_>,
329+
ptr: SchemaRefMut<'_>,
332330
deserialzer: &mut dyn erased_serde::Deserializer,
333331
) -> anyhow::Result<()>,
334332
);
@@ -338,7 +336,7 @@ impl SchemaMetaAssetLoader {
338336
pub fn load<'a, 'de, D>(
339337
&self,
340338
ctx: &mut MetaAssetLoadCtx,
341-
ptr: SchemaRefMut<'a, 'a>,
339+
ptr: SchemaRefMut<'a>,
342340
deserializer: D,
343341
) -> Result<(), erased_serde::Error>
344342
where

framework_crates/bones_asset/src/handle.rs

+8-8
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,11 @@ unsafe impl<T: HasSchema> HasSchema for Handle<T> {
130130
schema: u128::schema(),
131131
}],
132132
}),
133-
clone_fn: Some(<Self as RawClone>::raw_clone),
133+
clone_fn: Some(<Self as RawClone>::raw_clone_cb()),
134134
drop_fn: None,
135-
default_fn: Some(<Self as RawDefault>::raw_default),
136-
eq_fn: Some(<Self as RawEq>::raw_eq),
137-
hash_fn: Some(<Self as RawHash>::raw_hash),
135+
default_fn: Some(<Self as RawDefault>::raw_default_cb()),
136+
eq_fn: Some(<Self as RawEq>::raw_eq_cb()),
137+
hash_fn: Some(<Self as RawHash>::raw_hash_cb()),
138138
type_data: {
139139
let td = bones_schema::alloc::TypeDatas::default();
140140
td.insert(SchemaAssetHandle {
@@ -179,11 +179,11 @@ unsafe impl HasSchema for UntypedHandle {
179179
schema: u128::schema(),
180180
}],
181181
}),
182-
clone_fn: Some(<Self as RawClone>::raw_clone),
182+
clone_fn: Some(<Self as RawClone>::raw_clone_cb()),
183183
drop_fn: None,
184-
default_fn: Some(<Self as RawDefault>::raw_default),
185-
eq_fn: Some(<Self as RawEq>::raw_eq),
186-
hash_fn: Some(<Self as RawHash>::raw_hash),
184+
default_fn: Some(<Self as RawDefault>::raw_default_cb()),
185+
eq_fn: Some(<Self as RawEq>::raw_eq_cb()),
186+
hash_fn: Some(<Self as RawHash>::raw_hash_cb()),
187187
type_data: {
188188
let td = bones_schema::alloc::TypeDatas::default();
189189
td.insert(SchemaAssetHandle { schema: None }).unwrap();

framework_crates/bones_asset/src/lib.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ impl<T> From<Option<T>> for Maybe<T> {
7272

7373
fn maybe_loader(
7474
ctx: &mut MetaAssetLoadCtx,
75-
ptr: SchemaRefMut<'_, '_>,
75+
ptr: SchemaRefMut<'_>,
7676
deserialzer: &mut dyn erased_serde::Deserializer,
7777
) -> anyhow::Result<()> {
7878
deserialzer.deserialize_option(MaybeVisitor { ctx, ptr })?;
@@ -82,7 +82,7 @@ fn maybe_loader(
8282

8383
struct MaybeVisitor<'a, 'srv> {
8484
ctx: &'a mut MetaAssetLoadCtx<'srv>,
85-
ptr: SchemaRefMut<'a, 'a>,
85+
ptr: SchemaRefMut<'a>,
8686
}
8787

8888
impl<'a, 'srv, 'de> serde::de::Visitor<'de> for MaybeVisitor<'a, 'srv> {
@@ -112,7 +112,7 @@ impl<'a, 'srv, 'de> serde::de::Visitor<'de> for MaybeVisitor<'a, 'srv> {
112112
// Write the enum discriminant for the `Set` variant
113113
// SOUND: we know the discriminant due to the `#[repr(C, u8)]` annotation.
114114
unsafe {
115-
self.ptr.as_ptr().write(1);
115+
self.ptr.as_ptr().cast::<u8>().write(1);
116116
}
117117

118118
// Get the pointer to the enum value

0 commit comments

Comments
 (0)