Skip to content

Commit 06c1537

Browse files
authored
feat: Add initial benchmarks, integrate them into CI & add getters/settters for Scripts resource (#381)
# Summary - Moves tests to run centrally in the workspace tests directory - Refactors test utilities to share a lot of the test discovery and running code between languages and benchmarks - Adds criterion benchmarks - Adds CI for snapshoting performance from main to `bencher.dev`
1 parent 7a39963 commit 06c1537

File tree

19 files changed

+629
-348
lines changed

19 files changed

+629
-348
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
on:
2+
push:
3+
branches: main
4+
5+
jobs:
6+
benchmark_base_branch:
7+
name: Continuous Benchmarking with Bencher
8+
permissions:
9+
checks: write
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v4
13+
- name: Run Xtask initializer
14+
run: |
15+
cargo xtask init
16+
- uses: bencherdev/bencher@main
17+
- name: Track base branch benchmarks with Bencher
18+
run: |
19+
bencher run \
20+
--project bms \
21+
--token '${{ secrets.BENCHER_API_TOKEN }}' \
22+
--branch main \
23+
--testbed ubuntu-latest \
24+
--threshold-measure latency \
25+
--threshold-test t_test \
26+
--threshold-max-sample-size 64 \
27+
--threshold-upper-boundary 0.99 \
28+
--thresholds-reset \
29+
--err \
30+
--adapter json \
31+
--github-actions '${{ secrets.GITHUB_TOKEN }}' \
32+
bencher run --adapter rust_criterion "cargo bench --features lua54"

Cargo.toml

+16-2
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@ features = ["lua54", "rhai"]
2222
[features]
2323
default = ["core_functions", "bevy_bindings"]
2424

25-
## lua
26-
lua = ["bevy_mod_scripting_lua", "bevy_mod_scripting_functions/lua_bindings"]
25+
lua = [
26+
"bevy_mod_scripting_lua",
27+
"bevy_mod_scripting_functions/lua_bindings",
28+
] ## lua
2729
# one of these must be selected
2830
lua51 = ["bevy_mod_scripting_lua/lua51", "lua"]
2931
lua52 = ["bevy_mod_scripting_lua/lua52", "lua"]
@@ -76,8 +78,12 @@ clap = { version = "4.1", features = ["derive"] }
7678
rand = "0.8.5"
7779
bevy_console = "0.13"
7880
# rhai-rand = "0.1"
81+
criterion = { version = "0.5" }
7982
ansi-parser = "0.9"
8083
ladfile_builder = { path = "crates/ladfile_builder", version = "0.2.6" }
84+
script_integration_test_harness = { workspace = true }
85+
test_utils = { workspace = true }
86+
libtest-mimic = "0.8"
8187

8288
[workspace]
8389
members = [
@@ -149,3 +155,11 @@ todo = "deny"
149155

150156
[workspace.lints.rust]
151157
missing_docs = "deny"
158+
159+
[[bench]]
160+
name = "benchmarks"
161+
harness = false
162+
163+
[[test]]
164+
name = "script_tests"
165+
harness = false
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
local entity_with_component = world._get_entity_with_test_component("TestComponent")
2+
3+
function bench()
4+
local strings = world.get_component(entity_with_component, types.TestComponent).strings
5+
end
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
let entity_with_component = world._get_entity_with_test_component.call("TestComponent");
2+
3+
fn bench(){
4+
let strings = world.get_component.call(entity_with_component, types.TestComponent).strings;
5+
}

assets/benchmarks/component/get.lua

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
local entity_with_component = world._get_entity_with_test_component("TestComponent")
2+
3+
function bench()
4+
world.get_component(entity_with_component, types.TestComponent)
5+
end

assets/benchmarks/component/get.rhai

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
let entity_with_component = world._get_entity_with_test_component.call("TestComponent");
2+
3+
fn bench(){
4+
world.get_component.call(entity_with_component, types.TestComponent);
5+
}

benches/benchmarks.rs

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
use std::path::PathBuf;
2+
3+
use bevy::utils::HashMap;
4+
use criterion::{
5+
criterion_group, criterion_main, measurement::Measurement, BenchmarkGroup, Criterion,
6+
};
7+
use script_integration_test_harness::{run_lua_benchmark, run_rhai_benchmark};
8+
use test_utils::{discover_all_tests, Test};
9+
10+
extern crate bevy_mod_scripting;
11+
extern crate script_integration_test_harness;
12+
extern crate test_utils;
13+
14+
pub trait BenchmarkExecutor {
15+
fn benchmark_group(&self) -> String;
16+
fn benchmark_name(&self) -> String;
17+
fn execute<M: Measurement>(&self, criterion: &mut BenchmarkGroup<M>);
18+
}
19+
20+
impl BenchmarkExecutor for Test {
21+
fn benchmark_group(&self) -> String {
22+
// we want to use OS agnostic paths
23+
// use the file path from `benchmarks` onwards using folders as groupings
24+
// replace file separators with `/`
25+
// replace _ with spaces
26+
let path = self.path.to_string_lossy();
27+
let path = path.split("benchmarks").collect::<Vec<&str>>()[1]
28+
.replace(std::path::MAIN_SEPARATOR, "/");
29+
let first_folder = path.split("/").collect::<Vec<&str>>()[1];
30+
first_folder.replace("_", " ")
31+
}
32+
33+
fn benchmark_name(&self) -> String {
34+
// use just the file stem
35+
let name = self
36+
.path
37+
.file_stem()
38+
.unwrap()
39+
.to_string_lossy()
40+
.to_string()
41+
.replace("_", " ");
42+
43+
let language = self.kind.to_string();
44+
45+
format!("{name} {language}")
46+
}
47+
48+
fn execute<M: Measurement>(&self, criterion: &mut BenchmarkGroup<M>) {
49+
match self.kind {
50+
test_utils::TestKind::Lua => run_lua_benchmark(
51+
&self.path.to_string_lossy(),
52+
&self.benchmark_name(),
53+
criterion,
54+
)
55+
.expect("Benchmark failed"),
56+
test_utils::TestKind::Rhai => run_rhai_benchmark(
57+
&self.path.to_string_lossy(),
58+
&self.benchmark_name(),
59+
criterion,
60+
)
61+
.expect("benchmark failed"),
62+
}
63+
}
64+
}
65+
66+
fn script_benchmarks(criterion: &mut Criterion) {
67+
// find manifest dir
68+
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
69+
let tests = discover_all_tests(manifest_dir, |p| p.starts_with("benchmarks"));
70+
71+
// group by benchmark group
72+
let mut grouped: HashMap<String, Vec<Test>> =
73+
tests.into_iter().fold(HashMap::default(), |mut acc, t| {
74+
acc.entry(t.benchmark_group()).or_default().push(t);
75+
acc
76+
});
77+
78+
// sort within groups by benchmark name
79+
for (_, tests) in grouped.iter_mut() {
80+
tests.sort_by_key(|a| a.benchmark_name());
81+
}
82+
83+
for (group, tests) in grouped {
84+
let mut benchmark_group = criterion.benchmark_group(group);
85+
86+
for t in tests {
87+
t.execute(&mut benchmark_group);
88+
}
89+
90+
benchmark_group.finish();
91+
}
92+
}
93+
94+
criterion_group!(benches, script_benchmarks);
95+
criterion_main!(benches);

crates/bevy_mod_scripting_core/src/script.rs

+38
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,44 @@ pub struct Scripts<P: IntoScriptPluginParams> {
3939
pub(crate) scripts: HashMap<ScriptId, Script<P>>,
4040
}
4141

42+
impl<P: IntoScriptPluginParams> Scripts<P> {
43+
/// Inserts a script into the collection
44+
pub fn insert(&mut self, script: Script<P>) {
45+
self.scripts.insert(script.id.clone(), script);
46+
}
47+
48+
/// Removes a script from the collection, returning `true` if the script was in the collection, `false` otherwise
49+
pub fn remove<S: Into<ScriptId>>(&mut self, script: S) -> bool {
50+
self.scripts.remove(&script.into()).is_some()
51+
}
52+
53+
/// Checks if a script is in the collection
54+
/// Returns `true` if the script is in the collection, `false` otherwise
55+
pub fn contains<S: Into<ScriptId>>(&self, script: S) -> bool {
56+
self.scripts.contains_key(&script.into())
57+
}
58+
59+
/// Returns a reference to the script with the given id
60+
pub fn get<S: Into<ScriptId>>(&self, script: S) -> Option<&Script<P>> {
61+
self.scripts.get(&script.into())
62+
}
63+
64+
/// Returns a mutable reference to the script with the given id
65+
pub fn get_mut<S: Into<ScriptId>>(&mut self, script: S) -> Option<&mut Script<P>> {
66+
self.scripts.get_mut(&script.into())
67+
}
68+
69+
/// Returns an iterator over the scripts
70+
pub fn iter(&self) -> impl Iterator<Item = &Script<P>> {
71+
self.scripts.values()
72+
}
73+
74+
/// Returns a mutable iterator over the scripts
75+
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Script<P>> {
76+
self.scripts.values_mut()
77+
}
78+
}
79+
4280
impl<P: IntoScriptPluginParams> Default for Scripts<P> {
4381
fn default() -> Self {
4482
Self {

crates/languages/bevy_mod_scripting_lua/Cargo.toml

-9
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,5 @@ smol_str = "0.2.2"
4646
smallvec = "1.13"
4747
profiling = { workspace = true }
4848

49-
[dev-dependencies]
50-
script_integration_test_harness = { workspace = true }
51-
libtest-mimic = "0.8"
52-
regex = "1.11"
53-
54-
[[test]]
55-
name = "lua_tests"
56-
harness = false
57-
5849
[lints]
5950
workspace = true

crates/languages/bevy_mod_scripting_lua/tests/data/construct/construct_unit_struct.lua

-4
This file was deleted.

0 commit comments

Comments
 (0)