Optimize rollback sync-test performance (#86)
CI / build-and-test (push) Successful in 2m11s
CI / production-build (push) Successful in 11m44s

## Summary

- remove immutable stage geometry and derived animation/collision data from rollback snapshots
- avoid a duplicate full skeletal hierarchy traversal during root-motion sampling
- cache root-motion cycle deltas per animation clip
- optimize the game crate in development builds
- make Tracy profiling opt-in via the `tracy` feature

Reviewed-on: #86
Co-authored-by: Álex Román Núñez <eirexe@eirteam.moe>
This commit was merged in pull request #86.
This commit is contained in:
2026-10-07 15:50:45 +00:00
committed by EIREXE
parent 259dada54f
commit 3dfe2fa000
4 changed files with 127 additions and 27 deletions
Generated
+91
View File
@@ -516,6 +516,7 @@ checksum = "4bfadbebfc6599aa59289b754ac30023959854304f3d393da2cf62bb3cd5df8f"
dependencies = [ dependencies = [
"bevy_dylib", "bevy_dylib",
"bevy_internal", "bevy_internal",
"tracing",
] ]
[[package]] [[package]]
@@ -694,6 +695,7 @@ dependencies = [
"downcast-rs 2.0.2", "downcast-rs 2.0.2",
"log", "log",
"thiserror 2.0.19", "thiserror 2.0.19",
"tracing",
"variadics_please", "variadics_please",
"wasm-bindgen", "wasm-bindgen",
"web-sys", "web-sys",
@@ -1001,6 +1003,7 @@ dependencies = [
"slotmap", "slotmap",
"smallvec", "smallvec",
"thiserror 2.0.19", "thiserror 2.0.19",
"tracing",
"variadics_please", "variadics_please",
] ]
@@ -1421,9 +1424,11 @@ dependencies = [
"bevy_platform", "bevy_platform",
"bevy_utils", "bevy_utils",
"tracing", "tracing",
"tracing-error",
"tracing-log", "tracing-log",
"tracing-oslog", "tracing-oslog",
"tracing-subscriber", "tracing-subscriber",
"tracing-tracy",
"tracing-wasm", "tracing-wasm",
] ]
@@ -1733,9 +1738,11 @@ dependencies = [
"naga", "naga",
"nonmax", "nonmax",
"offset-allocator", "offset-allocator",
"profiling",
"send_wrapper", "send_wrapper",
"smallvec", "smallvec",
"thiserror 2.0.19", "thiserror 2.0.19",
"tracy-client",
"variadics_please", "variadics_please",
"wasm-bindgen", "wasm-bindgen",
"weak-table", "weak-table",
@@ -3463,6 +3470,21 @@ dependencies = [
"rusb", "rusb",
] ]
[[package]]
name = "generator"
version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54ade96dc9003043bce7c035c85a9df5a858bfb2039c5a2e6fdf00f324f6c551"
dependencies = [
"cc",
"cfg-if",
"libc",
"log",
"rustversion",
"windows-link",
"windows-result",
]
[[package]] [[package]]
name = "gethostname" name = "gethostname"
version = "1.1.0" version = "1.1.0"
@@ -4381,6 +4403,19 @@ version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
[[package]]
name = "loom"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca"
dependencies = [
"cfg-if",
"generator",
"scoped-tls",
"tracing",
"tracing-subscriber",
]
[[package]] [[package]]
name = "mach2" name = "mach2"
version = "0.5.0" version = "0.5.0"
@@ -5629,6 +5664,20 @@ name = "profiling"
version = "1.0.18" version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5"
dependencies = [
"profiling-procmacros",
"tracing",
]
[[package]]
name = "profiling-procmacros"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4488a4a36b9a4ba6b9334a32a39971f77c1436ec82c38707bce707699cc3bbcb"
dependencies = [
"quote",
"syn 2.0.119",
]
[[package]] [[package]]
name = "pxfm" name = "pxfm"
@@ -6829,6 +6878,16 @@ dependencies = [
"valuable", "valuable",
] ]
[[package]]
name = "tracing-error"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db"
dependencies = [
"tracing",
"tracing-subscriber",
]
[[package]] [[package]]
name = "tracing-log" name = "tracing-log"
version = "0.2.0" version = "0.2.0"
@@ -6870,6 +6929,17 @@ dependencies = [
"tracing-log", "tracing-log",
] ]
[[package]]
name = "tracing-tracy"
version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eaa1852afa96e0fe9e44caa53dc0bd2d9d05e0f2611ce09f97f8677af56e4ba"
dependencies = [
"tracing-core",
"tracing-subscriber",
"tracy-client",
]
[[package]] [[package]]
name = "tracing-wasm" name = "tracing-wasm"
version = "0.2.1" version = "0.2.1"
@@ -6881,6 +6951,27 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "tracy-client"
version = "0.18.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6131992ff3e2cb96f407eb4eb005427ba15e2687260d99089b90e60e33169ce0"
dependencies = [
"loom",
"once_cell",
"tracy-client-sys",
]
[[package]]
name = "tracy-client-sys"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab27f167b093214c68413a2e0bcd318f0591af475597405ff68138ba0a433379"
dependencies = [
"cc",
"windows-targets 0.52.6",
]
[[package]] [[package]]
name = "tree_magic_mini" name = "tree_magic_mini"
version = "3.2.2" version = "3.2.2"
+6
View File
@@ -9,6 +9,7 @@ authors = [""]
[features] [features]
default = ["development", "debug_features", "gamecube-adapter"] default = ["development", "debug_features", "gamecube-adapter"]
development = ["bevy/dynamic_linking", "bevy/file_watcher", "bevy/embedded_watcher", "hitbox-viewer"] development = ["bevy/dynamic_linking", "bevy/file_watcher", "bevy/embedded_watcher", "hitbox-viewer"]
tracy = ["bevy/trace_tracy"]
debug_features = ["bevy/free_camera"] debug_features = ["bevy/free_camera"]
gamecube-adapter = ["dep:gc-adapter-neo"] gamecube-adapter = ["dep:gc-adapter-neo"]
hitbox-viewer = ["bevy/bevy_dev_tools", "dep:egui_extras"] hitbox-viewer = ["bevy/bevy_dev_tools", "dep:egui_extras"]
@@ -44,6 +45,11 @@ fixed_math = "0.4.1"
[profile.dev.package."*"] [profile.dev.package."*"]
opt-level = 3 opt-level = 3
# The rollback simulation runs multiple times per rendered frame in sync-test
# mode, so optimizing the game crate is important even for development builds.
[profile.dev]
opt-level = 1
[profile.production] [profile.production]
inherits = "release" inherits = "release"
lto = "thin" lto = "thin"
+27 -20
View File
@@ -127,6 +127,8 @@ pub struct FighterBoneMatrices {
/// pose has the root translation removed. /// pose has the root translation removed.
previous_root_motion_translation: Option<FGVec3>, previous_root_motion_translation: Option<FGVec3>,
root_motion_delta: crate::math::vec3::FGVec3, root_motion_delta: crate::math::vec3::FGVec3,
cached_root_motion_kind: Option<AnimKind>,
cached_root_motion_cycle_delta: FGVec3,
} }
#[derive(Component, Clone, Debug, Default)] #[derive(Component, Clone, Debug, Default)]
@@ -366,12 +368,6 @@ impl FighterBoneMatrices {
.expect("configured root motion bone should exist"), .expect("configured root motion bone should exist"),
) )
}; };
self.model_matrices.resize(local.len(), None);
self.model_matrices.fill(None);
for index in 0..local.len() {
resolve_model_transform(index, parents, local, &mut self.model_matrices)
.expect("baked bone hierarchy should be valid");
}
let model_scale = FixedMat4 { let model_scale = FixedMat4 {
cols: [ cols: [
[model_scale, FGi32::ZERO, FGi32::ZERO, FGi32::ZERO], [model_scale, FGi32::ZERO, FGi32::ZERO, FGi32::ZERO],
@@ -380,13 +376,16 @@ impl FighterBoneMatrices {
[FGi32::ZERO, FGi32::ZERO, FGi32::ZERO, FGi32::ONE], [FGi32::ZERO, FGi32::ZERO, FGi32::ZERO, FGi32::ONE],
], ],
}; };
// Root motion only needs the configured bone's model transform. Resolve
// that ancestry first, then do the normal full-pose pass once below.
// Previously the complete hierarchy was resolved once before removing
// root translation and once afterwards.
let raw_root_motion_translation = root_index.map(|root_index| { let raw_root_motion_translation = root_index.map(|root_index| {
model_scale self.model_matrices.resize(local.len(), None);
.mul( self.model_matrices.fill(None);
self.model_matrices[root_index] let root = resolve_model_transform(root_index, parents, local, &mut self.model_matrices)
.expect("baked bone hierarchy should be valid"), .expect("baked bone hierarchy should be valid");
) model_scale.mul(root).get_translation()
.get_translation()
}); });
// Gameplay owns the root's displacement. Keep rotation and scale in // Gameplay owns the root's displacement. Keep rotation and scale in
@@ -394,11 +393,12 @@ impl FighterBoneMatrices {
// used by visuals and gameplay volumes. // used by visuals and gameplay volumes.
if let Some(root_index) = root_index { if let Some(root_index) = root_index {
self.local_matrices[root_index].translate(FGVec3::ZERO); self.local_matrices[root_index].translate(FGVec3::ZERO);
self.model_matrices.fill(None); }
for index in 0..local.len() { self.model_matrices.resize(local.len(), None);
resolve_model_transform(index, parents, &self.local_matrices, &mut self.model_matrices) self.model_matrices.fill(None);
.expect("baked bone hierarchy should be valid"); for index in 0..local.len() {
} resolve_model_transform(index, parents, &self.local_matrices, &mut self.model_matrices)
.expect("baked bone hierarchy should be valid");
} }
self.matrices.clear(); self.matrices.clear();
self.matrices.extend( self.matrices.extend(
@@ -517,9 +517,16 @@ fn sample_fighter_bone_matrices(
let manifest = manifests let manifest = manifests
.get(&fighter.manifest) .get(&fighter.manifest)
.expect("fighter manifest should be loaded before the match starts"); .expect("fighter manifest should be loaded before the match starts");
let root_motion_cycle_delta = baked let root_motion_cycle_delta = if matrices.cached_root_motion_kind == Some(frame.kind) {
.root_motion_cycle_delta(frame.kind, &manifest.root_motion_bone, manifest.model_scale) matrices.cached_root_motion_cycle_delta
.unwrap_or(FGVec3::ZERO); } else {
let delta = baked
.root_motion_cycle_delta(frame.kind, &manifest.root_motion_bone, manifest.model_scale)
.unwrap_or(FGVec3::ZERO);
matrices.cached_root_motion_kind = Some(frame.kind);
matrices.cached_root_motion_cycle_delta = delta;
delta
};
let root_motion_loop_frame_count = baked let root_motion_loop_frame_count = baked
.frame_count(frame.kind) .frame_count(frame.kind)
.map(|count| count.saturating_sub(1).max(1)); .map(|count| count.saturating_sub(1).max(1));
+2 -6
View File
@@ -42,8 +42,8 @@ use crate::{
AttackKind, FighterAttackPlugin, FighterAttackScriptAssets, FighterDamage, AttackKind, FighterAttackPlugin, FighterAttackScriptAssets, FighterDamage,
FighterKnockbackVelocity, FighterPendingHitReaction, FighterSolvedHurtboxes, FighterKnockbackVelocity, FighterPendingHitReaction, FighterSolvedHurtboxes,
FighterStaleMoveQueue, FighterStaleMoveQueue,
}, baked_animation::{FighterBoneIndexMap, FighterBoneMatrices}, ecb::FighterECBBoneIndices, manifest::FighterManifest, state::{FighterState, air::AirborneStateCommon, fall::FallState}, visual::{FighterAnimations, FighterInvulnerabilityVisualState}, }, baked_animation::FighterBoneIndexMap, ecb::FighterECBBoneIndices, manifest::FighterManifest, state::{FighterState, air::AirborneStateCommon, fall::FallState}, visual::{FighterAnimations, FighterInvulnerabilityVisualState},
}, game_settings::GameSettings, input::FighterInput, math::{int::FGi32, vec::FGVec2}, player::Player, schedule::GameplaySet, scripting::FighterAttackScript, stage::line::StageCollision, }, game_settings::GameSettings, input::FighterInput, math::{int::FGi32, vec::FGVec2}, player::Player, schedule::GameplaySet, scripting::FighterAttackScript,
}; };
use state::state_interrupt_system; use state::state_interrupt_system;
@@ -250,7 +250,6 @@ impl Plugin for FighterPlugin {
.checksum_component_with_hash::<FighterInvulnerabilityState>() .checksum_component_with_hash::<FighterInvulnerabilityState>()
.rollback_component_with_copy::<FighterKnockbackVelocity>() .rollback_component_with_copy::<FighterKnockbackVelocity>()
.checksum_component_with_hash::<FighterKnockbackVelocity>() .checksum_component_with_hash::<FighterKnockbackVelocity>()
.rollback_resource_with_reflect::<StageCollision>()
// Debug views. // Debug views.
.add_systems( .add_systems(
FixedPostUpdate, FixedPostUpdate,
@@ -284,11 +283,8 @@ impl Plugin for FighterPlugin {
) )
.rollback_component_with_clone::<FighterState>() .rollback_component_with_clone::<FighterState>()
.rollback_component_with_clone::<FighterHitboxes>() .rollback_component_with_clone::<FighterHitboxes>()
.rollback_component_with_clone::<FighterBoneMatrices>()
.rollback_component_with_clone::<FighterHitstopShake>() .rollback_component_with_clone::<FighterHitstopShake>()
.checksum_component_with_hash::<FighterHitboxes>() .checksum_component_with_hash::<FighterHitboxes>()
.rollback_component_with_clone::<FighterSolvedHurtboxes>()
.checksum_component_with_hash::<FighterSolvedHurtboxes>()
.checksum_component_with_hash::<FighterState>() .checksum_component_with_hash::<FighterState>()
.checksum_component_with_hash::<FighterFacingDirection>() .checksum_component_with_hash::<FighterFacingDirection>()
.register_type::<FighterFacingDirection>(); .register_type::<FighterFacingDirection>();