mirror of
https://github.com/haven1433/HexManiacAdvance.git
synced 2026-10-02 13:16:35 -05:00
Caching Bugfix
* Only allow one scope to be created at a time, to avoid race conditions with modifying the activeScopes. * Allow scopes to be created for the same model when a scope already exists. Use reference counting in this case. * Change at what level several of the scopes are created at.
This commit is contained in:
@@ -7,7 +7,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace HavenSoft.HexManiac.Core.Models {
|
||||
public interface IDataModel : IReadOnlyList<byte> {
|
||||
public interface IDataModel : IReadOnlyList<byte>, IEquatable<IDataModel> {
|
||||
byte[] RawData { get; }
|
||||
new byte this[int index] { get; set; }
|
||||
IReadOnlyList<ArrayRun> Arrays { get; }
|
||||
@@ -138,6 +138,8 @@ namespace HavenSoft.HexManiac.Core.Models {
|
||||
public virtual StoredMetadata ExportMetadata() => null;
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
public bool Equals(IDataModel other) => other == this;
|
||||
}
|
||||
|
||||
public static class IDataModelExtensions {
|
||||
|
||||
@@ -440,16 +440,14 @@ namespace HavenSoft.HexManiac.Core.Models {
|
||||
private void ModifyAnchorsFromPointerArray(ModelDelta changeToken, ArrayRun arrayRun, Action<ArrayRunElementSegment, ModelDelta, int> changeAnchors) {
|
||||
int segmentOffset = arrayRun.Start;
|
||||
// i loops over the different segments in the array
|
||||
using (ModelCacheScope.CreateScope(this)) {
|
||||
for (int i = 0; i < arrayRun.ElementContent.Count; i++) {
|
||||
if (arrayRun.ElementContent[i].Type != ElementContentType.Pointer) { segmentOffset += arrayRun.ElementContent[i].Length; continue; }
|
||||
// for a pointer segment, j loops over all the elements in the array
|
||||
for (int j = 0; j < arrayRun.ElementCount; j++) {
|
||||
var start = segmentOffset + arrayRun.ElementLength * j;
|
||||
changeAnchors(arrayRun.ElementContent[i], changeToken, start);
|
||||
}
|
||||
segmentOffset += arrayRun.ElementContent[i].Length;
|
||||
for (int i = 0; i < arrayRun.ElementContent.Count; i++) {
|
||||
if (arrayRun.ElementContent[i].Type != ElementContentType.Pointer) { segmentOffset += arrayRun.ElementContent[i].Length; continue; }
|
||||
// for a pointer segment, j loops over all the elements in the array
|
||||
for (int j = 0; j < arrayRun.ElementCount; j++) {
|
||||
var start = segmentOffset + arrayRun.ElementLength * j;
|
||||
changeAnchors(arrayRun.ElementContent[i], changeToken, start);
|
||||
}
|
||||
segmentOffset += arrayRun.ElementContent[i].Length;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -566,7 +564,9 @@ namespace HavenSoft.HexManiac.Core.Models {
|
||||
if (run is ArrayRun array && array.SupportsPointersToElements) run = array.AddSourcesPointingWithinArray(changeToken);
|
||||
|
||||
var newRun = run.MergeAnchor(sources);
|
||||
ObserveRunWritten(changeToken, newRun);
|
||||
using (ModelCacheScope.CreateScope(this)) {
|
||||
ObserveRunWritten(changeToken, newRun);
|
||||
}
|
||||
}
|
||||
|
||||
public override void MassUpdateFromDelta(IReadOnlyDictionary<int, IFormattedRun> runsToRemove, IReadOnlyDictionary<int, IFormattedRun> runsToAdd, IReadOnlyDictionary<int, string> namesToRemove, IReadOnlyDictionary<int, string> namesToAdd, IReadOnlyDictionary<int, string> unmappedPointersToRemove, IReadOnlyDictionary<int, string> unmappedPointersToAdd) {
|
||||
|
||||
@@ -1,18 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace HavenSoft.HexManiac.Core.Models.Runs {
|
||||
public class ModelCacheScope : IDisposable {
|
||||
private static readonly Dictionary<IDataModel, ModelCacheScope> activeScopes = new Dictionary<IDataModel, ModelCacheScope>();
|
||||
|
||||
private readonly IDataModel model;
|
||||
private int references;
|
||||
private ModelCacheScope(IDataModel model) => this.model = model;
|
||||
public static IDisposable CreateScope(IDataModel model) {
|
||||
Debug.Assert(!activeScopes.ContainsKey(model));
|
||||
activeScopes[model] = new ModelCacheScope(model);
|
||||
return activeScopes[model];
|
||||
lock (activeScopes) {
|
||||
if (!activeScopes.ContainsKey(model)) {
|
||||
activeScopes[model] = new ModelCacheScope(model);
|
||||
}
|
||||
activeScopes[model].references++;
|
||||
return activeScopes[model];
|
||||
}
|
||||
}
|
||||
public void Dispose() {
|
||||
lock (activeScopes) {
|
||||
activeScopes[model].references--;
|
||||
if (activeScopes[model].references == 0) activeScopes.Remove(model);
|
||||
}
|
||||
}
|
||||
public void Dispose() => activeScopes.Remove(model);
|
||||
|
||||
public static ModelCacheScope GetCache(IDataModel model) => activeScopes.TryGetValue(model, out var scope) ? scope : null;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using HavenSoft.HexManiac.Core.Models;
|
||||
using HavenSoft.HexManiac.Core.Models.Runs;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
@@ -33,6 +34,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
|
||||
private readonly StubCommand hideCommand;
|
||||
private readonly StubCommand stringToolCommand, tableToolCommand, tool3Command;
|
||||
private readonly HashSet<Action> deferredWork = new HashSet<Action>();
|
||||
private readonly IDataModel model;
|
||||
|
||||
private int selectedIndex;
|
||||
public int SelectedIndex {
|
||||
@@ -65,9 +67,11 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
|
||||
Debug.Assert(currentDeferralToken == null);
|
||||
currentDeferralToken = new StubDisposable {
|
||||
Dispose = () => {
|
||||
foreach (var action in deferredWork) action();
|
||||
deferredWork.Clear();
|
||||
currentDeferralToken = null;
|
||||
using (ModelCacheScope.CreateScope(model)) {
|
||||
foreach (var action in deferredWork) action();
|
||||
deferredWork.Clear();
|
||||
currentDeferralToken = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
return currentDeferralToken;
|
||||
@@ -79,6 +83,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
|
||||
public event EventHandler RequestMenuClose;
|
||||
|
||||
public ToolTray(IDataModel model, Selection selection, ChangeHistory<ModelDelta> history) {
|
||||
this.model = model;
|
||||
tools = new IToolViewModel[] {
|
||||
new PCSTool(model, selection, history, this),
|
||||
new TableTool(model, selection, history, this),
|
||||
|
||||
@@ -169,10 +169,12 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
|
||||
}
|
||||
return;
|
||||
} else if (run is IStreamRun stream) {
|
||||
var newContent = stream.SerializeRun();
|
||||
ignoreSelectionUpdates = true;
|
||||
using (new StubDisposable { Dispose = () => ignoreSelectionUpdates = false }) {
|
||||
TryUpdate(ref content, newContent, nameof(Content));
|
||||
using (ModelCacheScope.CreateScope(model)) {
|
||||
var newContent = stream.SerializeRun();
|
||||
ignoreSelectionUpdates = true;
|
||||
using (new StubDisposable { Dispose = () => ignoreSelectionUpdates = false }) {
|
||||
TryUpdate(ref content, newContent, nameof(Content));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1034,29 +1034,31 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
|
||||
}
|
||||
|
||||
private IReadOnlyList<AutoCompleteSelectionItem> GetAutocompleteOptions(IDataFormat originalFormat, string newText, int selectedIndex = -1) {
|
||||
if (originalFormat is Anchor anchor) originalFormat = anchor.OriginalFormat;
|
||||
if (newText.StartsWith(PointerStart.ToString())) {
|
||||
return Model.GetNewPointerAutocompleteOptions(newText, selectedIndex);
|
||||
} else if (newText.StartsWith(GotoMarker.ToString())) {
|
||||
return Model.GetNewPointerAutocompleteOptions(newText, selectedIndex);
|
||||
} else if (originalFormat is IntegerEnum intEnum) {
|
||||
var array = (ArrayRun)Model.GetNextRun(intEnum.Source);
|
||||
var segment = (ArrayRunEnumSegment)array.ElementContent[array.ConvertByteOffsetToArrayOffset(intEnum.Source).SegmentIndex];
|
||||
var options = segment.GetOptions(Model).Select(option => option + " "); // autocomplete needs to complete after selection, so add a space
|
||||
return AutoCompleteSelectionItem.Generate(options.Where(option => option.MatchesPartial(newText)), selectedIndex);
|
||||
} else if (originalFormat is EggSection || originalFormat is EggItem) {
|
||||
var eggRun = (EggMoveRun)Model.GetNextRun(((IDataFormatInstance)originalFormat).Source);
|
||||
var allOptions = eggRun.GetAutoCompleteOptions();
|
||||
return AutoCompleteSelectionItem.Generate(allOptions.Where(option => option.MatchesPartial(newText)), selectedIndex);
|
||||
} else if (originalFormat is PlmItem) {
|
||||
if (!newText.Contains(" ")) return AutoCompleteSelectionItem.Generate(Enumerable.Empty<string>(), -1);
|
||||
var moveName = newText.Substring(newText.IndexOf(' ')).Trim();
|
||||
if (moveName.Length == 0) return AutoCompleteSelectionItem.Generate(Enumerable.Empty<string>(), -1);
|
||||
var plmRun = (PLMRun)Model.GetNextRun(((IDataFormatInstance)originalFormat).Source);
|
||||
var allOptions = plmRun.GetAutoCompleteOptions(newText.Split(' ')[0]);
|
||||
return AutoCompleteSelectionItem.Generate(allOptions.Where(option => option.MatchesPartial(moveName)), selectedIndex);
|
||||
} else {
|
||||
throw new NotImplementedException();
|
||||
using (ModelCacheScope.CreateScope(Model)) {
|
||||
if (originalFormat is Anchor anchor) originalFormat = anchor.OriginalFormat;
|
||||
if (newText.StartsWith(PointerStart.ToString())) {
|
||||
return Model.GetNewPointerAutocompleteOptions(newText, selectedIndex);
|
||||
} else if (newText.StartsWith(GotoMarker.ToString())) {
|
||||
return Model.GetNewPointerAutocompleteOptions(newText, selectedIndex);
|
||||
} else if (originalFormat is IntegerEnum intEnum) {
|
||||
var array = (ArrayRun)Model.GetNextRun(intEnum.Source);
|
||||
var segment = (ArrayRunEnumSegment)array.ElementContent[array.ConvertByteOffsetToArrayOffset(intEnum.Source).SegmentIndex];
|
||||
var options = segment.GetOptions(Model).Select(option => option + " "); // autocomplete needs to complete after selection, so add a space
|
||||
return AutoCompleteSelectionItem.Generate(options.Where(option => option.MatchesPartial(newText)), selectedIndex);
|
||||
} else if (originalFormat is EggSection || originalFormat is EggItem) {
|
||||
var eggRun = (EggMoveRun)Model.GetNextRun(((IDataFormatInstance)originalFormat).Source);
|
||||
var allOptions = eggRun.GetAutoCompleteOptions();
|
||||
return AutoCompleteSelectionItem.Generate(allOptions.Where(option => option.MatchesPartial(newText)), selectedIndex);
|
||||
} else if (originalFormat is PlmItem) {
|
||||
if (!newText.Contains(" ")) return AutoCompleteSelectionItem.Generate(Enumerable.Empty<string>(), -1);
|
||||
var moveName = newText.Substring(newText.IndexOf(' ')).Trim();
|
||||
if (moveName.Length == 0) return AutoCompleteSelectionItem.Generate(Enumerable.Empty<string>(), -1);
|
||||
var plmRun = (PLMRun)Model.GetNextRun(((IDataFormatInstance)originalFormat).Source);
|
||||
var allOptions = plmRun.GetAutoCompleteOptions(newText.Split(' ')[0]);
|
||||
return AutoCompleteSelectionItem.Generate(allOptions.Where(option => option.MatchesPartial(moveName)), selectedIndex);
|
||||
} else {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1195,7 +1197,9 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
|
||||
// normal case: whether or not to accept the edit depends on the existing cell format
|
||||
var dataIndex = scroll.ViewPointToDataIndex(point);
|
||||
var completeEditOperation = new CompleteEditOperation(Model, dataIndex, underEdit.CurrentText, history.CurrentChange);
|
||||
underEdit.OriginalFormat.Visit(completeEditOperation, element.Value);
|
||||
using (ModelCacheScope.CreateScope(Model)) {
|
||||
underEdit.OriginalFormat.Visit(completeEditOperation, element.Value);
|
||||
}
|
||||
if (completeEditOperation.Result) {
|
||||
if (completeEditOperation.NewCell != null) {
|
||||
currentView[point.X, point.Y] = completeEditOperation.NewCell;
|
||||
@@ -1296,21 +1300,23 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
|
||||
ErrorInfo errorInfo;
|
||||
|
||||
// if it's an unnamed text/stream anchor, we have special logic for that
|
||||
if (underEdit.CurrentText == AnchorStart + PCSRun.SharedFormatString) {
|
||||
int count = Model.ConsiderResultsAsTextRuns(history.CurrentChange, new[] { index });
|
||||
if (count == 0) {
|
||||
errorInfo = new ErrorInfo("An anchor with nothing pointing to it must have a name.");
|
||||
using (ModelCacheScope.CreateScope(Model)) {
|
||||
if (underEdit.CurrentText == AnchorStart + PCSRun.SharedFormatString) {
|
||||
int count = Model.ConsiderResultsAsTextRuns(history.CurrentChange, new[] { index });
|
||||
if (count == 0) {
|
||||
errorInfo = new ErrorInfo("An anchor with nothing pointing to it must have a name.");
|
||||
} else {
|
||||
errorInfo = ErrorInfo.NoError;
|
||||
}
|
||||
} else if (underEdit.CurrentText == AnchorStart + PLMRun.SharedFormatString) {
|
||||
if (!PokemonModel.ConsiderAsPlmStream(Model, index, history.CurrentChange)) {
|
||||
errorInfo = new ErrorInfo("An anchor with nothing pointing to it must have a name.");
|
||||
} else {
|
||||
errorInfo = ErrorInfo.NoError;
|
||||
}
|
||||
} else {
|
||||
errorInfo = ErrorInfo.NoError;
|
||||
errorInfo = PokemonModel.ApplyAnchor(Model, history.CurrentChange, index, underEdit.CurrentText);
|
||||
}
|
||||
} else if (underEdit.CurrentText == AnchorStart + PLMRun.SharedFormatString) {
|
||||
if (!PokemonModel.ConsiderAsPlmStream(Model, index, history.CurrentChange)) {
|
||||
errorInfo = new ErrorInfo("An anchor with nothing pointing to it must have a name.");
|
||||
} else {
|
||||
errorInfo = ErrorInfo.NoError;
|
||||
}
|
||||
} else {
|
||||
errorInfo = PokemonModel.ApplyAnchor(Model, history.CurrentChange, index, underEdit.CurrentText);
|
||||
}
|
||||
|
||||
ClearEdits(point);
|
||||
|
||||
@@ -258,9 +258,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors {
|
||||
|
||||
if (fullValue == Pointer.NULL || (0 <= fullValue && fullValue < Model.Count)) {
|
||||
if (inArray) {
|
||||
using (ModelCacheScope.CreateScope(Model)) {
|
||||
UpdateArrayPointer((ArrayRun)currentRun, fullValue);
|
||||
}
|
||||
UpdateArrayPointer((ArrayRun)currentRun, fullValue);
|
||||
} else {
|
||||
Model.WritePointer(CurrentChange, memoryLocation, fullValue);
|
||||
Model.ObserveRunWritten(CurrentChange, new PointerRun(memoryLocation, sources));
|
||||
|
||||
Reference in New Issue
Block a user