Skip to content

Commit

Permalink
Tried to further optimize how animations are read/allocated, especial…
Browse files Browse the repository at this point in the history
…ly for Battalion Wars.
  • Loading branch information
MeltyPlayer committed Oct 23, 2023
1 parent 26452b1 commit 6370492
Show file tree
Hide file tree
Showing 17 changed files with 235 additions and 160 deletions.
49 changes: 36 additions & 13 deletions FinModelUtility/Fin/Fin Tests/data/KeyframesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,20 @@ public class KeyframesTests {
public void TestAddToEnd() {
var impl = new Keyframes<string>();

impl.SetKeyframe(0, "0");
impl.SetKeyframe(1, "1");
impl.SetKeyframe(2, "2");
impl.SetKeyframe(3, "3");
impl.SetKeyframe(4, "4");
impl.SetKeyframe(0, "0", out var performedBinarySearch0);
Assert.False(performedBinarySearch0);

impl.SetKeyframe(1, "1", out var performedBinarySearch1);
Assert.False(performedBinarySearch1);

impl.SetKeyframe(2, "2", out var performedBinarySearch2);
Assert.False(performedBinarySearch2);

impl.SetKeyframe(3, "3", out var performedBinarySearch3);
Assert.False(performedBinarySearch3);

impl.SetKeyframe(4, "4", out var performedBinarySearch4);
Assert.False(performedBinarySearch4);

AssertKeyframes_(impl,
new Keyframe<string>(0, "0"),
Expand All @@ -27,9 +36,14 @@ public void TestAddToEnd() {
public void TestReplace() {
var impl = new Keyframes<string>();

impl.SetKeyframe(1, "first");
impl.SetKeyframe(1, "second");
impl.SetKeyframe(1, "third");
impl.SetKeyframe(1, "first", out var performedBinarySearch1);
Assert.False(performedBinarySearch1);

impl.SetKeyframe(1, "second", out var performedBinarySearch2);
Assert.False(performedBinarySearch2);

impl.SetKeyframe(1, "third", out var performedBinarySearch3);
Assert.False(performedBinarySearch3);

AssertKeyframes_(impl, new Keyframe<string>(1, "third"));
}
Expand All @@ -38,11 +52,20 @@ public void TestReplace() {
public void TestInsertAtFront() {
var impl = new Keyframes<string>();

impl.SetKeyframe(4, "4");
impl.SetKeyframe(5, "5");
impl.SetKeyframe(2, "2");
impl.SetKeyframe(1, "1");
impl.SetKeyframe(0, "0");
impl.SetKeyframe(4, "4", out var performedBinarySearch4);
Assert.False(performedBinarySearch4);

impl.SetKeyframe(5, "5", out var performedBinarySearch5);
Assert.False(performedBinarySearch5);

impl.SetKeyframe(2, "2", out var performedBinarySearch2);
Assert.True(performedBinarySearch2);

impl.SetKeyframe(1, "1", out var performedBinarySearch1);
Assert.True(performedBinarySearch1);

impl.SetKeyframe(0, "0", out var performedBinarySearch0);
Assert.True(performedBinarySearch0);

AssertKeyframes_(impl,
new Keyframe<string>(0, "0"),
Expand Down
82 changes: 63 additions & 19 deletions FinModelUtility/Fin/Fin/src/data/Keyframes.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;

namespace fin.data {
Expand All @@ -14,18 +15,16 @@ public Keyframe(int frame, T value) {

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int CompareTo(Keyframe<T> other)
=> this.Frame.CompareTo(other.Frame);
=> this.Frame - other.Frame;

public override string ToString()
=> $"{{ Frame: {this.Frame}, Value: {this.Value} }}";
}

public interface IKeyframes<T> {
IReadOnlyList<Keyframe<T>> Definitions { get; }

public interface IReadOnlyKeyframes<T> {
bool IsDefined { get; }

void SetKeyframe(int frame, T value);
IReadOnlyList<Keyframe<T>> Definitions { get; }

Keyframe<T> GetKeyframeAtIndex(int index);
Keyframe<T>? GetKeyframeAtFrame(int frame);
Expand All @@ -37,8 +36,13 @@ bool FindIndexOfKeyframe(
out bool isLastKeyframe);
}

public interface IKeyframes<T> : IReadOnlyKeyframes<T> {
void SetKeyframe(int frame, T value);
void SetAllKeyframes(IEnumerable<T> value);
}

public class Keyframes<T> : IKeyframes<T> {
private readonly List<Keyframe<T>> impl_;
private List<Keyframe<T>> impl_;

public Keyframes(int initialCapacity = 0) {
this.impl_ = new(initialCapacity);
Expand All @@ -48,19 +52,27 @@ public Keyframes(int initialCapacity = 0) {

public bool IsDefined { get; set; }

public void SetKeyframe(int frame, T value) {
public void SetKeyframe(int frame, T value)
=> SetKeyframe(frame, value, out _);

public void SetKeyframe(int frame,
T value,
out bool performedBinarySearch) {
this.IsDefined = true;

var keyframeExists = this.FindIndexOfKeyframe(frame,
out var keyframeIndex,
out var existingKeyframe,
out var isLastKeyframe);
out var isLastKeyframe,
out performedBinarySearch);

var newKeyframe = new Keyframe<T>(frame, value);

if (keyframeExists && existingKeyframe.Frame == frame) {
this.lastAccessedKeyframeIndex_ = keyframeIndex;
this.impl_[keyframeIndex] = newKeyframe;
} else if (isLastKeyframe) {
this.lastAccessedKeyframeIndex_ = this.impl_.Count;
this.impl_.Add(newKeyframe);
} else if (keyframeExists && existingKeyframe.Frame < frame) {
this.impl_.Insert(keyframeIndex + 1, newKeyframe);
Expand All @@ -69,6 +81,14 @@ public void SetKeyframe(int frame, T value) {
}
}

public void SetAllKeyframes(IEnumerable<T> values) {
this.impl_ = values
.Select((value, frame) => new Keyframe<T>(frame, value))
.ToList();
this.IsDefined = this.impl_.Count > 0;
this.lastAccessedKeyframeIndex_ = this.impl_.Count - 1;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Keyframe<T> GetKeyframeAtIndex(int index) => this.impl_[index];

Expand All @@ -82,18 +102,40 @@ public void SetKeyframe(int frame, T value) {
}


private int lastAccessedIndex_ = -1;
private int lastAccessedKeyframeIndex_ = -1;

public bool FindIndexOfKeyframe(
int frame,
out int keyframeIndex,
out Keyframe<T> keyframe,
out bool isLastKeyframe) {
// Try to optimize the case where the next frame is being accessed.
out bool isLastKeyframe)
=> this.FindIndexOfKeyframe(frame,
out keyframeIndex,
out keyframe,
out isLastKeyframe,
out _);

public bool FindIndexOfKeyframe(
int frame,
out int keyframeIndex,
out Keyframe<T> keyframe,
out bool isLastKeyframe,
out bool performedBinarySearch) {
performedBinarySearch = false;

// Try to optimize the case where no frames have been processed yet.
var keyframeCount = this.impl_.Count;
if (this.lastAccessedIndex_ >= 0 &&
this.lastAccessedIndex_ < keyframeCount) {
keyframeIndex = this.lastAccessedIndex_;
if (this.lastAccessedKeyframeIndex_ == -1 || keyframeCount == 0) {
this.lastAccessedKeyframeIndex_ = keyframeIndex = 0;
keyframe = default;
isLastKeyframe = false;
return false;
}

// Try to optimize the case where the next frame is being accessed.
if (this.lastAccessedKeyframeIndex_ >= 0 &&
this.lastAccessedKeyframeIndex_ < keyframeCount) {
keyframeIndex = this.lastAccessedKeyframeIndex_;
keyframe = this.impl_[keyframeIndex];

if (frame >= keyframe.Frame) {
Expand All @@ -103,11 +145,11 @@ public bool FindIndexOfKeyframe(
return true;
}

var nextKeyframe = this.impl_[this.lastAccessedIndex_ + 1];
var nextKeyframe = this.impl_[this.lastAccessedKeyframeIndex_ + 1];
if (nextKeyframe.Frame > frame) {
return true;
} else if (nextKeyframe.Frame == frame) {
this.lastAccessedIndex_ = ++keyframeIndex;
this.lastAccessedKeyframeIndex_ = ++keyframeIndex;
keyframe = nextKeyframe;
isLastKeyframe = keyframeIndex == keyframeCount - 1;
return true;
Expand All @@ -117,16 +159,18 @@ public bool FindIndexOfKeyframe(

// Perform a binary search for the current frame.
var result = this.impl_.BinarySearch(new Keyframe<T>(frame, default!));
performedBinarySearch = true;

if (result >= 0) {
this.lastAccessedIndex_ = keyframeIndex = result;
this.lastAccessedKeyframeIndex_ = keyframeIndex = result;
keyframe = this.impl_[keyframeIndex];
isLastKeyframe = keyframeIndex == keyframeCount - 1;
return true;
}

var i = ~result;
if (i == keyframeCount) {
this.lastAccessedIndex_ = keyframeIndex = keyframeCount - 1;
this.lastAccessedKeyframeIndex_ = keyframeIndex = keyframeCount - 1;
isLastKeyframe = true;
if (keyframeCount > 0) {
keyframe = this.impl_[keyframeIndex];
Expand All @@ -137,7 +181,7 @@ public bool FindIndexOfKeyframe(
return false;
}

this.lastAccessedIndex_ = keyframeIndex = Math.Max(0, i - 1);
this.lastAccessedKeyframeIndex_ = keyframeIndex = Math.Max(0, i - 1);
keyframe = this.impl_[keyframeIndex];
var keyframeExists = keyframe.Frame <= frame;
if (!keyframeExists) {
Expand Down
12 changes: 7 additions & 5 deletions FinModelUtility/Fin/Fin/src/model/AnimationTrackInterfaces.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,21 @@ public interface IImplTrack<TValue> : ITrack {
IReadOnlyList<Keyframe<ValueAndTangents<TValue>>> Keyframes { get; }

[MethodImpl(MethodImplOptions.AggressiveInlining)]
void Set(int frame, TValue value)
=> this.Set(frame, value, null);
void SetKeyframe(int frame, TValue value)
=> this.SetKeyframe(frame, value, null);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
void Set(int frame, TValue value, float? tangent)
=> this.Set(frame, value, tangent, tangent);
void SetKeyframe(int frame, TValue value, float? tangent)
=> this.SetKeyframe(frame, value, tangent, tangent);

void Set(
void SetKeyframe(
int frame,
TValue value,
float? incomingTangent,
float? outgoingTangent);

void SetAllKeyframes(IEnumerable<TValue> value);

bool TryGetInterpolationData(
float frame,
out (float frame, TValue value, float? tangent)? fromData,
Expand Down
29 changes: 15 additions & 14 deletions FinModelUtility/Fin/Fin/src/model/impl/AnimationImpl.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;

using fin.data.indexable;
using fin.math.interpolation;

namespace fin.model.impl {
public partial class ModelImpl<TVertex> {
public IAnimationManager AnimationManager { get; } =
new AnimationManagerImpl();
public IAnimationManager AnimationManager { get; }

private class AnimationManagerImpl : IAnimationManager {
private readonly IModel model_;

private readonly IList<IModelAnimation> animations_ =
new List<IModelAnimation>();

private readonly IList<IMorphTarget> morphTargets_ =
new List<IMorphTarget>();

public AnimationManagerImpl() {
public AnimationManagerImpl(IModel model) {
this.model_ = model;
this.Animations =
new ReadOnlyCollection<IModelAnimation>(this.animations_);
this.MorphTargets =
Expand All @@ -28,20 +31,18 @@ public AnimationManagerImpl() {
public IReadOnlyList<IModelAnimation> Animations { get; }

public IModelAnimation AddAnimation() {
var animation = new ModelAnimationImpl();
var animation = new ModelAnimationImpl(this.model_.Skeleton.Count());
this.animations_.Add(animation);
return animation;
}

private class ModelAnimationImpl : IModelAnimation {
private readonly IndexableDictionary<IBone, IBoneTracks> boneTracks_ =
new();

private readonly IndexableDictionary<IBone, IBoneTracks> boneTracks_;
private readonly Dictionary<IMesh, IMeshTracks> meshTracks_ = new();

public ModelAnimationImpl() {
this.BoneTracks = this.boneTracks_;
this.MeshTracks = this.meshTracks_;
public ModelAnimationImpl(int boneCount) {
this.boneTracks_ =
new IndexableDictionary<IBone, IBoneTracks>(boneCount);
}

public string Name { get; set; }
Expand All @@ -50,14 +51,14 @@ public ModelAnimationImpl() {

public float FrameRate { get; set; }

public IReadOnlyIndexableDictionary<IBone, IBoneTracks> BoneTracks {
get;
}
public IReadOnlyIndexableDictionary<IBone, IBoneTracks> BoneTracks
=> this.boneTracks_;

public IBoneTracks AddBoneTracks(IBone bone)
=> this.boneTracks_[bone] = new BoneTracksImpl(this, bone);

public IReadOnlyDictionary<IMesh, IMeshTracks> MeshTracks { get; }
public IReadOnlyDictionary<IMesh, IMeshTracks> MeshTracks
=> this.meshTracks_;

public IMeshTracks AddMeshTracks(IMesh mesh)
=> this.meshTracks_[mesh] = new MeshTracksImpl(this);
Expand Down
10 changes: 2 additions & 8 deletions FinModelUtility/Fin/Fin/src/model/impl/BoneWeightsSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,7 @@ public bool TryGetExisting(
}

public static int GetHashCode(VertexSpace vertexSpace,
IReadOnlyList<IBoneWeight> weights) {
var hash = FluentHash.Start().With(vertexSpace);
foreach (var weight in weights) {
hash = hash.With(weight);
}

return hash;
}
IEnumerable<IBoneWeight> weights)
=> FluentHash.Start().With(vertexSpace).With(weights);
}
}
2 changes: 2 additions & 0 deletions FinModelUtility/Fin/Fin/src/model/impl/ModelImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ public partial class ModelImpl<TVertex>
: IModel<ISkin<TVertex>> where TVertex : IReadOnlyVertex {
public ModelImpl(Func<int, Position, TVertex> vertexCreator) {
this.Skin = new SkinImpl(vertexCreator);
this.AnimationManager = new AnimationManagerImpl(this);
}

// TODO: Rewrite this to take in options instead.
public ModelImpl(int vertexCount,
Func<int, Position, TVertex> vertexCreator) {
this.Skin = new SkinImpl(vertexCount, vertexCreator);
this.AnimationManager = new AnimationManagerImpl(this);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public void Set(
float? optionalIncomingTangent,
float? optionalOutgoingTangent)
=> this.axisTracks_[axis]
.Set(frame,
.SetKeyframe(frame,
radians,
optionalIncomingTangent,
optionalOutgoingTangent);
Expand Down
Loading

0 comments on commit 6370492

Please sign in to comment.