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
+27 -20
View File
@@ -127,6 +127,8 @@ pub struct FighterBoneMatrices {
/// pose has the root translation removed.
previous_root_motion_translation: Option<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)]
@@ -366,12 +368,6 @@ impl FighterBoneMatrices {
.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 {
cols: [
[model_scale, FGi32::ZERO, FGi32::ZERO, FGi32::ZERO],
@@ -380,13 +376,16 @@ impl FighterBoneMatrices {
[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| {
model_scale
.mul(
self.model_matrices[root_index]
.expect("baked bone hierarchy should be valid"),
)
.get_translation()
self.model_matrices.resize(local.len(), None);
self.model_matrices.fill(None);
let root = resolve_model_transform(root_index, parents, local, &mut self.model_matrices)
.expect("baked bone hierarchy should be valid");
model_scale.mul(root).get_translation()
});
// Gameplay owns the root's displacement. Keep rotation and scale in
@@ -394,11 +393,12 @@ impl FighterBoneMatrices {
// used by visuals and gameplay volumes.
if let Some(root_index) = root_index {
self.local_matrices[root_index].translate(FGVec3::ZERO);
self.model_matrices.fill(None);
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.model_matrices.resize(local.len(), None);
self.model_matrices.fill(None);
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.extend(
@@ -517,9 +517,16 @@ fn sample_fighter_bone_matrices(
let manifest = manifests
.get(&fighter.manifest)
.expect("fighter manifest should be loaded before the match starts");
let root_motion_cycle_delta = baked
.root_motion_cycle_delta(frame.kind, &manifest.root_motion_bone, manifest.model_scale)
.unwrap_or(FGVec3::ZERO);
let root_motion_cycle_delta = if matrices.cached_root_motion_kind == Some(frame.kind) {
matrices.cached_root_motion_cycle_delta
} 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
.frame_count(frame.kind)
.map(|count| count.saturating_sub(1).max(1));