Apple II - refactor states to use Serializer (#1859)

* apple II - poc to redo binary states to use the Serializer and pass it into virtu

* virtu - savestate logic for Video

* AppleII - serialize more components with AppleSerializer

* cleanup

* refactor MachineEvents to store delegates to call and have the calling code interact with an enum, prep work for serializing this class

* apple II - first attempt at serializing Machine events

* cleanup

* add isvblank to savestates

* put more things in savestates, cleanup

* cleanup

* cleanup, save more things

* cleanup, fixes

* cleanup, save more things

* virtu - savestate fixes

* cleanup

* save CurrentDisk in savestates
This commit is contained in:
adelikat 2020-02-22 09:53:56 -06:00 committed by GitHub
parent 9831ca59bd
commit 33ad336b6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 533 additions and 1048 deletions

View File

@ -8,7 +8,8 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
public ControllerDefinition ControllerDefinition => AppleIIController; public ControllerDefinition ControllerDefinition => AppleIIController;
public int Frame { get; private set; } private int _frame;
public int Frame { get => _frame; set => _frame = value; }
public string SystemId => "AppleII"; public string SystemId => "AppleII";

View File

@ -4,7 +4,12 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
{ {
public partial class AppleII : IInputPollable public partial class AppleII : IInputPollable
{ {
public int LagCount { get; set; } private int _lagcount;
public int LagCount
{
get => _lagcount;
set => _lagcount = value;
}
public bool IsLagFrame public bool IsLagFrame
{ {

View File

@ -1,181 +1,97 @@
using System; using System.IO;
using System.IO; using BizHawk.Common;
using BizHawk.Emulation.Common; using BizHawk.Emulation.Common;
using Jellyfish.Virtu; using Jellyfish.Virtu;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace BizHawk.Emulation.Cores.Computers.AppleII namespace BizHawk.Emulation.Cores.Computers.AppleII
{ {
public partial class AppleII : ITextStatable public partial class AppleII : ITextStatable
{ {
private class CoreConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Components);
}
public override bool CanRead => true;
public override bool CanWrite => false;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return CreateSerializer().Deserialize<Components>(reader);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
private void SerializeEverything(JsonWriter w)
{
// this is much faster than other possibilities for serialization
w.WriteStartObject();
w.WritePropertyName(nameof(Frame));
w.WriteValue(Frame);
w.WritePropertyName(nameof(LagCount));
w.WriteValue(LagCount);
w.WritePropertyName(nameof(IsLagFrame));
w.WriteValue(IsLagFrame);
w.WritePropertyName(nameof(CurrentDisk));
w.WriteValue(CurrentDisk);
w.WritePropertyName("PreviousDiskPressed");
w.WriteValue(_prevPressed);
w.WritePropertyName("NextDiskPressed");
w.WriteValue(_nextPressed);
w.WritePropertyName("Core");
CreateSerializer().Serialize(w, _machine);
w.WriteEndObject();
}
private void DeserializeEverything(JsonReader r)
{
var o = (OtherData)_ser.Deserialize(r, typeof(OtherData));
Frame = o.Frame;
LagCount = o.LagCount;
IsLagFrame = o.IsLagFrame;
CurrentDisk = o.CurrentDisk;
_machine = o.Core;
_prevPressed = o.PreviousDiskPressed;
_nextPressed = o.NextDiskPressed;
// since _machine was replaced, we need to reload settings from frontend
PutSettings(_settings);
}
public class OtherData
{
public int Frame;
public int LagCount;
public bool IsLagFrame;
public int CurrentDisk;
public bool PreviousDiskPressed;
public bool NextDiskPressed;
public Components Core;
}
private void InitSaveStates()
{
_ser.Converters.Add(new CoreConverter());
}
private readonly JsonSerializer _ser = new JsonSerializer();
public void SaveStateText(TextWriter writer) public void SaveStateText(TextWriter writer)
{ {
SerializeEverything(new JsonTextWriter(writer) { Formatting = Formatting.None }); SyncState(new AppleSerializer(writer));
} }
public void LoadStateText(TextReader reader) public void LoadStateText(TextReader reader)
{ {
DeserializeEverything(new JsonTextReader(reader)); SyncState(new AppleSerializer(reader));
} }
/*
* These are horrible; the LoadStateBinary() takes over 10x as long as LoadStateText()
* Until we figure out why JSON.NET's BSONwriter sucks and how to fix it, stick with text-as-binary
public void SaveStateBinary(BinaryWriter writer) public void SaveStateBinary(BinaryWriter writer)
{ {
SerializeEverything(new BsonWriter(writer)); SyncState(new AppleSerializer(writer));
} }
public void LoadStateBinary(BinaryReader reader) public void LoadStateBinary(BinaryReader reader)
{ {
DeserializeEverything(new BsonReader(reader)); SyncState(new AppleSerializer(reader));
}
*/
/*
public void SaveStateBinary(BinaryWriter writer)
{
var tw = new StreamWriter(writer.BaseStream, new System.Text.UTF8Encoding(false));
SaveStateText(tw);
tw.Flush();
}
public void LoadStateBinary(BinaryReader reader)
{
var tr = new StreamReader(reader.BaseStream, System.Text.Encoding.UTF8);
LoadStateText(tr);
}*/
// these homemade classes edge out the stock ones slightly, but need BufferedStream to not be bad
public void SaveStateBinary(BinaryWriter writer)
{
var buffer = new BufferedStream(writer.BaseStream, 16384);
var bw2 = new BinaryWriter(buffer);
SerializeEverything(new LBW(bw2));
bw2.Flush();
buffer.Flush();
}
public void LoadStateBinary(BinaryReader reader)
{
var buffer = new BufferedStream(reader.BaseStream, 16384);
var br2 = new BinaryReader(buffer);
DeserializeEverything(new LBR(br2));
} }
public byte[] SaveStateBinary() public byte[] SaveStateBinary()
{ {
// our savestate array can be of varying sizes, so this can't be too clever // our savestate array can be of varying sizes, so this can't be too clever
var stream = new MemoryStream(); using var stream = new MemoryStream();
var writer = new BinaryWriter(stream); using var writer = new BinaryWriter(stream);
SaveStateBinary(writer); SaveStateBinary(writer);
writer.Flush(); writer.Flush();
return stream.ToArray(); return stream.ToArray();
} }
private static JsonSerializer CreateSerializer() private void SyncState(AppleSerializer ser)
{ {
// TODO: converters could be cached for speedup int version = 2;
ser.BeginSection(nameof(AppleII));
ser.Sync(nameof(version), ref version);
ser.Sync("Frame", ref _frame);
ser.Sync("Lag", ref _lagcount);
ser.Sync("PrevDiskPressed", ref _prevPressed);
ser.Sync("NextDiskPressed", ref _nextPressed);
ser.Sync("CurrentDisk", ref _currentDisk);
var ser = new JsonSerializer ser.BeginSection("Events");
_machine.Events.Sync(ser);
ser.EndSection();
ser.BeginSection("Cpu");
_machine.Cpu.Sync(ser);
ser.EndSection();
ser.BeginSection("Video");
_machine.Video.Sync(ser);
ser.EndSection();
ser.BeginSection("Memory");
_machine.Memory.Sync(ser);
ser.EndSection();
ser.BeginSection("NoSlotClock");
_machine.NoSlotClock.Sync(ser);
ser.EndSection();
ser.BeginSection("DiskIIController");
_machine.DiskIIController.Sync(ser);
ser.EndSection();
ser.EndSection();
}
public class AppleSerializer : Serializer, IComponentSerializer
{
public AppleSerializer(BinaryReader br) : base(br)
{ {
TypeNameHandling = TypeNameHandling.Auto, }
PreserveReferencesHandling = PreserveReferencesHandling.All, // leaving out Array is a very important problem, and means that we can't rely on a directly shared array to work.
ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
};
ser.Converters.Add(new TypeTypeConverter(new[] public AppleSerializer(BinaryWriter bw) : base(bw)
{ {
// all expected Types to convert are either in this assembly or mscorlib }
typeof(Memory).Assembly,
typeof(object).Assembly
}));
ser.Converters.Add(new DelegateConverter()); public AppleSerializer(TextReader tr) : base(tr)
ser.Converters.Add(new ArrayConverter()); {
}
var cr = new DefaultContractResolver(); public AppleSerializer(TextWriter tw) : base(tw)
cr.DefaultMembersSearchFlags |= System.Reflection.BindingFlags.NonPublic; {
ser.ContractResolver = cr; }
return ser;
} }
} }
} }

View File

@ -57,7 +57,6 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
SetCallbacks(); SetCallbacks();
InitSaveStates();
SetupMemoryDomains(); SetupMemoryDomains();
PutSettings(settings ?? new Settings()); PutSettings(settings ?? new Settings());
} }
@ -72,7 +71,14 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
private readonly byte[] _appleIIRom; private readonly byte[] _appleIIRom;
private readonly byte[] _diskIIRom; private readonly byte[] _diskIIRom;
public int CurrentDisk { get; private set; } private int _currentDisk;
public int CurrentDisk
{
get => _currentDisk;
set => _currentDisk = value;
}
public int DiskCount => _romSet.Count; public int DiskCount => _romSet.Count;
public void SetDisk(int discNum) public void SetDisk(int discNum)

View File

@ -7,11 +7,6 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
/// </summary> /// </summary>
public sealed class Components public sealed class Components
{ {
/// <summary>
/// for deserialization only!!
/// </summary>
public Components() { }
public Components(byte[] appleIIe, byte[] diskIIRom) public Components(byte[] appleIIe, byte[] diskIIRom)
{ {
Events = new MachineEvents(); Events = new MachineEvents();
@ -19,6 +14,9 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
Cpu = new Cpu(Memory); Cpu = new Cpu(Memory);
Video = new Video(Events, Memory); Video = new Video(Events, Memory);
NoSlotClock = new NoSlotClock(Video);
DiskIIController = new DiskIIController(Video, diskIIRom);
var emptySlot = new EmptyPeripheralCard(Video); var emptySlot = new EmptyPeripheralCard(Video);
// Necessary because of tangling dependencies between memory and video classes // Necessary because of tangling dependencies between memory and video classes
@ -28,7 +26,7 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
new EmptyCassetteComponent(), new EmptyCassetteComponent(),
new Speaker(Events, Cpu), new Speaker(Events, Cpu),
Video, Video,
new NoSlotClock(Video), NoSlotClock,
emptySlot, emptySlot,
emptySlot, emptySlot,
emptySlot, emptySlot,
@ -42,9 +40,13 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
Video.Reset(); Video.Reset();
} }
public MachineEvents Events { get; set; } public MachineEvents Events { get; }
public Memory Memory { get; private set; } public Memory Memory { get; }
public Cpu Cpu { get; private set; } public Cpu Cpu { get; }
public Video Video { get; private set; } public Video Video { get; }
// Only needed for convenience of savestate syncing, else the memory component needs to do it
public NoSlotClock NoSlotClock { get; }
public DiskIIController DiskIIController { get; }
} }
} }

View File

@ -1,334 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json;
namespace BizHawk.Emulation.Cores.Computers.AppleII
{
public class ArrayConverter : JsonConverter
{
// JSON.NET cannot, when reading, use PreserveReferencesHandling on arrays, although it fully supports it on writing.
// Doing so while being able to fully preserve circular references would require storing the length of the array,
// or reading ahead in the JSON to compute the length. For arrays that could contain reference types, we choose the latter.
// For arrays of primitive types, there is no issue.
// TODO: on serialization, the type of the object is available, but is the expected type (ie, the one that we'll be fed during deserialization) available?
// need this to at least detect covariance cases...
public override bool CanConvert(Type objectType)
{
if (!typeof(Array).IsAssignableFrom(objectType))
{
return false;
}
if (objectType.GetArrayRank() > 1)
{
throw new NotImplementedException();
}
return true;
}
public override bool CanRead => true;
public override bool CanWrite => true;
private readonly JsonSerializer _bareSerializer = new JsonSerializer(); // full default settings, separate context
// ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local
private static void ReadExpectType(JsonReader reader, JsonToken expected)
{
if (!reader.Read())
{
throw new InvalidOperationException();
}
if (reader.TokenType != expected)
{
throw new InvalidOperationException();
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
if (reader.TokenType != JsonToken.StartObject)
{
throw new InvalidOperationException();
}
ReadExpectType(reader, JsonToken.PropertyName);
string prop = reader.Value.ToString();
ReadExpectType(reader, JsonToken.String);
string id = reader.Value.ToString();
if (prop == "$ref")
{
object ret = serializer.ReferenceResolver.ResolveReference(serializer, id);
ReadExpectType(reader, JsonToken.EndObject);
return ret;
}
if (prop == "$id")
{
ReadExpectType(reader, JsonToken.PropertyName);
prop = reader.Value.ToString();
if (prop == "$length") // complex array
{
ReadExpectType(reader, JsonToken.Integer);
int length = Convert.ToInt32(reader.Value);
ReadExpectType(reader, JsonToken.PropertyName);
if (reader.Value.ToString() != "$values")
throw new InvalidOperationException();
Type elementType = objectType.GetElementType();
// ReSharper disable once AssignNullToNotNullAttribute
Array ret = Array.CreateInstance(elementType, length);
// must register reference before deserializing elements to handle possible circular references
serializer.ReferenceResolver.AddReference(serializer, id, ret);
int index = 0;
ReadExpectType(reader, JsonToken.StartArray);
while (true)
{
if (!reader.Read())
throw new InvalidOperationException();
if (reader.TokenType == JsonToken.EndArray)
break;
ret.SetValue(serializer.Deserialize(reader, elementType), index++);
}
ReadExpectType(reader, JsonToken.EndObject);
return ret;
}
if (prop == "$values") // simple array
{
if (!reader.Read())
{
throw new InvalidOperationException();
}
object ret = _bareSerializer.Deserialize(reader, objectType);
// OK to add this after deserializing, as arrays of primitive types can't contain backrefs
serializer.ReferenceResolver.AddReference(serializer, id, ret);
ReadExpectType(reader, JsonToken.EndObject);
return ret;
}
throw new InvalidOperationException();
}
throw new InvalidOperationException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (serializer.ReferenceResolver.IsReferenced(serializer, value))
{
writer.WriteStartObject();
writer.WritePropertyName("$ref");
writer.WriteValue(serializer.ReferenceResolver.GetReference(serializer, value));
writer.WriteEndObject();
}
else
{
writer.WriteStartObject();
writer.WritePropertyName("$id");
writer.WriteValue(serializer.ReferenceResolver.GetReference(serializer, value));
var elementType = value.GetType().GetElementType();
if (elementType?.IsPrimitive ?? false)
{
writer.WritePropertyName("$values");
_bareSerializer.Serialize(writer, value);
}
else
{
var array = (Array)value;
writer.WritePropertyName("$length");
writer.WriteValue(array.Length);
writer.WritePropertyName("$values");
writer.WriteStartArray();
foreach (object o in array)
{
serializer.Serialize(writer, o, elementType);
}
writer.WriteEndArray();
}
writer.WriteEndObject();
}
}
}
public class TypeTypeConverter : JsonConverter
{
// serialize and deserialize types, ignoring assembly entirely and only using namespace+typename
// all types, including generic type arguments to supplied types, must be in one of the declared assemblies (only checked on read!)
// the main goal here is to have something with a slight chance of working across versions
public TypeTypeConverter(IEnumerable<Assembly> ass)
{
_assemblies = ass.ToList();
}
private readonly List<Assembly> _assemblies;
private readonly Dictionary<string, Type> _readLookup = new Dictionary<string, Type>();
public override bool CanConvert(Type objectType)
{
return typeof(Type).IsAssignableFrom(objectType);
}
public override bool CanRead => true;
public override bool CanWrite => true;
private Type GetType(string name)
{
if (!_readLookup.TryGetValue(name, out var ret))
{
ret = _assemblies.Select(ass => ass.GetType(name, false)).Single(t => t != null);
_readLookup.Add(name, ret);
}
return ret;
}
private static string GetName(Type type)
{
return $"{type.Namespace}.{type.Name}";
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
if (reader.TokenType == JsonToken.String)
{
return GetType(reader.Value.ToString());
}
if (reader.TokenType == JsonToken.StartArray) // full generic
{
List<string> values = serializer.Deserialize<List<string>>(reader);
return GetType(values[0]).MakeGenericType(values.Skip(1).Select(GetType).ToArray());
}
throw new InvalidOperationException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var type = (Type)value;
if (type.IsGenericType && !type.IsGenericTypeDefinition)
{
writer.WriteStartArray();
writer.WriteValue(GetName(type));
foreach (var t in type.GetGenericArguments())
{
writer.WriteValue(GetName(t));
}
writer.WriteEndArray();
}
else
{
writer.WriteValue(GetName(type));
}
}
}
public class DelegateConverter : JsonConverter
{
// caveats: if used on anonymous delegates and/or closures, brittle to name changes in the generated classes and methods
// brittle to type name changes in general
// must be serialized in tree with any real classes referred to by closures
// CAN NOT preserve reference equality of the delegates themselves, because the delegate must be created with
// target in one shot, with no possibility to change the target later. We preserve references to targets,
// and lose the ability to preserve references to delegates.
// TODO: much of this could be made somewhat smarter and more resilient
public override bool CanConvert(Type objectType)
{
return typeof(Delegate).IsAssignableFrom(objectType);
}
public override bool CanRead => true;
public override bool CanWrite => true;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var slug = serializer.Deserialize<Slug>(reader);
return slug?.GetDelegate();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var slug = new Slug((Delegate)value);
serializer.Serialize(writer, slug);
}
private class Slug
{
// ReSharper disable once MemberCanBePrivate.Local
// ReSharper disable once FieldCanBeMadeReadOnly.Local
public Type DelegateType;
// ReSharper disable once MemberCanBePrivate.Local
// ReSharper disable once FieldCanBeMadeReadOnly.Local
public Type MethodDeclaringType;
// ReSharper disable once MemberCanBePrivate.Local
// ReSharper disable once FieldCanBeMadeReadOnly.Local
public string MethodName;
// ReSharper disable once MemberCanBePrivate.Local
// ReSharper disable once FieldCanBeMadeReadOnly.Local
public List<Type> MethodParameters;
// ReSharper disable once MemberCanBePrivate.Local
// ReSharper disable once FieldCanBeMadeReadOnly.Local
public object Target;
public Delegate GetDelegate()
{
var mi = MethodDeclaringType.GetMethod(
MethodName,
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static,
null,
MethodParameters.ToArray(),
null);
// ReSharper disable once AssignNullToNotNullAttribute
return Delegate.CreateDelegate(DelegateType, Target, mi);
}
public Slug() { }
public Slug(Delegate d)
{
DelegateType = d.GetType();
MethodDeclaringType = d.Method.DeclaringType;
MethodName = d.Method.Name;
MethodParameters = d.Method.GetParameters().Select(p => p.ParameterType).ToList();
Target = d.Target;
}
}
}
}

View File

@ -1,238 +0,0 @@
using System;
using System.IO;
using Newtonsoft.Json;
namespace BizHawk.Emulation.Cores.Computers.AppleII
{
// barebones classes for writing and reading a simple bson-like format, used to gain a bit of speed in Apple II savestates
internal enum LBTOK : byte
{
Null,
Undefined,
StartArray,
EndArray,
StartObject,
EndObject,
Property,
S8,
U8,
S16,
U16,
S32,
U32,
S64,
U64,
False,
True,
String,
F32,
F64,
ByteArray,
}
public class LBR : JsonReader
{
private readonly BinaryReader _r;
private object _v;
private JsonToken _t;
public LBR(BinaryReader reader)
{
_r = reader;
}
public override void Close()
{
}
// as best as I can tell, the serializers refer to depth, but don't actually need to work except when doing certain error recovery
public override int Depth => 0;
public override string Path => throw new NotImplementedException();
public override Type ValueType => _v?.GetType();
public override JsonToken TokenType => _t;
public override object Value => _v;
public override bool Read()
{
LBTOK l = (LBTOK)_r.ReadByte();
switch (l)
{
case LBTOK.StartArray: _t = JsonToken.StartArray; _v = null; break;
case LBTOK.EndArray: _t = JsonToken.EndArray; _v = null; break;
case LBTOK.StartObject: _t = JsonToken.StartObject; _v = null; break;
case LBTOK.EndObject: _t = JsonToken.EndObject; _v = null; break;
case LBTOK.Null: _t = JsonToken.Null; _v = null; break;
case LBTOK.False: _t = JsonToken.Boolean; _v = false; break;
case LBTOK.True: _t = JsonToken.Boolean; _v = true; break;
case LBTOK.Property: _t = JsonToken.PropertyName; _v = _r.ReadString(); break;
case LBTOK.Undefined: _t = JsonToken.Undefined; _v = null; break;
case LBTOK.S8: _t = JsonToken.Integer; _v = _r.ReadSByte(); break;
case LBTOK.U8: _t = JsonToken.Integer; _v = _r.ReadByte(); break;
case LBTOK.S16: _t = JsonToken.Integer; _v = _r.ReadInt16(); break;
case LBTOK.U16: _t = JsonToken.Integer; _v = _r.ReadUInt16(); break;
case LBTOK.S32: _t = JsonToken.Integer; _v = _r.ReadInt32(); break;
case LBTOK.U32: _t = JsonToken.Integer; _v = _r.ReadUInt32(); break;
case LBTOK.S64: _t = JsonToken.Integer; _v = _r.ReadInt64(); break;
case LBTOK.U64: _t = JsonToken.Integer; _v = _r.ReadUInt64(); break;
case LBTOK.String: _t = JsonToken.String; _v = _r.ReadString(); break;
case LBTOK.F32: _t = JsonToken.Float; _v = _r.ReadSingle(); break;
case LBTOK.F64: _t = JsonToken.Float; _v = _r.ReadDouble(); break;
case LBTOK.ByteArray: _t = JsonToken.Bytes; _v = _r.ReadBytes(_r.ReadInt32()); break;
default:
throw new InvalidOperationException();
}
return true;
}
public override byte[] ReadAsBytes()
{
if (!Read() || _t != JsonToken.Bytes)
{
return null;
}
return (byte[])_v;
}
public override DateTime? ReadAsDateTime()
{
throw new NotImplementedException();
}
public override DateTimeOffset? ReadAsDateTimeOffset()
{
throw new NotImplementedException();
}
public override decimal? ReadAsDecimal()
{
throw new NotImplementedException();
}
public override int? ReadAsInt32()
{
// TODO: speed this up if needed
if (!Read())
{
return null;
}
switch (_t)
{
case JsonToken.Null:
return null;
case JsonToken.Integer:
case JsonToken.Float:
return Convert.ToInt32(_v);
case JsonToken.String:
if (int.TryParse(_v.ToString(), out var i))
{
return i;
}
return null;
default:
return null;
}
}
public override string ReadAsString()
{
if (!Read())
{
return null;
}
switch (_t)
{
case JsonToken.Null:
return null;
case JsonToken.Float:
case JsonToken.Integer:
case JsonToken.Boolean:
case JsonToken.String:
return _v.ToString();
default:
return null;
}
}
}
public class LBW : JsonWriter
{
private readonly BinaryWriter w;
private void WT(LBTOK t)
{
w.Write((byte)t);
}
public LBW(BinaryWriter w)
{
this.w = w;
}
public override void Flush()
{
w.Flush();
}
public override void Close()
{
}
public override void WriteValue(bool value) { WT(value ? LBTOK.True : LBTOK.False); }
public override void WriteValue(sbyte value) { WT(LBTOK.S8); w.Write(value); }
public override void WriteValue(byte value) { WT(LBTOK.U8); w.Write(value); }
public override void WriteValue(short value) { WT(LBTOK.S16); w.Write(value); }
public override void WriteValue(ushort value) { WT(LBTOK.U16); w.Write(value); }
public override void WriteValue(int value) { WT(LBTOK.S32); w.Write(value); }
public override void WriteValue(uint value) { WT(LBTOK.U32); w.Write(value); }
public override void WriteValue(long value) { WT(LBTOK.S64); w.Write(value); }
public override void WriteValue(ulong value) { WT(LBTOK.U64); w.Write(value); }
public override void WriteStartArray() { WT(LBTOK.StartArray); }
public override void WriteEndArray() { WT(LBTOK.EndArray); }
public override void WriteStartObject() { WT(LBTOK.StartObject); }
public override void WriteEndObject() { WT(LBTOK.EndObject); }
public override void WriteNull() { WT(LBTOK.Null); }
public override void WriteUndefined() { WT(LBTOK.Undefined); }
public override void WriteValue(float value) { WT(LBTOK.F32); w.Write(value); }
public override void WriteValue(double value) { WT(LBTOK.F64); w.Write(value); }
public override void WriteValue(byte[] value) { WT(LBTOK.ByteArray); w.Write(value.Length); w.Write(value); }
public override void WriteComment(string text) { throw new NotImplementedException(); }
public override void WriteWhitespace(string ws) { throw new NotImplementedException(); }
protected override void WriteIndent() { throw new NotImplementedException(); }
protected override void WriteIndentSpace() { throw new NotImplementedException(); }
public override void WriteEnd() { throw new NotImplementedException(); }
protected override void WriteEnd(JsonToken token) { throw new NotImplementedException(); }
public override void WriteRaw(string json) { throw new NotImplementedException(); }
public override void WriteRawValue(string json) { throw new NotImplementedException(); }
public override void WriteStartConstructor(string name) { throw new NotImplementedException(); }
public override void WriteEndConstructor() { throw new NotImplementedException(); }
protected override void WriteValueDelimiter() { throw new NotImplementedException(); }
public override void WritePropertyName(string name) { WT(LBTOK.Property); w.Write(name); }
public override void WriteValue(string value) { WT(LBTOK.String); w.Write(value); }
public override void WritePropertyName(string name, bool escape) { WT(LBTOK.Property); w.Write(name); } // no escaping required
public override void WriteValue(char value) { throw new NotImplementedException(); }
public override void WriteValue(DateTime value) { throw new NotImplementedException(); }
public override void WriteValue(DateTimeOffset value) { throw new NotImplementedException(); }
public override void WriteValue(decimal value) { throw new NotImplementedException(); }
public override void WriteValue(Guid value) { throw new NotImplementedException(); }
public override void WriteValue(TimeSpan value) { throw new NotImplementedException(); }
public override void WriteValue(Uri value) { throw new NotImplementedException(); }
}
}

View File

@ -1,15 +1,50 @@
using Newtonsoft.Json; using System;
using System;
namespace Jellyfish.Virtu namespace Jellyfish.Virtu
{ {
// ReSharper disable once UnusedMember.Global // ReSharper disable once UnusedMember.Global
public partial class Cpu public partial class Cpu
{ {
[JsonIgnore] public void Sync(IComponentSerializer ser)
{
ser.Sync(nameof(_is65C02), ref _is65C02);
ser.Sync(nameof(_multiplier), ref _multiplier);
ser.Sync(nameof(_ra), ref _ra);
ser.Sync(nameof(_rx), ref _rx);
ser.Sync(nameof(_ry), ref _ry);
ser.Sync(nameof(_rs), ref _rs);
ser.Sync(nameof(_rp), ref _rp);
ser.Sync(nameof(_rpc), ref _rpc);
ser.Sync(nameof(_ea), ref _ea);
ser.Sync(nameof(_cc), ref _cc);
ser.Sync(nameof(_opCode), ref _opCode);
ser.Sync(nameof(_cycles), ref _cycles);
if (!ser.IsReader)
{
// A way to set the action callback
Is65C02 = _is65C02;
}
}
private Action[] _executeOpCode65N02; private Action[] _executeOpCode65N02;
[JsonIgnore]
private Action[] _executeOpCode65C02; private Action[] _executeOpCode65C02;
private Action[] _executeOpCode;
private bool _is65C02;
private int _multiplier;
private int _ra;
private int _rx;
private int _ry;
private int _rs;
private int _rp;
private int _rpc;
private int _ea;
private int _cc;
private int _opCode;
private long _cycles;
private const int Pc = 0x01; private const int Pc = 0x01;
private const int Pz = 0x02; private const int Pz = 0x02;

View File

@ -1,6 +1,5 @@
using System; using System;
using System.Globalization; using System.Globalization;
using Newtonsoft.Json;
namespace Jellyfish.Virtu namespace Jellyfish.Virtu
{ {
@ -13,19 +12,15 @@ namespace Jellyfish.Virtu
int Execute(); int Execute();
long Cycles { get; } long Cycles { get; }
int Multiplier { get; } int Multiplier { get; }
// ReSharper disable once UnusedMember.Global
void Sync(IComponentSerializer ser);
} }
// ReSharper disable once UnusedMember.Global // ReSharper disable once UnusedMember.Global
public sealed partial class Cpu : ICpu public sealed partial class Cpu : ICpu
{ {
// ReSharper disable once FieldCanBeMadeReadOnly.Local private readonly IMemoryBus _memory;
private IMemoryBus _memory;
// ReSharper disable once UnusedMember.Global
public Cpu()
{
InitializeOpCodeDelegates();
}
public Cpu(IMemoryBus memory) public Cpu(IMemoryBus memory)
{ {
@ -3432,29 +3427,22 @@ namespace Jellyfish.Virtu
set { _is65C02 = value; _executeOpCode = _is65C02 ? _executeOpCode65C02 : _executeOpCode65N02; } set { _is65C02 = value; _executeOpCode = _is65C02 ? _executeOpCode65C02 : _executeOpCode65N02; }
} }
public int Multiplier { get; set; } public int Multiplier { get => _multiplier; private set => _multiplier = value; }
public int RA { get; set; } public int RA { get => _ra; set => _ra = value; }
public int RX { get; set; } public int RX { get => _rx; set => _rx = value; }
public int RY { get; set; } public int RY { get => _ry; set => _ry = value; }
public int RS { get; set; } public int RS { get => _rs; set => _rs = value; }
public int RP { get; set; } public int RP { get => _rp; set => _rp = value; }
public int RPC { get; set; } public int RPC { get => _rpc; set => _rpc = value; }
public int EA { get; private set; } public int EA { get => _ea; private set => _ea = value; }
public int CC { get; private set; } public int CC { get => _cc; private set => _cc = value; }
public int OpCode { get; private set; } public int OpCode { get => _opCode; private set => _opCode = value; }
public long Cycles { get; private set; } public long Cycles { get => _cycles; private set => _cycles = value; }
[JsonIgnore]
private bool _is65C02;
[JsonIgnore]
private Action[] _executeOpCode;
[JsonIgnore]
public Action<string[]> TraceCallback; public Action<string[]> TraceCallback;
/// <summary>Carry Flag</summary> /// <summary>Carry Flag</summary>
[JsonIgnore]
public bool FlagC public bool FlagC
{ {
get => (RP & 0x01) != 0; get => (RP & 0x01) != 0;
@ -3462,7 +3450,6 @@ namespace Jellyfish.Virtu
} }
/// <summary>Zero Flag</summary> /// <summary>Zero Flag</summary>
[JsonIgnore]
public bool FlagZ public bool FlagZ
{ {
get => (RP & 0x02) != 0; get => (RP & 0x02) != 0;
@ -3470,7 +3457,6 @@ namespace Jellyfish.Virtu
} }
/// <summary>Interrupt Disable Flag</summary> /// <summary>Interrupt Disable Flag</summary>
[JsonIgnore]
public bool FlagI public bool FlagI
{ {
get => (RP & 0x04) != 0; get => (RP & 0x04) != 0;
@ -3478,7 +3464,6 @@ namespace Jellyfish.Virtu
} }
/// <summary>Decimal Mode Flag</summary> /// <summary>Decimal Mode Flag</summary>
[JsonIgnore]
public bool FlagD public bool FlagD
{ {
get => (RP & 0x08) != 0; get => (RP & 0x08) != 0;
@ -3486,7 +3471,6 @@ namespace Jellyfish.Virtu
} }
/// <summary>Break Flag</summary> /// <summary>Break Flag</summary>
[JsonIgnore]
public bool FlagB public bool FlagB
{ {
get => (RP & 0x10) != 0; get => (RP & 0x10) != 0;
@ -3494,7 +3478,6 @@ namespace Jellyfish.Virtu
} }
/// <summary>T... Flag</summary> /// <summary>T... Flag</summary>
[JsonIgnore]
public bool FlagT public bool FlagT
{ {
get => (RP & 0x20) != 0; get => (RP & 0x20) != 0;
@ -3502,7 +3485,6 @@ namespace Jellyfish.Virtu
} }
/// <summary>Overflow Flag</summary> /// <summary>Overflow Flag</summary>
[JsonIgnore]
public bool FlagV public bool FlagV
{ {
get => (RP & 0x40) != 0; get => (RP & 0x40) != 0;
@ -3510,7 +3492,6 @@ namespace Jellyfish.Virtu
} }
/// <summary>Negative Flag</summary> /// <summary>Negative Flag</summary>
[JsonIgnore]
public bool FlagN public bool FlagN
{ {
get => (RP & 0x80) != 0; get => (RP & 0x80) != 0;

View File

@ -4,9 +4,8 @@ namespace Jellyfish.Virtu
{ {
internal abstract class Disk525 internal abstract class Disk525
{ {
// ReSharper disable once UnusedMember.Global protected byte[] Data;
// ReSharper disable once PublicConstructorInAbstractClass public bool IsWriteProtected;
public Disk525() { }
protected Disk525(byte[] data, bool isWriteProtected) protected Disk525(byte[] data, bool isWriteProtected)
{ {
@ -14,6 +13,12 @@ namespace Jellyfish.Virtu
IsWriteProtected = isWriteProtected; IsWriteProtected = isWriteProtected;
} }
public virtual void Sync(IComponentSerializer ser)
{
ser.Sync(nameof(Data), ref Data, false);
ser.Sync(nameof(IsWriteProtected), ref IsWriteProtected);
}
public static Disk525 CreateDisk(string name, byte[] data, bool isWriteProtected) public static Disk525 CreateDisk(string name, byte[] data, bool isWriteProtected)
{ {
if (name == null) if (name == null)
@ -43,11 +48,6 @@ namespace Jellyfish.Virtu
public abstract void ReadTrack(int number, int fraction, byte[] buffer); public abstract void ReadTrack(int number, int fraction, byte[] buffer);
public abstract void WriteTrack(int number, int fraction, byte[] buffer); public abstract void WriteTrack(int number, int fraction, byte[] buffer);
public byte[] Data { get; protected set; }
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Local
public bool IsWriteProtected { get; private set; }
public const int SectorCount = 16; public const int SectorCount = 16;
public const int SectorSize = 0x100; public const int SectorSize = 0x100;
public const int TrackSize = 0x1A00; public const int TrackSize = 0x1A00;

View File

@ -2,12 +2,19 @@
namespace Jellyfish.Virtu namespace Jellyfish.Virtu
{ {
// ReSharper disable once UnusedMember.Global
internal enum SectorSkew { None = 0, Dos, ProDos }; internal enum SectorSkew { None = 0, Dos, ProDos };
internal sealed class DiskDsk : Disk525 internal sealed class DiskDsk : Disk525
{ {
// ReSharper disable once UnusedMember.Global private const int SecondaryBufferLength = 0x56;
public DiskDsk() { } private const int Volume = 0xFE;
private byte[] _trackBuffer;
private int _trackOffset;
private byte[] _primaryBuffer = new byte[0x100];
private byte[] _secondaryBuffer = new byte[SecondaryBufferLength + 1];
private int[] _sectorSkew;
public DiskDsk(byte[] data, bool isWriteProtected, SectorSkew sectorSkew) public DiskDsk(byte[] data, bool isWriteProtected, SectorSkew sectorSkew)
: base(data, isWriteProtected) : base(data, isWriteProtected)
@ -15,6 +22,16 @@ namespace Jellyfish.Virtu
_sectorSkew = SectorSkewMode[(int)sectorSkew]; _sectorSkew = SectorSkewMode[(int)sectorSkew];
} }
public override void Sync(IComponentSerializer ser)
{
ser.Sync(nameof(_trackBuffer), ref _trackBuffer, false);
ser.Sync(nameof(_trackOffset), ref _trackOffset);
ser.Sync(nameof(_primaryBuffer), ref _primaryBuffer, false);
ser.Sync(nameof(_secondaryBuffer), ref _secondaryBuffer, false);
ser.Sync(nameof(_sectorSkew), ref _sectorSkew, false);
base.Sync(ser);
}
public override void ReadTrack(int number, int fraction, byte[] buffer) public override void ReadTrack(int number, int fraction, byte[] buffer)
{ {
int track = number / 2; int track = number / 2;
@ -165,13 +182,12 @@ namespace Jellyfish.Virtu
private int ReadNibble44() private int ReadNibble44()
{ {
return (((ReadNibble() << 1) | 0x1) & ReadNibble()); return ((ReadNibble() << 1) | 0x1) & ReadNibble();
} }
private byte ReadTranslatedNibble() private byte ReadTranslatedNibble()
{ {
byte data = NibbleToByte[ReadNibble()]; return NibbleToByte[ReadNibble()];
return data;
} }
private bool ReadDataNibbles(int sectorOffset) private bool ReadDataNibbles(int sectorOffset)
@ -268,14 +284,6 @@ namespace Jellyfish.Virtu
WriteNibble(ByteToNibble[a]); // data checksum WriteNibble(ByteToNibble[a]); // data checksum
} }
private byte[] _trackBuffer;
private int _trackOffset;
private byte[] _primaryBuffer = new byte[0x100];
private const int SecondaryBufferLength = 0x56;
private byte[] _secondaryBuffer = new byte[SecondaryBufferLength + 1];
private int[] _sectorSkew;
private const int Volume = 0xFE;
private static readonly byte[] SwapBits = { 0, 2, 1, 3 }; private static readonly byte[] SwapBits = { 0, 2, 1, 3 };
private static readonly int[] SectorSkewNone = private static readonly int[] SectorSkewNone =

View File

@ -8,16 +8,26 @@ namespace Jellyfish.Virtu
// ReSharper disable once UnusedMemberInSuper.Global // ReSharper disable once UnusedMemberInSuper.Global
DiskIIDrive Drive1 { get; } DiskIIDrive Drive1 { get; }
// ReSharper disable once UnusedMember.Global
void Sync(IComponentSerializer ser);
} }
// ReSharper disable once UnusedMember.Global // ReSharper disable once UnusedMember.Global
public sealed class DiskIIController : IDiskIIController public sealed class DiskIIController : IDiskIIController
{ {
// ReSharper disable once FieldCanBeMadeReadOnly.Local private const int Phase1On = 1 << 1;
private IVideo _video; private readonly IVideo _video;
private readonly byte[] _romRegionC1C7;
// ReSharper disable once UnusedMember.Global private bool _driveLight;
public DiskIIController() { } private int _latch;
private int _phaseStates;
private bool _motorOn;
private int _driveNumber;
private bool _loadMode;
private bool _writeMode;
private bool _driveSpin;
public DiskIIController(IVideo video, byte[] diskIIRom) public DiskIIController(IVideo video, byte[] diskIIRom)
{ {
@ -32,8 +42,30 @@ namespace Jellyfish.Virtu
_writeMode = false; _writeMode = false;
} }
public bool DriveLight { get; set; } public DiskIIDrive Drive1 { get; }
public DiskIIDrive Drive2 { get; }
public bool DriveLight
{
get => _driveLight;
set => _driveLight = value;
}
public void Sync(IComponentSerializer ser)
{
ser.Sync(nameof(_driveLight), ref _driveLight);
ser.Sync(nameof(_latch), ref _latch);
ser.Sync(nameof(_phaseStates), ref _phaseStates);
ser.Sync(nameof(_motorOn), ref _motorOn);
ser.Sync(nameof(_driveNumber), ref _driveNumber);
ser.Sync(nameof(_loadMode), ref _loadMode);
ser.Sync(nameof(_writeMode), ref _writeMode);
ser.Sync(nameof(_driveSpin), ref _driveSpin);
Drive1.Sync(ser);
Drive2.Sync(ser);
}
public IList<DiskIIDrive> Drives => new List<DiskIIDrive> { Drive1, Drive2 }; public IList<DiskIIDrive> Drives => new List<DiskIIDrive> { Drive1, Drive2 };
public void WriteIoRegionC8CF(int address, int data) => _video.ReadFloatingBus(); public void WriteIoRegionC8CF(int address, int data) => _video.ReadFloatingBus();
@ -236,24 +268,5 @@ namespace Jellyfish.Virtu
Drives[_driveNumber].ApplyPhaseChange(_phaseStates); Drives[_driveNumber].ApplyPhaseChange(_phaseStates);
} }
} }
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Local
public DiskIIDrive Drive1 { get; private set; }
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Local
public DiskIIDrive Drive2 { get; private set; }
private const int Phase1On = 1 << 1;
private int _latch;
private int _phaseStates;
private bool _motorOn;
private int _driveNumber;
private bool _loadMode;
private bool _writeMode;
private bool _driveSpin;
private byte[] _romRegionC1C7 = new byte[0x0100];
} }
} }

View File

@ -1,14 +1,17 @@
using Newtonsoft.Json; namespace Jellyfish.Virtu
namespace Jellyfish.Virtu
{ {
public sealed class DiskIIDrive public sealed class DiskIIDrive
{ {
// ReSharper disable once FieldCanBeMadeReadOnly.Local private readonly IDiskIIController _diskController;
private IDiskIIController _diskController; private readonly int[][] _driveArmStepDelta = new int[PhaseCount][];
// ReSharper disable once UnusedMember.Global private bool _trackLoaded;
public DiskIIDrive() { } private bool _trackChanged;
private int _trackNumber;
private int _trackOffset;
private byte[] _trackData = new byte[Disk525.TrackSize];
private Disk525 _disk;
public DiskIIDrive(IDiskIIController diskController) public DiskIIDrive(IDiskIIController diskController)
{ {
@ -19,6 +22,18 @@ namespace Jellyfish.Virtu
_driveArmStepDelta[3] = new[] { 0, 1, 0, 1, -1, 0, -1, 0, 0, 1, 0, 1, -1, 0, -1, 0 }; // phase 3 _driveArmStepDelta[3] = new[] { 0, 1, 0, 1, -1, 0, -1, 0, 0, 1, 0, 1, -1, 0, -1, 0 }; // phase 3
} }
public void Sync(IComponentSerializer ser)
{
ser.Sync(nameof(_trackLoaded), ref _trackLoaded);
ser.Sync(nameof(_trackChanged), ref _trackChanged);
ser.Sync(nameof(_trackNumber), ref _trackNumber);
ser.Sync(nameof(_trackOffset), ref _trackOffset);
ser.Sync(nameof(_trackData), ref _trackData, false);
// TODO: save the delta, this is saving the rom into save states
_disk?.Sync(ser);
}
// ReSharper disable once UnusedMember.Global // ReSharper disable once UnusedMember.Global
public void InsertDisk(string name, byte[] data, bool isWriteProtected) public void InsertDisk(string name, byte[] data, bool isWriteProtected)
{ {
@ -101,24 +116,9 @@ namespace Jellyfish.Virtu
} }
} }
[JsonIgnore] public bool IsWriteProtected => _disk?.IsWriteProtected ?? false;
public bool IsWriteProtected => _disk.IsWriteProtected;
private const int TrackNumberMax = 0x44; private const int TrackNumberMax = 0x44;
private const int PhaseCount = 4; private const int PhaseCount = 4;
// ReSharper disable once FieldCanBeMadeReadOnly.Local
private int[][] _driveArmStepDelta = new int[PhaseCount][];
private bool _trackLoaded;
private bool _trackChanged;
private int _trackNumber;
private int _trackOffset;
// ReSharper disable once FieldCanBeMadeReadOnly.Local
private byte[] _trackData = new byte[Disk525.TrackSize];
private Disk525 _disk;
} }
} }

View File

@ -4,9 +4,6 @@ namespace Jellyfish.Virtu
{ {
internal sealed class DiskNib : Disk525 internal sealed class DiskNib : Disk525
{ {
// ReSharper disable once UnusedMember.Global
public DiskNib() { }
public DiskNib(byte[] data, bool isWriteProtected) public DiskNib(byte[] data, bool isWriteProtected)
: base(data, isWriteProtected) : base(data, isWriteProtected)
{ {

View File

@ -0,0 +1,17 @@
namespace Jellyfish.Virtu
{
// Serves as a generalized interface to the BizHawk serializer
public interface IComponentSerializer
{
bool IsReader { get; }
void Sync(string name, ref bool val);
void Sync(string name, ref int val);
void Sync(string name, ref long val);
void Sync(string name, ref ulong val);
void Sync(string name, ref bool[] val, bool useNull);
void Sync(string name, ref byte[] val, bool useNull);
void Sync(string name, ref ushort[] val, bool useNull);
void Sync(string name, ref int[] val, bool useNull);
}
}

View File

@ -143,9 +143,6 @@ namespace Jellyfish.Virtu
public sealed class Keyboard public sealed class Keyboard
{ {
// ReSharper disable once UnusedMember.Global
public Keyboard() { }
static Keyboard() static Keyboard()
{ {
for (int i = 0; i < 62; i++) for (int i = 0; i < 62; i++)
@ -226,15 +223,15 @@ namespace Jellyfish.Virtu
0x7a1a5a1a, // z 0x7a1a5a1a, // z
}; };
/// <param name="key">0 - 55</param> // key: 0 - 55
private static int KeyToAscii(int key, bool control, bool shift) private static int KeyToAscii(int key, bool control, bool shift)
{ {
int s = control ? (shift ? 0 : 16) : (shift ? 8 : 24); int s = control ? shift ? 0 : 16 : shift ? 8 : 24;
return (int)(KeyAsciiData[key] >> s & 0x7f); return (int)(KeyAsciiData[key] >> s & 0x7f);
} }
// ReSharper disable once InconsistentNaming // ReSharper disable once InconsistentNaming
private static Dictionary<string, Keys> DescriptionsToKeys = new Dictionary<string, Keys>(); private static readonly Dictionary<string, Keys> DescriptionsToKeys = new Dictionary<string, Keys>();
private static Keys FromStrings(IEnumerable<string> keynames) private static Keys FromStrings(IEnumerable<string> keynames)
{ {

View File

@ -1,29 +1,87 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Linq;
namespace Jellyfish.Virtu namespace Jellyfish.Virtu
{ {
public enum EventCallbacks
{
FlushOutput,
FlushRow,
LeaveVBlank,
ResetVsync,
InverseText
}
internal sealed class MachineEvent internal sealed class MachineEvent
{ {
public MachineEvent(int delta, Action action) public MachineEvent(int delta, EventCallbacks type)
{ {
Delta = delta; Delta = delta;
Action = action; Type = type;
}
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "Delta = {0} Action = {{{1}.{2}}}", Delta, Action.Method.DeclaringType?.Name, Action.Method.Name);
} }
public int Delta { get; set; } public int Delta { get; set; }
public Action Action { get; set; } public EventCallbacks Type { get; set; }
} }
public sealed class MachineEvents public sealed class MachineEvents
{ {
public void AddEvent(int delta, Action action) private readonly Dictionary<EventCallbacks, Action> _eventDelegates = new Dictionary<EventCallbacks, Action>();
private readonly LinkedList<MachineEvent> _used = new LinkedList<MachineEvent>();
private readonly LinkedList<MachineEvent> _free = new LinkedList<MachineEvent>();
// ReSharper disable once UnusedMember.Global
public void Sync(IComponentSerializer ser)
{
if (ser.IsReader)
{
int[] usedDelta = new int[0];
int[] usedType = new int[0];
int[] freeDelta = new int[0];
int[] freeType = new int[0];
ser.Sync("UsedDelta", ref usedDelta, false);
ser.Sync("UsedType", ref usedType, false);
ser.Sync("FreeDelta", ref freeDelta, false);
ser.Sync("FreeType", ref freeType, false);
_used.Clear();
for (int i = 0; i < usedDelta.Length; i++)
{
var e = new MachineEvent(usedDelta[i], (EventCallbacks)usedType[i]);
_used.AddLast(new LinkedListNode<MachineEvent>(e));
}
_free.Clear();
for (int i = 0; i < freeDelta.Length; i++)
{
var e = new MachineEvent(freeDelta[i], (EventCallbacks)freeType[i]);
_free.AddLast(new LinkedListNode<MachineEvent>(e));
}
}
else
{
var usedDelta = _used.Select(u => u.Delta).ToArray();
var usedType = _used.Select(u => (int)u.Type).ToArray();
var freeDelta = _free.Select(f => f.Delta).ToArray();
var freeType = _free.Select(f => (int)f.Type).ToArray();
ser.Sync("UsedDelta", ref usedDelta, false);
ser.Sync("UsedType", ref usedType, false);
ser.Sync("FreeDelta", ref freeDelta, false);
ser.Sync("FreeType", ref freeType, false);
}
}
public void AddEventDelegate(EventCallbacks type, Action action)
{
_eventDelegates[type] = action;
}
public void AddEvent(int delta, EventCallbacks type)
{ {
var node = _used.First; var node = _used.First;
for (; node != null; node = node.Next) for (; node != null; node = node.Next)
@ -44,11 +102,11 @@ namespace Jellyfish.Virtu
{ {
_free.RemoveFirst(); _free.RemoveFirst();
newNode.Value.Delta = delta; newNode.Value.Delta = delta;
newNode.Value.Action = action; newNode.Value.Type = type;
} }
else else
{ {
newNode = new LinkedListNode<MachineEvent>(new MachineEvent(delta, action)); newNode = new LinkedListNode<MachineEvent>(new MachineEvent(delta, type));
} }
if (node != null) if (node != null)
@ -61,7 +119,7 @@ namespace Jellyfish.Virtu
} }
} }
public int FindEvent(Action action) public int FindEvent(EventCallbacks type)
{ {
int delta = 0; int delta = 0;
@ -69,9 +127,9 @@ namespace Jellyfish.Virtu
{ {
delta += node.Value.Delta; delta += node.Value.Delta;
var other = node.Value.Action; var other = node.Value.Type;
if (other.Method == action.Method && other.Target == action.Target) if (other == type)
{ {
return delta; return delta;
} }
@ -88,7 +146,7 @@ namespace Jellyfish.Virtu
while (node.Value.Delta <= 0) while (node.Value.Delta <= 0)
{ {
node.Value.Action(); _eventDelegates[node.Value.Type]();
RemoveEvent(node); RemoveEvent(node);
node = _used.First; node = _used.First;
} }
@ -104,11 +162,5 @@ namespace Jellyfish.Virtu
_used.Remove(node); _used.Remove(node);
_free.AddFirst(node); // cache node; avoids garbage _free.AddFirst(node); // cache node; avoids garbage
} }
// ReSharper disable once FieldCanBeMadeReadOnly.Local
private LinkedList<MachineEvent> _used = new LinkedList<MachineEvent>();
// ReSharper disable once FieldCanBeMadeReadOnly.Local
private LinkedList<MachineEvent> _free = new LinkedList<MachineEvent>();
} }
} }

View File

@ -1,5 +1,4 @@
using Newtonsoft.Json; using System;
using System;
namespace Jellyfish.Virtu namespace Jellyfish.Virtu
{ {
@ -96,7 +95,6 @@ namespace Jellyfish.Virtu
Video.ModeC, Video.ModeD, Video.Mode1, Video.Mode2, Video.ModeE, Video.ModeF, Video.Mode1, Video.Mode2 Video.ModeC, Video.ModeD, Video.Mode1, Video.Mode2, Video.ModeE, Video.ModeF, Video.Mode1, Video.Mode2
}; };
[JsonIgnore]
private Action<int, byte>[][][] WriteRamModeBankRegion; private Action<int, byte>[][][] WriteRamModeBankRegion;
} }
} }

View File

@ -1,10 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Newtonsoft.Json;
namespace Jellyfish.Virtu namespace Jellyfish.Virtu
{ {
// ReSharper disable once UnusedMember.Global
public enum MonitorType { Unknown, Standard, Enhanced } public enum MonitorType { Unknown, Standard, Enhanced }
public interface IMemoryBus public interface IMemoryBus
@ -31,8 +31,12 @@ namespace Jellyfish.Virtu
int VideoMode { get; } int VideoMode { get; }
MonitorType Monitor { get; } MonitorType Monitor { get; }
// ReSharper disable once UnusedMember.Global
void Sync(IComponentSerializer ser);
} }
// ReSharper disable once UnusedMember.Global
public sealed partial class Memory : IMemoryBus public sealed partial class Memory : IMemoryBus
{ {
private IGamePort _gamePort; private IGamePort _gamePort;
@ -47,15 +51,30 @@ namespace Jellyfish.Virtu
private IPeripheralCard _slot5; private IPeripheralCard _slot5;
private IPeripheralCard _slot7; private IPeripheralCard _slot7;
// TODO: this shouldn't be in savestates! private readonly byte[] _appleIIe;
// ReSharper disable once FieldCanBeMadeReadOnly.Local private readonly byte[][] _regionRead = new byte[RegionCount][];
private byte[] _appleIIe; private readonly byte[][] _regionWrite = new byte[RegionCount][];
private readonly Action<int, byte>[] _writeRegion = new Action<int, byte>[RegionCount];
// ReSharper disable once UnusedMember.Global private bool _lagged;
public Memory() private int _state;
{ private int _slotRegionC8CF;
InitializeWriteDelegates(); private byte[] _zeroPage;
} private byte[] _ramMainRegion0001 = new byte[0x0200];
private byte[] _ramMainRegion02BF = new byte[0xBE00];
private byte[] _ramMainBank1RegionD0DF = new byte[0x1000];
private byte[] _ramMainBank2RegionD0DF = new byte[0x1000];
private byte[] _ramMainRegionE0FF = new byte[0x2000];
private byte[] _ramAuxRegion0001 = new byte[0x0200];
private byte[] _ramAuxRegion02BF = new byte[0xBE00];
private byte[] _ramAuxBank1RegionD0DF = new byte[0x1000];
private byte[] _ramAuxBank2RegionD0DF = new byte[0x1000];
private byte[] _ramAuxRegionE0FF = new byte[0x2000];
private byte[] _romExternalRegionC1CF = new byte[0x0F00];
private byte[] _romInternalRegionC1CF = new byte[0x0F00];
private byte[] _romRegionD0DF = new byte[0x1000];
private byte[] _romRegionE0FF = new byte[0x2000];
// ReSharper disable once UnusedMember.Global // ReSharper disable once UnusedMember.Global
public Memory(byte[] appleIIe) public Memory(byte[] appleIIe)
@ -64,6 +83,37 @@ namespace Jellyfish.Virtu
InitializeWriteDelegates(); InitializeWriteDelegates();
} }
public void Sync(IComponentSerializer ser)
{
ser.Sync(nameof(_lagged), ref _lagged);
ser.Sync(nameof(_state), ref _state);
ser.Sync(nameof(_slotRegionC8CF), ref _slotRegionC8CF);
ser.Sync(nameof(_zeroPage), ref _zeroPage, false);
ser.Sync(nameof(_ramMainRegion0001), ref _ramMainRegion0001, false);
ser.Sync(nameof(_ramMainRegion02BF), ref _ramMainRegion02BF, false);
ser.Sync(nameof(_ramMainBank1RegionD0DF), ref _ramMainBank1RegionD0DF, false);
ser.Sync(nameof(_ramMainBank2RegionD0DF), ref _ramMainBank2RegionD0DF, false);
ser.Sync(nameof(_ramMainRegionE0FF), ref _ramMainRegionE0FF, false);
ser.Sync(nameof(_ramAuxRegion0001), ref _ramAuxRegion0001, false);
ser.Sync(nameof(_ramAuxRegion02BF), ref _ramAuxRegion02BF, false);
ser.Sync(nameof(_ramAuxBank1RegionD0DF), ref _ramAuxBank1RegionD0DF, false);
ser.Sync(nameof(_ramAuxBank2RegionD0DF), ref _ramAuxBank2RegionD0DF, false);
ser.Sync(nameof(_ramAuxRegionE0FF), ref _ramAuxRegionE0FF, false);
ser.Sync(nameof(_romExternalRegionC1CF), ref _romExternalRegionC1CF, false);
ser.Sync(nameof(_romInternalRegionC1CF), ref _romInternalRegionC1CF, false);
ser.Sync(nameof(_romRegionD0DF), ref _romRegionD0DF, false);
ser.Sync(nameof(_romRegionE0FF), ref _romRegionE0FF, false);
if (ser.IsReader)
{
MapRegion0001();
MapRegion02BF();
MapRegionC0CF();
MapRegionD0FF();
}
}
// ReSharper disable once UnusedMember.Global // ReSharper disable once UnusedMember.Global
public void Initialize( public void Initialize(
Keyboard keyboard, Keyboard keyboard,
@ -123,7 +173,7 @@ namespace Jellyfish.Virtu
} }
} }
public bool Lagged { get; set; } public bool Lagged { get => _lagged; set => _lagged = value; }
private IList<IPeripheralCard> Slots => new List<IPeripheralCard> private IList<IPeripheralCard> Slots => new List<IPeripheralCard>
{ {
@ -2100,51 +2150,15 @@ namespace Jellyfish.Virtu
public MonitorType Monitor { get; private set; } public MonitorType Monitor { get; private set; }
public int VideoMode => StateVideoMode[_state & StateVideo]; public int VideoMode => StateVideoMode[_state & StateVideo];
[JsonIgnore]
private Action<int, byte> _writeIoRegionC0C0; private Action<int, byte> _writeIoRegionC0C0;
[JsonIgnore]
private Action<int, byte> _writeIoRegionC1C7; private Action<int, byte> _writeIoRegionC1C7;
[JsonIgnore]
private Action<int, byte> _writeIoRegionC3C3; private Action<int, byte> _writeIoRegionC3C3;
[JsonIgnore]
private Action<int, byte> _writeIoRegionC8CF; private Action<int, byte> _writeIoRegionC8CF;
[JsonIgnore]
private Action<int, byte> _writeRomRegionD0FF; private Action<int, byte> _writeRomRegionD0FF;
[JsonIgnore]
public Action<uint> ReadCallback; public Action<uint> ReadCallback;
[JsonIgnore]
public Action<uint> WriteCallback; public Action<uint> WriteCallback;
[JsonIgnore]
public Action<uint> ExecuteCallback; public Action<uint> ExecuteCallback;
[JsonIgnore]
public Action InputCallback; public Action InputCallback;
private int _state;
private int _slotRegionC8CF;
private byte[] _zeroPage;
private byte[][] _regionRead = new byte[RegionCount][];
private byte[][] _regionWrite = new byte[RegionCount][];
private Action<int, byte>[] _writeRegion = new Action<int, byte>[RegionCount];
private byte[] _ramMainRegion0001 = new byte[0x0200];
private byte[] _ramMainRegion02BF = new byte[0xBE00];
private byte[] _ramMainBank1RegionD0DF = new byte[0x1000];
private byte[] _ramMainBank2RegionD0DF = new byte[0x1000];
private byte[] _ramMainRegionE0FF = new byte[0x2000];
private byte[] _ramAuxRegion0001 = new byte[0x0200];
private byte[] _ramAuxRegion02BF = new byte[0xBE00];
private byte[] _ramAuxBank1RegionD0DF = new byte[0x1000];
private byte[] _ramAuxBank2RegionD0DF = new byte[0x1000];
private byte[] _ramAuxRegionE0FF = new byte[0x2000];
private byte[] _romExternalRegionC1CF = new byte[0x0F00];
private byte[] _romInternalRegionC1CF = new byte[0x0F00];
private byte[] _romRegionD0DF = new byte[0x1000];
private byte[] _romRegionE0FF = new byte[0x2000];
} }
} }

View File

@ -6,16 +6,20 @@ namespace Jellyfish.Virtu
{ {
int Read(int address, int value); int Read(int address, int value);
void Write(int address); void Write(int address);
// ReSharper disable once UnusedMember.Global
void Sync(IComponentSerializer ser);
} }
// ReSharper disable once UnusedMember.Global // ReSharper disable once UnusedMember.Global
public sealed class NoSlotClock : ISlotClock public sealed class NoSlotClock : ISlotClock
{ {
// ReSharper disable once FieldCanBeMadeReadOnly.Local private readonly Video _video;
private Video _video;
// ReSharper disable once UnusedMember.Global private bool _clockEnabled;
public NoSlotClock() { } private bool _writeEnabled;
private RingRegister _clockRegister;
private RingRegister _comparisonRegister;
public NoSlotClock(Video video) public NoSlotClock(Video video)
{ {
@ -27,6 +31,14 @@ namespace Jellyfish.Virtu
_comparisonRegister = new RingRegister(ClockInitSequence, 0x1); _comparisonRegister = new RingRegister(ClockInitSequence, 0x1);
} }
public void Sync(IComponentSerializer ser)
{
ser.Sync(nameof(_clockEnabled), ref _clockEnabled);
ser.Sync(nameof(_writeEnabled), ref _writeEnabled);
_clockRegister.Sync(ser);
_comparisonRegister.Sync(ser);
}
public int Read(int address, int value) public int Read(int address, int value)
{ {
// this may read or write the clock // this may read or write the clock
@ -142,11 +154,6 @@ namespace Jellyfish.Virtu
private const ulong ClockInitSequence = 0x5CA33AC55CA33AC5; private const ulong ClockInitSequence = 0x5CA33AC55CA33AC5;
private bool _clockEnabled;
private bool _writeEnabled;
private RingRegister _clockRegister;
private RingRegister _comparisonRegister;
private struct RingRegister private struct RingRegister
{ {
public RingRegister(ulong data, ulong mask) public RingRegister(ulong data, ulong mask)
@ -155,6 +162,12 @@ namespace Jellyfish.Virtu
_mask = mask; _mask = mask;
} }
public void Sync(IComponentSerializer ser)
{
ser.Sync(nameof(_data), ref _data);
ser.Sync(nameof(_mask), ref _mask);
}
public void Reset() public void Reset()
{ {
_mask = 0x1; _mask = 0x1;

View File

@ -1,8 +1,4 @@
using System; namespace Jellyfish.Virtu
using System.Runtime.Serialization;
using Newtonsoft.Json;
namespace Jellyfish.Virtu
{ {
public interface ISpeaker public interface ISpeaker
{ {
@ -13,46 +9,46 @@ namespace Jellyfish.Virtu
// ReSharper disable once UnusedMember.Global // ReSharper disable once UnusedMember.Global
void GetSamples(out short[] samples, out int nSamp); void GetSamples(out short[] samples, out int nSamp);
// ReSharper disable once UnusedMember.Global
void Sync(IComponentSerializer ser);
} }
// ReSharper disable once UnusedMember.Global
public sealed class Speaker : ISpeaker public sealed class Speaker : ISpeaker
{ {
// ReSharper disable once FieldCanBeMadeReadOnly.Local
private MachineEvents _events;
// ReSharper disable once FieldCanBeMadeReadOnly.Local
private ICpu _cpu;
public Speaker() { }
public Speaker(MachineEvents events, ICpu cpu)
{
_events = events;
_cpu = cpu;
_flushOutputEvent = FlushOutputEvent; // cache delegates; avoids garbage
_events.AddEvent(CyclesPerFlush * _cpu.Multiplier, _flushOutputEvent);
_isHigh = false;
_highCycles = _totalCycles = 0;
}
private const int CyclesPerFlush = 23; private const int CyclesPerFlush = 23;
// ReSharper disable once FieldCanBeMadeReadOnly.Local private readonly MachineEvents _events;
private Action _flushOutputEvent; private readonly ICpu _cpu;
private bool _isHigh; private bool _isHigh;
private int _highCycles; private int _highCycles;
private int _totalCycles; private int _totalCycles;
private long _lastCycles; private long _lastCycles;
[JsonIgnore] // only relevant if trying to savestate mid-frame // only relevant if trying to savestate mid-frame
private readonly short[] _buffer = new short[4096]; private readonly short[] _buffer = new short[4096];
[JsonIgnore] // only relevant if trying to savestate mid-frame
private int _position; private int _position;
#region Api public Speaker(MachineEvents events, ICpu cpu)
{
_events = events;
_cpu = cpu;
_events.AddEventDelegate(EventCallbacks.FlushOutput, FlushOutputEvent);
_events.AddEvent(CyclesPerFlush * _cpu.Multiplier, EventCallbacks.FlushOutput);
_isHigh = false;
_highCycles = _totalCycles = 0;
}
public void Sync(IComponentSerializer ser)
{
ser.Sync(nameof(_isHigh), ref _isHigh);
ser.Sync(nameof(_highCycles), ref _highCycles);
ser.Sync(nameof(_totalCycles), ref _totalCycles);
ser.Sync(nameof(_lastCycles), ref _lastCycles);
}
public void Clear() public void Clear()
{ {
@ -66,8 +62,6 @@ namespace Jellyfish.Virtu
_position = 0; _position = 0;
} }
#endregion
public void ToggleOutput() public void ToggleOutput()
{ {
UpdateCycles(); UpdateCycles();
@ -81,7 +75,7 @@ namespace Jellyfish.Virtu
Output(_highCycles * short.MaxValue / _totalCycles); Output(_highCycles * short.MaxValue / _totalCycles);
_highCycles = _totalCycles = 0; _highCycles = _totalCycles = 0;
_events.AddEvent(CyclesPerFlush * _cpu.Multiplier, _flushOutputEvent); _events.AddEvent(CyclesPerFlush * _cpu.Multiplier, EventCallbacks.FlushOutput);
} }
private void UpdateCycles() private void UpdateCycles()
@ -104,12 +98,5 @@ namespace Jellyfish.Virtu
_buffer[_position++] = (short)data; _buffer[_position++] = (short)data;
} }
} }
[OnDeserialized]
private void OnDeserialized(StreamingContext context)
{
_position = 0;
}
} }
} }

View File

@ -1615,8 +1615,7 @@ namespace Jellyfish.Virtu
public const int ModeE = 0xE; public const int ModeE = 0xE;
public const int ModeF = 0xF; public const int ModeF = 0xF;
// ReSharper disable once FieldCanBeMadeReadOnly.Local private readonly Action<int>[] _flushRowMode;
private Action<int>[] _flushRowMode;
private const int Width = 560; private const int Width = 560;
private const int Height = VLineEnterVBlank; private const int Height = VLineEnterVBlank;

View File

@ -1,6 +1,4 @@
using System; using System;
using System.Runtime.Serialization;
using Newtonsoft.Json;
namespace Jellyfish.Virtu namespace Jellyfish.Virtu
{ {
@ -9,7 +7,9 @@ namespace Jellyfish.Virtu
public interface IVideo public interface IVideo
{ {
// ReSharper disable once UnusedMember.Global
int[] GetVideoBuffer(); int[] GetVideoBuffer();
// ReSharper disable once UnusedMember.Global
void Reset(); void Reset();
void DirtyCell(int addressOffset); void DirtyCell(int addressOffset);
@ -20,26 +20,42 @@ namespace Jellyfish.Virtu
void SetCharSet(); void SetCharSet();
bool IsVBlank { get; } bool IsVBlank { get; }
// ReSharper disable once UnusedMember.Global
void Sync(IComponentSerializer ser);
} }
public sealed partial class Video : IVideo public sealed partial class Video : IVideo
{ {
// ReSharper disable once FieldCanBeMadeReadOnly.Local private readonly MachineEvents _events;
private MachineEvents _events; private readonly IMemoryBus _memory;
// ReSharper disable once FieldCanBeMadeReadOnly.Local private readonly int[] _colorPalette = new int[ColorPaletteCount];
private IMemoryBus _memory;
private bool _isVBlank;
private int[] _framebuffer = new int[560 * 384];
private bool _isTextInversed;
private ScannerOptions _scannerOptions;
private int _cyclesPerVBlank;
private int _cyclesPerVBlankPreset;
private int _cyclesPerVSync;
private int _cyclesPerFlash;
private int _vCountPreset;
private int _vLineLeaveVBlank;
private ushort[] _charSet;
private bool _isMonochrome;
private bool[] _isCellDirty = new bool[Height * CellColumns + 1]; // includes sentinel
public Video() { }
public Video(MachineEvents events, IMemoryBus memory) public Video(MachineEvents events, IMemoryBus memory)
{ {
_events = events; _events = events;
_memory = memory; _memory = memory;
_flushRowEvent = FlushRowEvent; // cache delegates; avoids garbage _events.AddEventDelegate(EventCallbacks.FlushRow, FlushRowEvent);
_inverseTextEvent = InverseTextEvent; _events.AddEventDelegate(EventCallbacks.InverseText, InverseTextEvent);
_leaveVBlankEvent = LeaveVBlankEvent; _events.AddEventDelegate(EventCallbacks.LeaveVBlank, LeaveVBlankEvent);
_resetVSyncEvent = ResetVSyncEvent; _events.AddEventDelegate(EventCallbacks.ResetVsync, ResetVSyncEvent);
_flushRowMode = new Action<int>[] _flushRowMode = new Action<int>[]
{ {
@ -73,22 +89,43 @@ namespace Jellyfish.Virtu
IsMonochrome = false; IsMonochrome = false;
ScannerOptions = ScannerOptions.None; ScannerOptions = ScannerOptions.None;
IsVBlank = true; _isVBlank = true;
_events.AddEvent(_cyclesPerVBlankPreset, _leaveVBlankEvent); // align flush events with scanner; assumes vcount preset at start of frame [3-15, 3-16] _events.AddEvent(_cyclesPerVBlankPreset, EventCallbacks.LeaveVBlank); // align flush events with scanner; assumes vcount preset at start of frame [3-15, 3-16]
_events.AddEvent(_cyclesPerVSync, _resetVSyncEvent); _events.AddEvent(_cyclesPerVSync, EventCallbacks.ResetVsync);
_events.AddEvent(_cyclesPerFlash, _inverseTextEvent); _events.AddEvent(_cyclesPerFlash, EventCallbacks.InverseText);
}
public void Sync(IComponentSerializer ser)
{
if (ser.IsReader)
{
int option = 0;
ser.Sync(nameof(_scannerOptions), ref option);
_scannerOptions = (ScannerOptions)option;
}
else
{
int option = (int)_scannerOptions;
ser.Sync(nameof(_scannerOptions), ref option);
}
ser.Sync(nameof(_isVBlank), ref _isVBlank);
ser.Sync(nameof(_framebuffer), ref _framebuffer, false);
ser.Sync(nameof(_isTextInversed), ref _isTextInversed);
ser.Sync(nameof(_cyclesPerVBlank), ref _cyclesPerVBlank);
ser.Sync(nameof(_cyclesPerVBlankPreset), ref _cyclesPerVBlankPreset);
ser.Sync(nameof(_cyclesPerVSync), ref _cyclesPerVSync);
ser.Sync(nameof(_cyclesPerFlash), ref _cyclesPerFlash);
ser.Sync(nameof(_vCountPreset), ref _vCountPreset);
ser.Sync(nameof(_vLineLeaveVBlank), ref _vLineLeaveVBlank);
ser.Sync(nameof(_charSet), ref _charSet, false);
ser.Sync(nameof(_isCellDirty), ref _isCellDirty, false);
ser.Sync(nameof(_isMonochrome), ref _isMonochrome);
} }
// ReSharper disable once UnusedMember.Global // ReSharper disable once UnusedMember.Global
public int[] GetVideoBuffer() => Framebuffer; public int[] GetVideoBuffer() => _framebuffer;
[OnDeserialized]
private void OnDeserialized(StreamingContext context)
{
// the VideoService forgets all of its information on LoadState
DirtyScreen();
}
public void Reset() public void Reset()
{ {
@ -149,7 +186,7 @@ namespace Jellyfish.Virtu
public int ReadFloatingBus() // [5-40] public int ReadFloatingBus() // [5-40]
{ {
// derive scanner counters from current cycles into frame; assumes hcount and vcount preset at start of frame [3-13, 3-15, 3-16] // derive scanner counters from current cycles into frame; assumes hcount and vcount preset at start of frame [3-13, 3-15, 3-16]
int cycles = _cyclesPerVSync - _events.FindEvent(_resetVSyncEvent); int cycles = _cyclesPerVSync - _events.FindEvent(EventCallbacks.ResetVsync);
int hClock = cycles % CyclesPerHSync; int hClock = cycles % CyclesPerHSync;
int hCount = (hClock != 0) ? HCountPreset + hClock - 1 : 0; int hCount = (hClock != 0) ? HCountPreset + hClock - 1 : 0;
int vLine = cycles / CyclesPerHSync; int vLine = cycles / CyclesPerHSync;
@ -918,18 +955,18 @@ namespace Jellyfish.Virtu
private void FlushRowEvent() private void FlushRowEvent()
{ {
int y = (_cyclesPerVSync - _cyclesPerVBlankPreset - _events.FindEvent(_resetVSyncEvent)) / CyclesPerHSync; int y = (_cyclesPerVSync - _cyclesPerVBlankPreset - _events.FindEvent(EventCallbacks.ResetVsync)) / CyclesPerHSync;
_flushRowMode[_memory.VideoMode](y - CellHeight); // in arrears _flushRowMode[_memory.VideoMode](y - CellHeight); // in arrears
if (y < Height) if (y < Height)
{ {
_events.AddEvent(CyclesPerFlush, _flushRowEvent); _events.AddEvent(CyclesPerFlush, EventCallbacks.FlushRow);
} }
else else
{ {
IsVBlank = true; _isVBlank = true;
_events.AddEvent(_cyclesPerVBlank, _leaveVBlankEvent); _events.AddEvent(_cyclesPerVBlank, EventCallbacks.LeaveVBlank);
} }
} }
@ -946,18 +983,18 @@ namespace Jellyfish.Virtu
{ {
_isTextInversed = !_isTextInversed; _isTextInversed = !_isTextInversed;
DirtyScreenText(); DirtyScreenText();
_events.AddEvent(_cyclesPerFlash, _inverseTextEvent); _events.AddEvent(_cyclesPerFlash, EventCallbacks.InverseText);
} }
private void LeaveVBlankEvent() private void LeaveVBlankEvent()
{ {
IsVBlank = false; _isVBlank = false;
_events.AddEvent(CyclesPerFlush, _flushRowEvent); _events.AddEvent(CyclesPerFlush, EventCallbacks.FlushRow);
} }
private void ResetVSyncEvent() private void ResetVSyncEvent()
{ {
_events.AddEvent(_cyclesPerVSync, _resetVSyncEvent); _events.AddEvent(_cyclesPerVSync, EventCallbacks.ResetVsync);
} }
private void SetPalette() private void SetPalette()
@ -1002,13 +1039,10 @@ namespace Jellyfish.Virtu
DirtyScreen(); DirtyScreen();
} }
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Local
private int[] Framebuffer { get; set; } = new int[560 * 384];
private void SetPixel(int x, int y, int color) private void SetPixel(int x, int y, int color)
{ {
int i = 560 * (2 * y) + x; int i = 560 * (2 * y) + x;
Framebuffer[i] = Framebuffer[i + 560] = _colorPalette[color]; _framebuffer[i] = _framebuffer[i + 560] = _colorPalette[color];
} }
private void SetScanner() private void SetScanner()
@ -1030,53 +1064,36 @@ namespace Jellyfish.Virtu
_cyclesPerFlash = VSyncsPerFlash * _cyclesPerVSync; _cyclesPerFlash = VSyncsPerFlash * _cyclesPerVSync;
} }
[JsonIgnore] public bool IsMonochrome
public bool IsMonochrome { get => _isMonochrome; set { _isMonochrome = value; DirtyScreen(); } } {
get => _isMonochrome;
set { _isMonochrome = value; DirtyScreen(); }
}
[JsonIgnore] internal ScannerOptions ScannerOptions
internal ScannerOptions ScannerOptions { get => _scannerOptions; set { _scannerOptions = value; SetScanner(); } } {
get => _scannerOptions;
set { _scannerOptions = value; SetScanner(); }
}
public bool IsVBlank { get; private set; } public bool IsVBlank => _isVBlank;
private Action _flushRowEvent; private readonly int _colorBlack;
private Action _inverseTextEvent; private readonly int _colorDarkBlue;
private Action _leaveVBlankEvent; private readonly int _colorDarkGreen;
private Action _resetVSyncEvent; private readonly int _colorMediumBlue;
private readonly int _colorBrown;
private int _colorBlack; private readonly int _colorLightGrey;
private int _colorDarkBlue; private readonly int _colorGreen;
private int _colorDarkGreen; private readonly int _colorAquamarine;
private int _colorMediumBlue; private readonly int _colorDeepRed;
private int _colorBrown; private readonly int _colorPurple;
private int _colorLightGrey; private readonly int _colorDarkGrey;
private int _colorGreen; private readonly int _colorLightBlue;
private int _colorAquamarine; private readonly int _colorOrange;
private int _colorDeepRed; private readonly int _colorPink;
private int _colorPurple; private readonly int _colorYellow;
private int _colorDarkGrey; private readonly int _colorWhite;
private int _colorLightBlue; private readonly int _colorMonochrome;
private int _colorOrange;
private int _colorPink;
private int _colorYellow;
private int _colorWhite;
private int _colorMonochrome;
[JsonIgnore] // not sync relevant
private bool _isMonochrome;
private bool _isTextInversed;
private ScannerOptions _scannerOptions;
private int _cyclesPerVBlank;
private int _cyclesPerVBlankPreset;
private int _cyclesPerVSync;
private int _cyclesPerFlash;
private int _vCountPreset;
private int _vLineLeaveVBlank;
private ushort[] _charSet;
private int[] _colorPalette = new int[ColorPaletteCount];
[JsonIgnore] // everything is automatically dirtied on load, so no need to save
private bool[] _isCellDirty = new bool[Height * CellColumns + 1]; // includes sentinel
} }
} }

View File

@ -33,9 +33,6 @@
<Prefer32Bit>false</Prefer32Bit> <Prefer32Bit>false</Prefer32Bit>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Newtonsoft.Json">
<HintPath>..\..\References\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Xml.Linq" />
@ -53,6 +50,7 @@
<Compile Include="DiskIIController.cs" /> <Compile Include="DiskIIController.cs" />
<Compile Include="DiskIIDrive.cs" /> <Compile Include="DiskIIDrive.cs" />
<Compile Include="DiskNib.cs" /> <Compile Include="DiskNib.cs" />
<Compile Include="IComponentSerializer.cs" />
<Compile Include="IGamePort.cs" /> <Compile Include="IGamePort.cs" />
<Compile Include="Keyboard.cs" /> <Compile Include="Keyboard.cs" />
<Compile Include="MachineEvents.cs" /> <Compile Include="MachineEvents.cs" />

View File

@ -1,5 +1,6 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CheckNamespace/@EntryIndexedValue">DO_NOT_SHOW</s:String> <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CheckNamespace/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=0DF/@EntryIndexedValue">0DF</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AA/@EntryIndexedValue">AA</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AA/@EntryIndexedValue">AA</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AB/@EntryIndexedValue">AB</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AB/@EntryIndexedValue">AB</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AC/@EntryIndexedValue">AC</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AC/@EntryIndexedValue">AC</s:String>

Binary file not shown.