Skip to content

Commit

Permalink
Merge pull request #1402 from ergoxiv/hotfix/patch-7-1-misc-fixes
Browse files Browse the repository at this point in the history
- Removed the "None" as a selectable option from the age customize editor menu.
        Note: It is used internally to determine if the model is a non-humanoid NPC but it should not be selectable by the user.
- Fixed infinite actor refresh loop causing flickering and game crashes.
- Added thread locks to resolve an issue where the history class might occasionally try to record multiple elements at once from differen't threads, causing Anamnesis throw a critical exception and close itself.
  • Loading branch information
StoiaCode authored Nov 16, 2024
2 parents bd97e69 + 6eb0658 commit 13f222a
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 43 deletions.
5 changes: 1 addition & 4 deletions Anamnesis/Actor/Refresh/AnamnesisActorRefresher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@

namespace Anamnesis.Actor.Refresh;

using System.Threading.Tasks;
using Anamnesis.Memory;
using Anamnesis.Services;
using System.Threading.Tasks;
using static Anamnesis.Memory.ActorBasicMemory;

public class AnamnesisActorRefresher : IActorRefresher
Expand All @@ -28,7 +28,6 @@ public bool CanRefresh(ActorMemory actor)

public async Task RefreshActor(ActorMemory actor)
{
await Task.Delay(16);
if (SettingsService.Current.EnableNpcHack && actor.ObjectKind == ActorTypes.Player)
{
actor.ObjectKind = ActorTypes.BattleNpc;
Expand All @@ -45,7 +44,5 @@ public async Task RefreshActor(ActorMemory actor)
await Task.Delay(75);
actor.RenderMode = RenderModes.Draw;
}

await Task.Delay(150);
}
}
5 changes: 4 additions & 1 deletion Anamnesis/Actor/Views/CustomizeEditor.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ public CustomizeEditor()
this.ContentArea.DataContext = this;

this.GenderComboBox.ItemsSource = Enum.GetValues(typeof(ActorCustomizeMemory.Genders));
this.AgeComboBox.ItemsSource = Enum.GetValues(typeof(ActorCustomizeMemory.Ages));
this.AgeComboBox.ItemsSource =
Enum.GetValues(typeof(ActorCustomizeMemory.Ages))
.Cast<ActorCustomizeMemory.Ages>()
.Where(age => age != ActorCustomizeMemory.Ages.None);

List<Race> races = new();
foreach (Race race in GameDataService.Races)
Expand Down
4 changes: 4 additions & 0 deletions Anamnesis/Memory/ActorMemory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,10 @@ private void HandlePropertyChanged(object? sender, PropertyChangedEventArgs e)
// Refresh the actor
if (this.AutomaticRefreshEnabled && change.OriginBind.Flags.HasFlag(BindFlags.ActorRefresh))
{
// Don't refresh because of a refresh
if (this.IsRefreshing && (change.OriginBind.Name == nameof(this.ObjectKind) || change.OriginBind.Name == nameof(this.RenderMode)))
return;

if (change.OriginBind.Flags.HasFlag(BindFlags.WeaponRefresh))
this.IsWeaponDirty = true;

Expand Down
108 changes: 70 additions & 38 deletions Anamnesis/Memory/History.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,62 +144,91 @@ public class HistoryEntry

private readonly List<PropertyChange> changes = new();

public bool HasChanges => this.changes.Count > 0;
public int Count => this.changes.Count;
public bool HasChanges
{
get
{
lock (this.changes)
{
return this.changes.Count > 0;
}
}
}

public int Count
{
get
{
lock (this.changes)
{
return this.changes.Count;
}
}
}

public string Name { get; set; } = string.Empty;
public string ChangeList => this.GetChangeList();

public void Restore()
{
Log.Verbose($"Restoring change set:\n{this}");

for (int i = this.changes.Count - 1; i >= 0; i--)
lock (this.changes)
{
PropertyChange change = this.changes[i];
if (change.OriginBind is PropertyBindInfo propertyBind)
for (int i = this.changes.Count - 1; i >= 0; i--)
{
propertyBind.Property.SetValue(propertyBind.Memory, change.OldValue);
PropertyChange change = this.changes[i];
if (change.OriginBind is PropertyBindInfo propertyBind)
{
propertyBind.Property.SetValue(propertyBind.Memory, change.OldValue);
}
}
}
}

public void Record(PropertyChange change)
{
// Keep only the latest change for each bind to minimize stored changes and recovery steps
PropertyChange? existingChange = this.changes.FirstOrDefault(c => c.Path == change.Path);
if (existingChange.HasValue)
lock (this.changes)
{
// Validate the existing change's OldValue
if (IsValidOldValue(existingChange.Value.OldValue))
// Keep only the latest change for each bind to minimize stored changes and recovery steps
PropertyChange? existingChange = this.changes.FirstOrDefault(c => c.Path == change.Path);
if (existingChange.HasValue)
{
// Transfer the old value of the existing change to the new change if it is valid
change.OldValue = existingChange.Value.OldValue;
// Validate the existing change's OldValue
if (IsValidOldValue(existingChange.Value.OldValue))
{
// Transfer the old value of the existing change to the new change if it is valid
change.OldValue = existingChange.Value.OldValue;
}

// Remove the existing change
this.changes.Remove(existingChange.Value);
}

// Remove the existing change
this.changes.Remove(existingChange.Value);
}

// Add the latest change into the history entry's changes list
this.changes.Add(change);
// Add the latest change into the history entry's changes list
this.changes.Add(change);

// Sanity check history depth
if (this.changes.Count > MaxChanges)
{
Log.Warning($"Change depth exceded max: {MaxChanges}. Flushing");
this.changes.Clear();
// Sanity check history depth
if (this.changes.Count > MaxChanges)
{
Log.Warning($"Change depth exceded max: {MaxChanges}. Flushing");
this.changes.Clear();
}
}
}

public string GetName()
{
HashSet<string> names = new();
foreach (PropertyChange change in this.changes)
lock (this.changes)
{
if (change.Name == null)
continue;
foreach (PropertyChange change in this.changes)
{
if (change.Name == null)
continue;

names.Add(change.Name);
names.Add(change.Name);
}
}

StringBuilder builder = new();
Expand All @@ -220,18 +249,21 @@ public string GetChangeList()

// Flatten the changes to repeated changes to the same value dont show up
Dictionary<BindInfo, PropertyChange> flattenedChanges = new();
foreach (PropertyChange change in this.changes)
lock (this.changes)
{
if (!flattenedChanges.ContainsKey(change.OriginBind))
foreach (PropertyChange change in this.changes)
{
flattenedChanges.Add(change.OriginBind, new(change));
}
else
{
PropertyChange existingChange = flattenedChanges[change.OriginBind];

existingChange.NewValue = change.NewValue;
flattenedChanges[change.OriginBind] = existingChange;
if (!flattenedChanges.ContainsKey(change.OriginBind))
{
flattenedChanges.Add(change.OriginBind, new(change));
}
else
{
PropertyChange existingChange = flattenedChanges[change.OriginBind];

existingChange.NewValue = change.NewValue;
flattenedChanges[change.OriginBind] = existingChange;
}
}
}

Expand Down

0 comments on commit 13f222a

Please sign in to comment.