From 7bbf669c10cbc1e5205e62f0128e1f37d3d37673 Mon Sep 17 00:00:00 2001 From: Benjamin Popp Date: Sat, 23 Jan 2021 23:07:01 -0600 Subject: [PATCH] Implement simple Tuple Stream Serialization --- .../Models/Runs/ArrayRunElementSegment.cs | 35 ++++++++ .../Tools/TupleArrayElementViewModel.cs | 88 ++++++++----------- src/HexManiac.Tests/TupleTests.cs | 20 +++++ 3 files changed, 91 insertions(+), 52 deletions(-) diff --git a/src/HexManiac.Core/Models/Runs/ArrayRunElementSegment.cs b/src/HexManiac.Core/Models/Runs/ArrayRunElementSegment.cs index 76ba04bb..97bf4b3e 100644 --- a/src/HexManiac.Core/Models/Runs/ArrayRunElementSegment.cs +++ b/src/HexManiac.Core/Models/Runs/ArrayRunElementSegment.cs @@ -329,12 +329,47 @@ namespace HavenSoft.HexManiac.Core.Models.Runs { Elements = content; if (content.Sum(seg => seg.BitWidth) > length * 8) throw new ArrayRunParseException($"{name}: tuple too long to fit in field!"); } + + public override string ToText(IDataModel rawData, int offset, bool deep = false) { + var result = "("; + var bitOffset = 0; + foreach (var segment in Elements) { + if (!string.IsNullOrEmpty(segment.SourceName)) { + // TODO + } else if (segment.BitWidth == 1) { + result += segment.Read(rawData, offset, bitOffset) == 1 ? "true" : "false"; + } else { + result += segment.Read(rawData, offset, bitOffset); + } + result += " "; + bitOffset += segment.BitWidth; + } + return result.Trim() + ")"; + } } public class TupleSegment { public string Name { get; } public string SourceName { get; } public int BitWidth { get; } public TupleSegment(string name, int width, string sourceName = null) => (Name, BitWidth, SourceName) = (name, width, sourceName); + public int Read(IDataModel model, int start, int bitOffset) { + var requiredByteLength = (bitOffset + BitWidth + 7) / 8; + if (requiredByteLength > 4) return 0; + var bitArray = model.ReadMultiByteValue(start, requiredByteLength); + bitArray >>= bitOffset; + bitArray &= (1 << BitWidth) - 1; + return bitArray; + } + public void Write(IDataModel model, ModelDelta token, int start, int bitOffset, int value) { + var requiredByteLength = (bitOffset + BitWidth+ 7) / 8; + if (requiredByteLength > 4) return; + var bitArray = model.ReadMultiByteValue(start, requiredByteLength); + var mask = (1 << BitWidth) - 1; + value &= mask; + bitArray &= ~(mask << bitOffset); + bitArray |= value << bitOffset; + model.WriteMultiByteValue(start, requiredByteLength, token, bitArray); + } } public class ArrayRunColorSegment : ArrayRunElementSegment { diff --git a/src/HexManiac.Core/ViewModels/Tools/TupleArrayElementViewModel.cs b/src/HexManiac.Core/ViewModels/Tools/TupleArrayElementViewModel.cs index a0a98366..e8452234 100644 --- a/src/HexManiac.Core/ViewModels/Tools/TupleArrayElementViewModel.cs +++ b/src/HexManiac.Core/ViewModels/Tools/TupleArrayElementViewModel.cs @@ -1,10 +1,10 @@ using HavenSoft.HexManiac.Core.Models; using HavenSoft.HexManiac.Core.Models.Runs; -using HavenSoft.HexManiac.Core.ViewModels.DataFormats; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; +using System.Diagnostics; using System.Linq; namespace HavenSoft.HexManiac.Core.ViewModels.Tools { @@ -24,7 +24,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { var bitOffset = 0; for (int i = 0; i < tupleItem.Elements.Count; i++) { if (tupleItem.Elements[i].BitWidth == 1) { - Children.Add(new CheckBoxTupleElementViewModel(viewPort, start, bitOffset, tupleItem.Elements[i].Name)); + Children.Add(new CheckBoxTupleElementViewModel(viewPort, start, bitOffset, tupleItem.Elements[i])); } else if (!string.IsNullOrEmpty(tupleItem.Elements[i].SourceName)) { Children.Add(new EnumTupleElementViewModel(viewPort, start, bitOffset, tupleItem.Elements[i])); } else { @@ -56,37 +56,23 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { } public class NumericTupleElementViewModel : ViewModelCore, ITupleElementViewModel { - protected ViewPort ViewPort { get; } - public string Name { get; } + private readonly TupleSegment seg; + private readonly ViewPort viewPort; + public string Name => seg.Name; public int BitOffset { get; } - public int BitLength { get; } + public int BitLength => seg.BitWidth; public int Start { get; private set; } public int Content { - get { - var requiredByteLength = (BitOffset + BitLength + 7) / 8; - if (requiredByteLength > 4) return 0; - var bitArray = ViewPort.Model.ReadMultiByteValue(Start, requiredByteLength); - bitArray >>= BitOffset; - bitArray &= (1 << BitLength) - 1; - return bitArray; - } + get => seg.Read(viewPort.Model, Start, BitOffset); set { - var requiredByteLength = (BitOffset + BitLength + 7) / 8; - if (requiredByteLength > 4) return; - var bitArray = ViewPort.Model.ReadMultiByteValue(Start, requiredByteLength); - var mask = (1 << BitLength) - 1; - value &= mask; - bitArray &= ~(mask << BitOffset); - bitArray |= value << BitOffset; - ViewPort.Model.WriteMultiByteValue(Start, requiredByteLength, ViewPort.CurrentChange, bitArray); + seg.Write(viewPort.Model, viewPort.CurrentChange, Start, BitOffset, value); NotifyPropertyChanged(); } } public NumericTupleElementViewModel(ViewPort viewPort, int start, int bitOffset, TupleSegment segment) { - ViewPort = viewPort; - (Name, Start, BitOffset, BitLength) = (segment.Name, start, bitOffset, segment.BitWidth); + (this.viewPort, seg, Start, BitOffset) = (viewPort, segment, start, bitOffset); } public virtual bool TryCopy(ITupleElementViewModel other) { @@ -103,37 +89,26 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { } public class CheckBoxTupleElementViewModel : ViewModelCore, ITupleElementViewModel { + private readonly TupleSegment seg; private readonly ViewPort viewPort; - public string Name { get; } + public string Name => seg.Name; public int BitOffset { get; } public int BitLength => 1; public int Start { get; private set; } public bool IsChecked { - get { - var requiredByteLength = (BitOffset + BitLength + 7) / 8; - if (requiredByteLength > 4) return false; - var bitArray = viewPort.Model.ReadMultiByteValue(Start, requiredByteLength); - bitArray >>= BitOffset; - return (bitArray & 1) == 1; - } + get => seg.Read(viewPort.Model, Start, BitOffset) == 1; set { - var requiredByteLength = (BitOffset + BitLength + 7) / 8; - if (requiredByteLength > 4) return; - var bitArray = viewPort.Model.ReadMultiByteValue(Start, requiredByteLength); - if (value) { - bitArray |= 1 << BitOffset; - } else { - bitArray &= ~(1 << BitOffset); - } - viewPort.Model.WriteMultiByteValue(Start, requiredByteLength, viewPort.CurrentChange, bitArray); + var bit = value ? 1 : 0; + seg.Write(viewPort.Model, viewPort.CurrentChange, Start, BitOffset, bit); NotifyPropertyChanged(); } } - public CheckBoxTupleElementViewModel(ViewPort viewPort, int start, int bitOffset, string name) { + public CheckBoxTupleElementViewModel(ViewPort viewPort, int start, int bitOffset, TupleSegment segment) { + Debug.Assert(segment.BitWidth == 1, "Checkboxes should not be constructed from segments with BitWidth other than 1!"); this.viewPort = viewPort; - (Name, Start, BitOffset) = (name, start, bitOffset); + (seg, Start, BitOffset) = (segment, start, bitOffset); } public bool TryCopy(ITupleElementViewModel other) { @@ -148,29 +123,38 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { } } - public class EnumTupleElementViewModel : NumericTupleElementViewModel { - public string EnumName { get; } + public class EnumTupleElementViewModel : ViewModelCore, ITupleElementViewModel { + private readonly TupleSegment seg; + private readonly ViewPort viewPort; + public string Name => seg.Name; + public int BitOffset { get; } + public int BitLength => seg.BitWidth; + public int Start { get; private set; } + public string EnumName => seg.SourceName; - public IReadOnlyList Options => ArrayRunEnumSegment.GetOptions(ViewPort.Model, EnumName).ToList(); + public IReadOnlyList Options => ArrayRunEnumSegment.GetOptions(viewPort.Model, EnumName).ToList(); public int SelectedIndex { - get => Content; + get => seg.Read(viewPort.Model, Start, BitOffset); set { - Content = value; + seg.Write(viewPort.Model, viewPort.CurrentChange, Start, BitOffset, value); NotifyPropertyChanged(); } } - public EnumTupleElementViewModel(ViewPort viewPort, int start, int bitOffset, TupleSegment segment) : base(viewPort, start, bitOffset, segment) { - EnumName = segment.SourceName; + public EnumTupleElementViewModel(ViewPort viewPort, int start, int bitOffset, TupleSegment segment) { + (this.viewPort, Start, BitOffset, seg) = (viewPort, start, BitOffset, segment); } - public override bool TryCopy(ITupleElementViewModel other) { + public bool TryCopy(ITupleElementViewModel other) { if (!(other is EnumTupleElementViewModel that)) return false; if (EnumName != that.EnumName) return false; + if (Name != that.Name) return false; + if (BitOffset != that.BitOffset) return false; + if (BitLength != that.BitLength) return false; - if (!base.TryCopy(other)) return false; - + Start = that.Start; + NotifyPropertyChanged(nameof(Start)); NotifyPropertyChanged(nameof(SelectedIndex)); return true; } diff --git a/src/HexManiac.Tests/TupleTests.cs b/src/HexManiac.Tests/TupleTests.cs index e8b1b667..5c80f825 100644 --- a/src/HexManiac.Tests/TupleTests.cs +++ b/src/HexManiac.Tests/TupleTests.cs @@ -1,6 +1,7 @@ using HavenSoft.HexManiac.Core; using HavenSoft.HexManiac.Core.Models.Runs; using HavenSoft.HexManiac.Core.ViewModels.Tools; +using System; using System.Linq; using Xunit; @@ -124,6 +125,25 @@ namespace HavenSoft.HexManiac.Tests { Assert.Equal(0b1000, Model[0]); } + [Fact] + public void TupleStream_Serialize_ElementsAreWrappedInParenthesis() { + Model[10] = 0xFF; + Model[11] = 0xFF; + ViewPort.Edit("^table[value.|t|a:|b. next.|h]!FFFF "); + + var text = ((IStreamRun)Model.GetNextRun(0)).SerializeRun(); + + var lines = text.Split(Environment.NewLine); + Assert.Equal("(0 false), 00", lines[0]); + Assert.Equal(5, lines.Length); + } + + // TODO test that we can serialize tuple enum segments + // TODO test that we can read from streams to tuples + // TODO check that tablestream dependencies works right for tuple enums + // TODO tuple segments with empty names do not appear in the table tool + // TODO tuple segments with empty names do not appear in the stream + private TupleArrayElementViewModel TupleTable => (TupleArrayElementViewModel)ViewPort.Tools.TableTool.Children.Where(child => child is TupleArrayElementViewModel).Single(); } }