Skip to content

feat: ✨ Parallelizable Script Systems with Res and Query parameters & Schedule debugging utilities #361

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 31 commits into from
Mar 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
3461ecf
initial work :construction:
makspll Mar 5, 2025
deddd15
WIP
makspll Mar 10, 2025
3db90fd
AHA
makspll Mar 10, 2025
ec7fe5b
typo
makspll Mar 10, 2025
7790e00
dont make script systems exclusive by default
makspll Mar 10, 2025
8e6250c
remove hardcoded test name
makspll Mar 10, 2025
8330c2f
simple tests
makspll Mar 10, 2025
2a0bdc2
make script exclusive systems actually exclusive
makspll Mar 10, 2025
7bebdc4
add test for parameters
makspll Mar 10, 2025
ce5e43b
expand ordering test to include ordering between script systems
makspll Mar 11, 2025
ad9ecb9
improve dependency graph dot rendering and add more tests
makspll Mar 11, 2025
e1f3bcf
convert rest of tests to rhai
makspll Mar 11, 2025
eb644ca
make system queries work
makspll Mar 11, 2025
c0b5f98
initial work :construction:
makspll Mar 5, 2025
7c85b3c
WIP
makspll Mar 10, 2025
1983185
AHA
makspll Mar 10, 2025
69b7ec2
typo
makspll Mar 10, 2025
8c15c94
dont make script systems exclusive by default
makspll Mar 10, 2025
2baa24e
remove hardcoded test name
makspll Mar 10, 2025
de72f07
simple tests
makspll Mar 10, 2025
18bff16
make script exclusive systems actually exclusive
makspll Mar 10, 2025
6790f4f
add test for parameters
makspll Mar 10, 2025
c3c925b
expand ordering test to include ordering between script systems
makspll Mar 11, 2025
002825c
improve dependency graph dot rendering and add more tests
makspll Mar 11, 2025
07199cb
convert rest of tests to rhai
makspll Mar 11, 2025
7d9df09
make system queries work
makspll Mar 11, 2025
61c3321
Merge branch 'feat/script-systems' of github.com:makspll/bevy_mod_scr…
makspll Mar 11, 2025
d26080b
formatting
makspll Mar 11, 2025
f3c2641
Update script_system.rs
makspll Mar 12, 2025
59d65f8
fix test
makspll Mar 12, 2025
a0addb2
Merge branch 'feat/script-systems' of github.com:makspll/bevy_mod_scr…
makspll Mar 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ members = [
"crates/ladfile",
"crates/lad_backends/mdbook_lad_preprocessor",
"crates/ladfile_builder",
"crates/bevy_system_reflection",
]
resolver = "2"
exclude = ["crates/bevy_api_gen", "crates/macro_tests"]
Expand Down
240 changes: 240 additions & 0 deletions assets/lua_to_rhai_test_conversion_prompt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
// perfect application of AI
// helps proompting LLMs to do good for this project

# Convert lua to rhai script
Convert the current test to a rhai test. below are examples and instructions on the conversions necessary:

## Dynamic function calls
Functions which are not native to rhai MUST be explicitly called using the below syntax:

```rhai
type.function.call(arguments...)
```

native rhai functions MUST NOT be converted using the syntax above.

Below is a list of some of the native functions their argument names and some constants available in rhai:

```csv
function,arguments
is_odd,
is_even,
min,"a, b"
max,"a, b"
to_float,
to_decimal,
abs,value
sign,value
is_zero,
sin,angle
cos,angle
tan,angle
sinh,angle
cosh,angle
tanh,angle
hypot,"x, y"
asin,value
acos,value
atan,value
atan,"x, y"
asinh,value
acosh,value
atanh,value
sqrt,value
exp,value
ln,value
log,value
floor,
ceiling,
round,
round,decimal_points
int,
fraction,
round_up,decimal_points
round_down,decimal_points
round_half_up,decimal_points
round_half_down,decimal_points
to_int,
to_decimal,
to_float,
to_degrees,
to_radians,
is_nan,
is_finite,
is_infinite,
parse_int,"string, [radix]"
parse_float,string
parse_decimal,string
to_binary,value
to_octal,value
to_hex,value
PI,
E,
```

## Operators
Operators are different in lua and rhai, below is a list of operators supported:

```
Operators Assignment operators Supported types
(see standard types)
+, +=

INT
FLOAT (if not no_float)
Decimal (requires decimal)
char
string

-, *, /, %, **, -=, *=, /=, %=, **=

INT
FLOAT (if not no_float)
Decimal (requires decimal)

<<, >> <<=, >>=

INT

&, |, ^ &=, |=, ^=

INT (bit-wise)
bool (non-short-circuiting)

&&, ||

bool (short-circuits)

==, !=

INT
FLOAT (if not no_float)
Decimal (requires decimal)
bool
char
string
BLOB
numeric range
()

>, >=, <, <=

INT
FLOAT (if not no_float)
Decimal (requires decimal)
char
string
()
```

## Function syntax
Functions in rhai look like this:

```rhai
fn function_name(arg1, arg2) {
return value;
}
```

## Semicolons
Every statement must end in a semicolon


Below is a new section on Rhai strings that you can add to the prompt:

## Rhai Strings

Rhai supports different string types such as raw strings (enclosed by matching `#` and double-quotes), multi-line literal strings (enclosed by backticks to preserve exact formatting), and strings with interpolation (using `${…}` inside multi-line literals). These variants allow you to easily include complex content like newlines, quotes, and even embedded expressions while keeping the original formatting. Here are three examples:

````rhai
// Raw string example:
let raw_str = #"Hello, raw string! \n No escape sequences here."#;
````

````rhai
// Multi-line literal string example:
let multi_line = `
This is a multi-line literal string,
which preserves whitespaces, newlines, and "quotes" exactly.
`;
````

````rhai
// String interpolation example:
let value = 42;
let interpolated = `The answer is ${value}, which is computed dynamically.`;
````

## Null Checks
null checks can be performed by checking `type_of(value) == "()"`

## Examples
Below is an example lua test and its equivalent rhai script:

### Lua
```lua
local entity_a = world.spawn()
local entity_b = world.spawn()
local entity_c = world.spawn()
local entity_d = world._get_entity_with_test_component("CompWithFromWorldAndComponentData")

local component_with = world.get_type_by_name("CompWithFromWorldAndComponentData")
local component_without = world.get_type_by_name("CompWithDefaultAndComponentData")

world.add_default_component(entity_a, component_with)
world.add_default_component(entity_b, component_with)
world.add_default_component(entity_c, component_with)

world.add_default_component(entity_b, component_without)

local found_entities = {}
for i,result in pairs(world.query():component(component_with):without(component_without):build()) do
table.insert(found_entities, result:entity())
end

assert(#found_entities == 3, "Expected 3 entities, got " .. #found_entities)

expected_entities = {
entity_c,
entity_d,
entity_a,
}

for i, entity in ipairs(found_entities) do
assert(entity:index() == expected_entities[i]:index(), "Expected entity " .. expected_entities[i]:index() .. " but got " .. entity:index())
end
```

### Rhai
```rhai
let entity_a = world.spawn_.call();
let entity_b = world.spawn_.call();
let entity_c = world.spawn_.call();
let entity_d = world._get_entity_with_test_component.call("CompWithFromWorldAndComponentData");

let component_with = world.get_type_by_name.call("CompWithFromWorldAndComponentData");
let component_without = world.get_type_by_name.call("CompWithDefaultAndComponentData");

world.add_default_component.call(entity_a, component_with);
world.add_default_component.call(entity_b, component_with);
world.add_default_component.call(entity_c, component_with);

world.add_default_component.call(entity_b, component_without);

let found_entities = [];
for (result, i) in world.query.call().component.call(component_with).without.call(component_without).build.call() {
found_entities.push(result.entity.call());
}

assert(found_entities.len == 3, "Expected 3 entities, got " + found_entities.len);

let expected_entities = [
entity_d,
entity_a,
entity_c,
];

for (entity, i) in found_entities {
assert(entity.index.call() == expected_entities[i].index.call(), "Expected entity " + expected_entities[i].index.call() + " but got " + entity.index.call());
}
```
43 changes: 43 additions & 0 deletions assets/tests/add_system/added_systems_run_in_parallel.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@

function on_test()
local post_update_schedule = world.get_schedule_by_name("PostUpdate")

local test_system = post_update_schedule:get_system_by_name("on_test_post_update")

local system_a = world.add_system(
post_update_schedule,
system_builder("custom_system_a", script_id)
:after(test_system)
)

local system_b = world.add_system(
post_update_schedule,
system_builder("custom_system_b", script_id)
:after(test_system)
)

-- generate a schedule graph and verify it's what we expect
local dot_graph = post_update_schedule:render_dot()

local expected_dot_graph = [[
digraph {
node_0 [label="bevy_mod_scripting_core::bindings::allocator::garbage_collector"];
node_1 [label="on_test_post_update"];
node_2 [label="script_integration_test_harness::dummy_before_post_update_system"];
node_3 [label="script_integration_test_harness::dummy_post_update_system"];
node_4 [label="custom_system_a"];
node_5 [label="custom_system_b"];
node_6 [label="SystemSet GarbageCollection"];
node_7 [label="SystemSet ScriptSystem(custom_system_a)"];
node_8 [label="SystemSet ScriptSystem(custom_system_b)"];
node_0 -> node_6 [color=red, label="child of", arrowhead=diamond];
node_4 -> node_7 [color=red, label="child of", arrowhead=diamond];
node_5 -> node_8 [color=red, label="child of", arrowhead=diamond];
node_1 -> node_4 [color=blue, label="runs before", arrowhead=normal];
node_1 -> node_5 [color=blue, label="runs before", arrowhead=normal];
node_2 -> node_3 [color=blue, label="runs before", arrowhead=normal];
}
]]

assert_str_eq(dot_graph, expected_dot_graph, "Expected the schedule graph to match the expected graph")
end
41 changes: 41 additions & 0 deletions assets/tests/add_system/added_systems_run_in_parallel.rhai
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
fn on_test() {
let post_update_schedule = world.get_schedule_by_name.call("PostUpdate");

let test_system = post_update_schedule.get_system_by_name.call("on_test_post_update");

let system_a = world.add_system.call(
post_update_schedule,
system_builder.call("custom_system_a", script_id)
.after.call(test_system)
);

let system_b = world.add_system.call(
post_update_schedule,
system_builder.call("custom_system_b", script_id)
.after.call(test_system)
);

// generate a schedule graph and verify it's what we expect
let dot_graph = post_update_schedule.render_dot.call();

let expected_dot_graph = `
digraph {
node_0 [label="bevy_mod_scripting_core::bindings::allocator::garbage_collector"];
node_1 [label="on_test_post_update"];
node_2 [label="script_integration_test_harness::dummy_before_post_update_system"];
node_3 [label="script_integration_test_harness::dummy_post_update_system"];
node_4 [label="custom_system_a"];
node_5 [label="custom_system_b"];
node_6 [label="SystemSet GarbageCollection"];
node_7 [label="SystemSet ScriptSystem(custom_system_a)"];
node_8 [label="SystemSet ScriptSystem(custom_system_b)"];
node_0 -> node_6 [color=red, label="child of", arrowhead=diamond];
node_4 -> node_7 [color=red, label="child of", arrowhead=diamond];
node_5 -> node_8 [color=red, label="child of", arrowhead=diamond];
node_1 -> node_4 [color=blue, label="runs before", arrowhead=normal];
node_1 -> node_5 [color=blue, label="runs before", arrowhead=normal];
node_2 -> node_3 [color=blue, label="runs before", arrowhead=normal];
}`;

assert_str_eq.call(dot_graph, expected_dot_graph, "Expected the schedule graph to match the expected graph");
}
17 changes: 15 additions & 2 deletions assets/tests/add_system/adds_system_in_correct_order.lua
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ function on_test()
system_builder("custom_system_before", script_id)
:before(test_system)
)

local script_system_between = world.add_system(
post_update_schedule,
system_builder("custom_system_between", script_id)
:after(test_system)
:before(system_after)
)
end


Expand All @@ -39,10 +46,16 @@ function custom_system_after()
runs[#runs + 1] = "custom_system_after"
end

function custom_system_between()
print("custom_system_between")
runs[#runs + 1] = "custom_system_between"
end

-- runs in the `Last` bevy schedule
function on_test_last()
assert(#runs == 3, "Expected 3 runs, got: " .. #runs)
assert(#runs == 4, "Expected 4 runs, got: " .. #runs)
assert(runs[1] == "custom_system_before", "Expected custom_system_before to run first, got: " .. runs[1])
assert(runs[2] == "on_test_post_update", "Expected on_test_post_update to run second, got: " .. runs[2])
assert(runs[3] == "custom_system_after", "Expected custom_system_after to run third, got: " .. runs[3])
assert(runs[3] == "custom_system_between", "Expected custom_system_between to run third, got: " .. runs[3])
assert(runs[4] == "custom_system_after", "Expected custom_system_after to run second, got: " .. runs[4])
end
Loading
Loading