Optimize rollback hot paths (#53)
CI / build-and-test (push) Successful in 1m16s
CI / production-build (push) Successful in 9m36s

## Summary

Fixes #20.

- Reuse fighter animation scratch and output buffers across rollback frames.
- Select air-collision hits in one pass without candidate allocations or sorting.
- Index contiguous replay input frames directly and binary-search checksums.
- Add regression tests for buffer reuse and replay lookup/validation.

Reviewed-on: #53
Co-authored-by: Álex Román Núñez <eirexe@eirteam.moe>
This commit was merged in pull request #53.
This commit is contained in:
2026-09-20 12:41:13 +00:00
committed by EIREXE
parent bcf1e114ef
commit 85236c4e0f
4 changed files with 143 additions and 55 deletions
+15 -1
View File
@@ -4,8 +4,8 @@ mod asset_baking;
mod runtime;
mod visual_binding;
pub use asset_baking::{BakedAnimationAssetProcessingPlugin, BakedAnimationPlugin};
pub use crate::math::fixed_transform::FixedMat4;
pub use asset_baking::{BakedAnimationAssetProcessingPlugin, BakedAnimationPlugin};
pub use runtime::{
BakedAnimationClip, BakedFighterAnimations, FighterBoneIndexMap, FighterBoneMatrices,
update_fighter_bone_matrices,
@@ -164,6 +164,20 @@ mod tests {
);
}
#[test]
fn repeated_pose_updates_reuse_scratch_and_output_buffers() {
let mut matrices = FighterBoneMatrices::default();
let names = ["root".into(), "hand".into()];
let parents = [None, Some(0)];
let pose = [FixedMat4::IDENTITY, FixedMat4::IDENTITY];
matrices.set_pose(&names, &parents, &pose, FGi32::ONE);
let capacities = matrices.buffer_capacities();
matrices.set_pose(&names, &parents, &pose, FGi32::ONE);
assert_eq!(capacities, matrices.buffer_capacities());
}
#[test]
fn model_scale_affects_gameplay_bones_without_changing_visual_locals() {
let mut root = FixedMat4::IDENTITY;
+28 -8
View File
@@ -3,7 +3,7 @@ use std::collections::HashMap;
use bevy::prelude::*;
use serde::{Deserialize, Serialize};
use super::{BAKED_ANIMATION_FPS};
use super::BAKED_ANIMATION_FPS;
use crate::{
fighter::{
@@ -12,8 +12,8 @@ use crate::{
manifest::FighterManifest,
visual::FighterAnimations,
},
math::fixed_transform::FixedMat4,
math::int::FGi32,
math::fixed_transform::FixedMat4
};
#[derive(Clone, Debug, Serialize, Deserialize)]
@@ -70,12 +70,24 @@ pub struct FighterBoneMatrices {
previous_frame_matrices: Vec<FixedMat4>,
local_matrices: Vec<FixedMat4>,
previous_local_matrices: Vec<FixedMat4>,
model_matrices: Vec<Option<FixedMat4>>,
}
#[derive(Component, Clone, Debug, Default)]
pub struct FighterBoneIndexMap(pub HashMap<String, usize>);
impl FighterBoneMatrices {
#[cfg(test)]
pub(super) fn buffer_capacities(&self) -> [usize; 5] {
[
self.matrices.capacity(),
self.local_matrices.capacity(),
self.previous_frame_matrices.capacity(),
self.previous_local_matrices.capacity(),
self.model_matrices.capacity(),
]
}
pub fn get(&self, bone: &str) -> Option<(FixedMat4, FixedMat4)> {
let index = self.bone_names.iter().position(|name| name == bone)?;
Some((
@@ -142,13 +154,19 @@ impl FighterBoneMatrices {
assert_eq!(self.bone_names, bone_names, "baked bone names changed");
assert_eq!(self.parents, parents, "baked bone parents changed");
}
self.matrices.reserve(local.len());
self.previous_frame_matrices.reserve(local.len());
self.local_matrices.reserve(local.len());
self.previous_local_matrices.reserve(local.len());
self.model_matrices.reserve(local.len());
std::mem::swap(&mut self.matrices, &mut self.previous_frame_matrices);
std::mem::swap(&mut self.local_matrices, &mut self.previous_local_matrices);
self.local_matrices.clear();
self.local_matrices.extend_from_slice(local);
let mut model = vec![None; local.len()];
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 model)
resolve_model_transform(index, parents, local, &mut self.model_matrices)
.expect("baked bone hierarchy should be valid");
}
let model_scale = FixedMat4 {
@@ -159,10 +177,12 @@ impl FighterBoneMatrices {
[FGi32::ZERO, FGi32::ZERO, FGi32::ZERO, FGi32::ONE],
],
};
self.matrices = model
.into_iter()
.map(|matrix| model_scale.mul(matrix.expect("baked bone hierarchy should be valid")))
.collect();
self.matrices.clear();
self.matrices.extend(
self.model_matrices.iter().map(|matrix| {
model_scale.mul(matrix.expect("baked bone hierarchy should be valid"))
}),
);
}
}