Skip to content

Commit bfa0498

Browse files
authored
feat: Add GlobalNamespace::system_builder, World::add_system and allow dynamic system creation (#335)
* WIP script systems * make ordering work with rust systems * add comment
1 parent 69d146d commit bfa0498

File tree

29 files changed

+1298
-254
lines changed

29 files changed

+1298
-254
lines changed

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ features = ["lua54", "rhai"]
2323
default = ["core_functions", "bevy_bindings"]
2424

2525
## lua
26-
lua = ["bevy_mod_scripting_lua"]
26+
lua = ["bevy_mod_scripting_lua", "bevy_mod_scripting_functions/lua_bindings"]
2727
# one of these must be selected
2828
lua51 = ["bevy_mod_scripting_lua/lua51", "lua"]
2929
lua52 = ["bevy_mod_scripting_lua/lua52", "lua"]
@@ -45,7 +45,7 @@ mlua_async = ["bevy_mod_scripting_lua?/mlua_async"]
4545

4646

4747
## rhai
48-
rhai = ["bevy_mod_scripting_rhai"]
48+
rhai = ["bevy_mod_scripting_rhai", "bevy_mod_scripting_functions/rhai_bindings"]
4949

5050
## rune
5151
# rune = ["bevy_mod_scripting_rune"]
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
-- add two systems, one before and one after the existing `on_test_post_update` callback, then assert all systems have run
2+
-- in the `on_test_last` callback
3+
4+
local runs = {}
5+
6+
-- runs on `Update`
7+
function on_test()
8+
local post_update_schedule = world.get_schedule_by_name("PostUpdate")
9+
10+
local test_system = post_update_schedule:get_system_by_name("on_test_post_update")
11+
12+
local system_after = world.add_system(
13+
post_update_schedule,
14+
system_builder("custom_system_after", script_id)
15+
:after(test_system)
16+
)
17+
18+
local system_before = world.add_system(
19+
post_update_schedule,
20+
system_builder("custom_system_before", script_id)
21+
:before(test_system)
22+
)
23+
end
24+
25+
26+
function custom_system_before()
27+
print("custom_system_before")
28+
runs[#runs + 1] = "custom_system_before"
29+
end
30+
31+
-- runs on post_update
32+
function on_test_post_update()
33+
print("on_test_post_update")
34+
runs[#runs + 1] = "on_test_post_update"
35+
end
36+
37+
function custom_system_after()
38+
print("custom_system_after")
39+
runs[#runs + 1] = "custom_system_after"
40+
end
41+
42+
-- runs in the `Last` bevy schedule
43+
function on_test_last()
44+
assert(#runs == 3, "Expected 3 runs, got: " .. #runs)
45+
assert(runs[1] == "custom_system_before", "Expected custom_system_before to run first, got: " .. runs[1])
46+
assert(runs[2] == "on_test_post_update", "Expected on_test_post_update to run second, got: " .. runs[2])
47+
assert(runs[3] == "custom_system_after", "Expected custom_system_after to run third, got: " .. runs[3])
48+
end
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
let runs = [];
2+
3+
fn on_test() {
4+
let post_update_schedule = world.get_schedule_by_name.call("PostUpdate");
5+
let test_system = post_update_schedule.get_system_by_name.call("on_test_post_update");
6+
7+
let builder_after = system_builder.call("custom_system_after", script_id).after.call(test_system);
8+
let system_after = world.add_system.call(post_update_schedule, builder_after);
9+
10+
let builder_before = system_builder.call("custom_system_before", script_id).before.call(test_system);
11+
let system_before = world.add_system.call(post_update_schedule, builder_before);
12+
}
13+
14+
fn custom_system_before() {
15+
print("custom_system_before");
16+
runs.push("custom_system_before");
17+
}
18+
19+
fn on_test_post_update() {
20+
print("on_test_post_update");
21+
runs.push("on_test_post_update");
22+
}
23+
24+
fn custom_system_after() {
25+
print("custom_system_after");
26+
runs.push("custom_system_after");
27+
}
28+
29+
fn on_test_last() {
30+
assert(runs.len() == 3, "Expected 3 runs, got: " + runs.len().to_string());
31+
assert(runs[0] == "custom_system_before", "Expected custom_system_before to run first, got: " + runs[0]);
32+
assert(runs[1] == "on_test_post_update", "Expected on_test_post_update to run second, got: " + runs[1]);
33+
assert(runs[2] == "custom_system_after", "Expected custom_system_after to run third, got: " + runs[2]);
34+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
assert(world.get_schedule_by_name("Update") ~= nil, "Schedule not found under short identifier")
2+
assert(world.get_schedule_by_name("bevy_app::main_schedule::Update") ~= nil, "Schedule not found under long identifier")
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
assert(type_of(world.get_schedule_by_name.call("Update")) != "()", "Schedule not found under short identifier");
2+
assert(type_of(world.get_schedule_by_name.call("bevy_app::main_schedule::Update")) != "()", "Schedule not found under long identifier");

assets/tests/rhai_tests.rs

Lines changed: 0 additions & 144 deletions
This file was deleted.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
function on_test()
2+
local startup_schedule = world.get_schedule_by_name("Startup")
3+
4+
5+
local expected_systems = {
6+
"dummy_startup_system",
7+
}
8+
9+
for i, system in ipairs(expected_systems) do
10+
local found_system = startup_schedule:get_system_by_name(system)
11+
assert(found_system ~= nil, "Expected system not found: " .. system)
12+
end
13+
end
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
fn on_test() {
2+
let startup_schedule = world.get_schedule_by_name.call("Startup");
3+
4+
5+
let expected_systems = [
6+
"dummy_startup_system"
7+
];
8+
9+
for system in expected_systems {
10+
let found_system = startup_schedule.get_system_by_name.call(system);
11+
assert(type_of(found_system) != "()", "Expected system not found: " + system);
12+
}
13+
}

crates/bevy_mod_scripting_core/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ derivative = "2.2"
4141
profiling = { workspace = true }
4242
bevy_mod_scripting_derive = { workspace = true }
4343
fixedbitset = "0.5"
44+
petgraph = "0.6"
4445

4546
[dev-dependencies]
4647
test_utils = { workspace = true }

crates/bevy_mod_scripting_core/src/bindings/function/script_function.rs

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Implementations of the [`ScriptFunction`] and [`ScriptFunctionMut`] traits for functions with up to 13 arguments.
22
33
use super::{from::FromScript, into::IntoScript, namespace::Namespace};
4+
use crate::asset::Language;
45
use crate::bindings::function::arg_meta::ArgMeta;
56
use crate::docgen::info::{FunctionInfo, GetFunctionInfo};
67
use crate::{
@@ -38,24 +39,30 @@ pub trait ScriptFunctionMut<'env, Marker> {
3839

3940
/// The caller context when calling a script function.
4041
/// Functions can choose to react to caller preferences such as converting 1-indexed numbers to 0-indexed numbers
41-
#[derive(Clone, Copy, Debug, Reflect, Default)]
42+
#[derive(Clone, Debug, Reflect)]
4243
#[reflect(opaque)]
4344
pub struct FunctionCallContext {
44-
/// Whether the caller uses 1-indexing on all indexes and expects 0-indexing conversions to be performed.
45-
pub convert_to_0_indexed: bool,
45+
language: Language,
4646
}
4747
impl FunctionCallContext {
4848
/// Create a new FunctionCallContext with the given 1-indexing conversion preference
49-
pub fn new(convert_to_0_indexed: bool) -> Self {
50-
Self {
51-
convert_to_0_indexed,
52-
}
49+
pub const fn new(language: Language) -> Self {
50+
Self { language }
5351
}
5452

5553
/// Tries to access the world, returning an error if the world is not available
5654
pub fn world<'l>(&self) -> Result<WorldGuard<'l>, InteropError> {
5755
ThreadWorldContainer.try_get_world()
5856
}
57+
/// Whether the caller uses 1-indexing on all indexes and expects 0-indexing conversions to be performed.
58+
pub fn convert_to_0_indexed(&self) -> bool {
59+
matches!(&self.language, Language::Lua)
60+
}
61+
62+
/// Gets the scripting language of the caller
63+
pub fn language(&self) -> Language {
64+
self.language.clone()
65+
}
5966
}
6067

6168
#[derive(Clone, Reflect)]
@@ -589,7 +596,7 @@ macro_rules! impl_script_function {
589596
let received_args_len = args.len();
590597
let expected_arg_count = count!($($param )*);
591598

592-
$( let $context = caller_context; )?
599+
$( let $context = caller_context.clone(); )?
593600
let world = caller_context.world()?;
594601
// Safety: we're not holding any references to the world, the arguments which might have aliased will always be dropped
595602
let ret: Result<ScriptValue, InteropError> = unsafe {
@@ -672,7 +679,10 @@ mod test {
672679

673680
with_local_world(|| {
674681
let out = script_function
675-
.call(vec![ScriptValue::from(1)], FunctionCallContext::default())
682+
.call(
683+
vec![ScriptValue::from(1)],
684+
FunctionCallContext::new(Language::Lua),
685+
)
676686
.unwrap();
677687

678688
assert_eq!(out, ScriptValue::from(1));
@@ -685,8 +695,10 @@ mod test {
685695
let script_function = fn_.into_dynamic_script_function().with_name("my_fn");
686696

687697
with_local_world(|| {
688-
let out =
689-
script_function.call(vec![ScriptValue::from(1)], FunctionCallContext::default());
698+
let out = script_function.call(
699+
vec![ScriptValue::from(1)],
700+
FunctionCallContext::new(Language::Lua),
701+
);
690702

691703
assert!(out.is_err());
692704
assert_eq!(
@@ -709,11 +721,13 @@ mod test {
709721
let script_function = fn_.into_dynamic_script_function().with_name("my_fn");
710722

711723
with_local_world(|| {
712-
let out =
713-
script_function.call(vec![ScriptValue::from(1)], FunctionCallContext::default());
724+
let out = script_function.call(
725+
vec![ScriptValue::from(1)],
726+
FunctionCallContext::new(Language::Lua),
727+
);
714728

715729
assert!(out.is_err());
716-
let world = FunctionCallContext::default().world().unwrap();
730+
let world = FunctionCallContext::new(Language::Lua).world().unwrap();
717731
// assert no access is held
718732
assert!(world.list_accesses().is_empty());
719733
});

crates/bevy_mod_scripting_core/src/bindings/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ pub mod function;
66
pub mod pretty_print;
77
pub mod query;
88
pub mod reference;
9+
pub mod schedule;
910
pub mod script_value;
1011
pub mod world;
1112

0 commit comments

Comments
 (0)