From c167c043e1263299a3a53f0c6df031f4ffa2fa7a Mon Sep 17 00:00:00 2001 From: goyuken Date: Mon, 8 Oct 2012 14:37:42 +0000 Subject: [PATCH] rework libsnescore's deterministic savestate mode. like before, savestates are created every single frame. unlike before, now they are created on the frame before they "happen". this is all presented invisibly to the user. don't try to load old savestates in deterministic mode. don't try to mix deterministic and non-deterministic savestates. playing back old movies (provided they don't start from a savestate) should cause no problems, but may or may not sync. --- .../Consoles/Nintendo/SNES/LibsnesCore.cs | 197 ++++++++++++++---- 1 file changed, 161 insertions(+), 36 deletions(-) diff --git a/BizHawk.Emulation/Consoles/Nintendo/SNES/LibsnesCore.cs b/BizHawk.Emulation/Consoles/Nintendo/SNES/LibsnesCore.cs index 02f8666d93..178af1dcd9 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/SNES/LibsnesCore.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/SNES/LibsnesCore.cs @@ -286,6 +286,9 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES { public bool IsSGB { get; private set; } + /// disable all external callbacks. the front end should not even know the core is frame advancing + bool nocallbacks = false; + bool disposed = false; public void Dispose() { @@ -439,7 +442,14 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES this.DeterministicEmulation = DeterministicEmulation; if (DeterministicEmulation) // save frame-0 savestate now - CoreSaveStateInternal(true); + { + MemoryStream ms = new MemoryStream(); + BinaryWriter bw = new BinaryWriter(ms); + bw.Write(CoreSaveState()); + bw.Write(true); // framezero, so no controller follows and don't frameadvance on load + bw.Close(); + savestatebuff = ms.ToArray(); + } } //must keep references to these so that they wont get garbage collected @@ -453,7 +463,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES ushort snes_input_state(int port, int device, int index, int id) { - if (CoreInputComm.InputCallback != null) CoreInputComm.InputCallback(); + if (!nocallbacks && CoreInputComm.InputCallback != null) CoreInputComm.InputCallback(); //Console.WriteLine("{0} {1} {2} {3}", port, device, index, id); string key = "P" + (1 + port) + " "; @@ -553,6 +563,21 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES public void FrameAdvance(bool render, bool rendersound) { + // for deterministic emulation, save the state we're going to use before frame advance + // don't do this during nocallbacks though, since it's already been done + if (!nocallbacks && DeterministicEmulation) + { + MemoryStream ms = new MemoryStream(); + BinaryWriter bw = new BinaryWriter(ms); + bw.Write(CoreSaveState()); + bw.Write(false); // not framezero + SnesSaveController ssc = new SnesSaveController(); + ssc.CopyFrom(Controller); + ssc.Serialize(bw); + bw.Close(); + savestatebuff = ms.ToArray(); + } + // speedup when sound rendering is not needed if (!rendersound) LibsnesDll.snes_set_audio_sample(null); @@ -591,11 +616,6 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES if (IsLagFrame) LagCount++; - if (DeterministicEmulation) - { - // save the one internal savestate for this frame now - CoreSaveStateInternal(true); - } } public DisplayType DisplayType @@ -695,6 +715,104 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES } public void ResetFrameCounter() { timeFrameCounter = 0; } + + #region savestates + + /// + /// can freeze a copy of a controller input set and serialize\deserialize it + /// + class SnesSaveController : IController + { + // this is all rather general, so perhaps should be moved out of LibsnesCore + + ControllerDefinition def; + + public SnesSaveController() + { + this.def = null; + } + + public SnesSaveController(ControllerDefinition def) + { + this.def = def; + } + + WorkingDictionary buttons = new WorkingDictionary(); + + /// + /// invalid until CopyFrom has been called + /// + public ControllerDefinition Type + { + get { return def; } + } + + public void Serialize(BinaryWriter b) + { + b.Write(buttons.Keys.Count); + foreach (var k in buttons.Keys) + { + b.Write(k); + b.Write(buttons[k]); + } + } + + /// + /// no checking to see if the deserialized controls match any definition + /// + /// + public void DeSerialize(BinaryReader b) + { + buttons.Clear(); + int numbuttons = b.ReadInt32(); + for (int i = 0; i < numbuttons; i++) + { + string k = b.ReadString(); + float v = b.ReadSingle(); + buttons.Add(k, v); + } + } + + /// + /// this controller's definition changes to that of source + /// + /// + public void CopyFrom(IController source) + { + this.def = source.Type; + buttons.Clear(); + foreach (var k in def.BoolButtons) + buttons.Add(k, source.IsPressed(k) ? 1.0f : 0); + foreach (var k in def.FloatControls) + { + if (buttons.Keys.Contains(k)) + throw new Exception("name collision between bool and float lists!"); + buttons.Add(k, source.GetFloat(k)); + } + } + + public bool this[string button] + { + get { return buttons[button] != 0; } + } + + public bool IsPressed(string button) + { + return buttons[button] != 0; + } + + public float GetFloat(string name) + { + return buttons[name]; + } + + public void UpdateControls(int frame) + { + throw new NotImplementedException(); + } + } + + public void SaveStateText(TextWriter writer) { var temp = SaveStateBinary(); @@ -712,8 +830,10 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES public void SaveStateBinary(BinaryWriter writer) { - byte[] buf = CoreSaveState(); - writer.Write(buf); + if (!DeterministicEmulation) + writer.Write(CoreSaveState()); + else + writer.Write(savestatebuff); // other variables writer.Write(IsLagFrame); @@ -728,6 +848,30 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES byte[] buf = reader.ReadBytes(size); CoreLoadState(buf); + if (DeterministicEmulation) // deserialize controller and fast-foward now + { + // reconstruct savestatebuff at the same time to avoid a costly core serialize + MemoryStream ms = new MemoryStream(); + BinaryWriter bw = new BinaryWriter(ms); + bw.Write(buf); + bool framezero = reader.ReadBoolean(); + bw.Write(framezero); + if (!framezero) + { + SnesSaveController ssc = new SnesSaveController(ControllerDefinition); + ssc.DeSerialize(reader); + IController tmp = this.Controller; + this.Controller = ssc; + nocallbacks = true; + FrameAdvance(false, false); + nocallbacks = false; + this.Controller = tmp; + ssc.Serialize(bw); + } + bw.Close(); + savestatebuff = ms.ToArray(); + } + // other variables IsLagFrame = reader.ReadBoolean(); LagCount = reader.ReadInt32(); @@ -757,40 +901,21 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES /// handle the unmanaged part of savestating /// byte[] CoreSaveState() - { - if (!DeterministicEmulation) - return CoreSaveStateInternal(false); - else - return savestatebuff; - } - - /// - /// most recent internal savestate, for deterministic mode - /// - byte[] savestatebuff; - - /// - /// internal function handling savestate - /// this can cause determinism problems if called improperly! - /// - byte[] CoreSaveStateInternal(bool cache) { int size = LibsnesDll.snes_serialize_size(); byte[] buf = new byte[size]; fixed (byte* pbuf = &buf[0]) LibsnesDll.snes_serialize(new IntPtr(pbuf), size); - if (cache) - { - savestatebuff = buf; - return null; - } - else - { - savestatebuff = null; - return buf; - } + return buf; } + /// + /// most recent internal savestate, for deterministic mode ONLY + /// + byte[] savestatebuff; + + #endregion + // Arbitrary extensible core comm mechanism public CoreInputComm CoreInputComm { get; set; } public CoreOutputComm CoreOutputComm { get { return _CoreOutputComm; } }