BizHawk/BizHawk.Emulation.Common/TextState.cs

149 lines
3.7 KiB
C#

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Newtonsoft.Json;
namespace BizHawk.Emulation.Common
{
// managed counterpart to unmanaged serialization code in GB and WSWAN cores
// T is a class that has any other data you want to serialize in it
public class TextState<T>
where T : new()
{
public TextState()
{
ExtraData = new T();
}
public class Node
{
public readonly Dictionary<string, byte[]> Data = new Dictionary<string, byte[]>();
public readonly Dictionary<string, Node> Objects = new Dictionary<string, Node>();
// methods named "ShouldSerialize*" are detected and dynamically invoked by JSON.NET
// if they return false during serialization, the field/prop is omitted from the created json
// ReSharper disable once UnusedMember.Global
public bool ShouldSerializeData()
{
return Data.Count > 0;
}
// ReSharper disable once UnusedMember.Global
public bool ShouldSerializeObjects()
{
return Objects.Count > 0;
}
}
public readonly Node Root = new Node();
[JsonIgnore]
private Stack<Node> Nodes;
[JsonIgnore]
private Node Current => Nodes.Peek();
public void Prepare()
{
Nodes = new Stack<Node>();
Nodes.Push(Root);
}
public void Save(IntPtr data, int length, string name)
{
byte[] d = new byte[length];
Marshal.Copy(data, d, 0, length);
Current.Data.Add(name, d); // will except for us if the key is already present
}
/// <exception cref="InvalidOperationException"><paramref name="length"/> does not match the length of the data saved as <paramref name="name"/></exception>
public void Load(IntPtr data, int length, string name)
{
byte[] d = Current.Data[name];
if (length != d.Length)
{
throw new InvalidOperationException();
}
Marshal.Copy(d, 0, data, length);
}
public void EnterSectionSave(string name)
{
Node next = new Node();
Current.Objects.Add(name, next);
Nodes.Push(next);
}
public void EnterSectionLoad(string name)
{
Node next = Current.Objects[name];
Nodes.Push(next);
}
public void EnterSection(string name)
{
// works for either save or load, but as a consequence cannot report intelligent
// errors about section name mismatches
Current.Objects.TryGetValue(name, out var next);
if (next == null)
{
next = new Node();
Current.Objects.Add(name, next);
}
Nodes.Push(next);
}
/// <exception cref="InvalidOperationException"><paramref name="name"/> doesn't match the section being closed</exception>
public void ExitSection(string name)
{
Node last = Nodes.Pop();
if (Current.Objects[name] != last)
{
throw new InvalidOperationException();
}
}
// other data besides the core
public readonly T ExtraData;
public TextStateFPtrs GetFunctionPointersSave()
{
return new TextStateFPtrs
{
Save = Save,
Load = null,
EnterSection = EnterSectionSave,
ExitSection = ExitSection
};
}
public TextStateFPtrs GetFunctionPointersLoad()
{
return new TextStateFPtrs
{
Save = null,
Load = Load,
EnterSection = EnterSectionLoad,
ExitSection = ExitSection
};
}
}
[StructLayout(LayoutKind.Sequential)]
public struct TextStateFPtrs
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void DataFunction(IntPtr data, int length, string name);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void SectionFunction(string name);
public DataFunction Save;
public DataFunction Load;
public SectionFunction EnterSection;
public SectionFunction ExitSection;
}
}