diff --git a/Assets/Lua/GBA/SonicAdvance_CamHack.lua b/Assets/Lua/GBA/SonicAdvance_CamHack.lua index c861f29c72d..2311476f3f1 100644 --- a/Assets/Lua/GBA/SonicAdvance_CamHack.lua +++ b/Assets/Lua/GBA/SonicAdvance_CamHack.lua @@ -6,27 +6,26 @@ local addr_offY = 0x5B98 local addr_camX = 0x59D0 local addr_camY = 0x59D2 -while true do - client.invisibleemulation(true) - local memorystate = memorysavestate.savecorestate() +local id = client.show_future(function(f) + if f == 0 then + offX = mainmemory.read_u16_le(addr_offX) + offY = mainmemory.read_u16_le(addr_offY) + camX = mainmemory.read_u16_le(addr_camX) + camY = mainmemory.read_u16_le(addr_camY) + + Xval = camX + offX - 128 + Yval = camY + offY - 80 + + mainmemory.write_u16_le(addr_camX, Xval) + mainmemory.write_u16_le(addr_camY, Yval) + elseif f == 2 then + return true + end - offX = mainmemory.read_u16_le(addr_offX) - offY = mainmemory.read_u16_le(addr_offY) - camX = mainmemory.read_u16_le(addr_camX) - camY = mainmemory.read_u16_le(addr_camY) - - Xval = camX + offX - 128 - Yval = camY + offY - 80 - - mainmemory.write_u16_le(addr_camX, Xval) - mainmemory.write_u16_le(addr_camY, Yval) - - client.seekframe(emu.framecount()+1) - client.invisibleemulation(false) - client.seekframe(emu.framecount()+1) - client.invisibleemulation(true) - memorysavestate.loadcorestate(memorystate) - memorysavestate.removestate(memorystate) --- client.invisibleemulation(false) + return false +end, 2) +event.onexit(function() event.unregisterbyid(id) end) + +while true do emu.frameadvance() -end \ No newline at end of file +end diff --git a/src/BizHawk.Client.Common/Api/Classes/EmuClientApi.cs b/src/BizHawk.Client.Common/Api/Classes/EmuClientApi.cs index ed58b2a16d8..00eed4f7ae9 100644 --- a/src/BizHawk.Client.Common/Api/Classes/EmuClientApi.cs +++ b/src/BizHawk.Client.Common/Api/Classes/EmuClientApi.cs @@ -135,8 +135,6 @@ public void FrameSkip(int numFrames) public int GetWindowSize() => _config.GetWindowScaleFor(Emulator.SystemId); - public void InvisibleEmulation(bool invisible) => _mainForm.InvisibleEmulation = invisible; - public bool IsPaused() => _mainForm.EmulatorPaused; public bool IsSeeking() => _mainForm.IsSeeking; @@ -176,13 +174,6 @@ public void Screenshot(string path) public int ScreenWidth() => _displayManager.GetPanelNativeSize().Width; - public void SeekFrame(int frame) - { - var wasPaused = _mainForm.EmulatorPaused; - while (Emulator.Frame != frame) _mainForm.SeekFrameAdvance(); - if (!wasPaused) _mainForm.UnpauseEmulator(); - } - public void SetClientExtraPadding(int left, int top, int right, int bottom) { _displayManager.ClientExtraPadding = (left, top, right, bottom); @@ -218,6 +209,12 @@ public void SetWindowSize(int size) } } + public void ShowFuture(Func preFrameCallback, int maxFrames) + { + _mainForm.PreFutureFrameCallback = preFrameCallback; + _mainForm.MaxFutureFrames = maxFrames; + } + public void SpeedMode(int percent) { if (percent is > 0 and <= 6400) _mainForm.ClickSpeedItem(percent); diff --git a/src/BizHawk.Client.Common/Api/Interfaces/IEmuClientApi.cs b/src/BizHawk.Client.Common/Api/Interfaces/IEmuClientApi.cs index d27353282d7..3373fb12a72 100644 --- a/src/BizHawk.Client.Common/Api/Interfaces/IEmuClientApi.cs +++ b/src/BizHawk.Client.Common/Api/Interfaces/IEmuClientApi.cs @@ -71,12 +71,6 @@ public interface IEmuClientApi : IDisposable, IExternalApi int GetWindowSize(); - /// - /// Use with for CamHack. - /// Refer to MainForm.InvisibleEmulation for the workflow details. - /// - void InvisibleEmulation(bool invisible); - bool IsPaused(); bool IsSeeking(); @@ -116,12 +110,6 @@ public interface IEmuClientApi : IDisposable, IExternalApi int ScreenWidth(); - /// - /// Use with for CamHack. - /// Refer to MainForm.InvisibleEmulation for the workflow details. - /// - void SeekFrame(int frame); - /// /// Sets the extra padding added to the 'native' surface so that you can draw HUD elements in predictable placements /// @@ -148,6 +136,13 @@ public interface IEmuClientApi : IDisposable, IExternalApi void SetWindowSize(int size); + /// + /// Tell the client to display a frame from the future, instead of the current frame. + /// + /// This will be called before emulating each future frame. Emulation will stop when this function returns true, and the just-run frame will be displaed. Pass null to disable future frame display. + /// The maximum number of future frames to emulate. Useful to avoid freezing the client UI in case of accidentally never returning true from the callback. + void ShowFuture(Func preFrameCallback, int maxFrames); + void SpeedMode(int percent); void TogglePause(); diff --git a/src/BizHawk.Client.Common/IMainFormForApi.cs b/src/BizHawk.Client.Common/IMainFormForApi.cs index 3306b601e87..73e5bc7baa9 100644 --- a/src/BizHawk.Client.Common/IMainFormForApi.cs +++ b/src/BizHawk.Client.Common/IMainFormForApi.cs @@ -16,9 +16,6 @@ public interface IMainFormForApi /// only referenced from bool EmulatorPaused { get; } - /// only referenced from - bool InvisibleEmulation { get; set; } - /// only referenced from bool IsSeeking { get; } @@ -28,12 +25,18 @@ public interface IMainFormForApi /// only referenced from bool IsRewinding { get; } + /// only referenced from + public int MaxFutureFrames { get; set; } + /// only referenced from (HttpCommunication HTTP, MemoryMappedFiles MMF, SocketServer Sockets) NetworkingHelpers { get; } /// only referenced from bool PauseAvi { get; set; } + /// only referenced from + public Func/*?*/ PreFutureFrameCallback { get; set; } + /// only referenced from void ClearHolds(); @@ -103,9 +106,6 @@ public interface IMainFormForApi /// referenced from and void SaveState(string path, string userFriendlyStateName, bool fromLua = false, bool suppressOSD = false); - /// only referenced from - void SeekFrameAdvance(); - /// only referenced from void StepRunLoop_Throttle(); diff --git a/src/BizHawk.Client.Common/lua/CommonLibs/ClientLuaLibrary.cs b/src/BizHawk.Client.Common/lua/CommonLibs/ClientLuaLibrary.cs index a411dee0199..60aea36d8f2 100644 --- a/src/BizHawk.Client.Common/lua/CommonLibs/ClientLuaLibrary.cs +++ b/src/BizHawk.Client.Common/lua/CommonLibs/ClientLuaLibrary.cs @@ -17,6 +17,8 @@ namespace BizHawk.Client.Common [Description("A library for manipulating the EmuHawk client UI")] public sealed class ClientLuaLibrary : LuaLibraryBase { + public NLFAddCallback CreateAndRegisterNamedFunction { get; set; } + [OptionalService] private IVideoProvider VideoProvider { get; set; } @@ -94,18 +96,6 @@ public void FrameSkip(int numFrames) public string GetLuaEngine() => "NLua+Lua"; - [LuaMethodExample("client.invisibleemulation( true );")] - [LuaMethod("invisibleemulation", "Enters/exits turbo mode and disables/enables most emulator updates.")] - public void InvisibleEmulation(bool invisible) - => APIs.EmuClient.InvisibleEmulation(invisible); - - [LuaDeprecatedMethod] - [LuaMethod("seekframe", "Does nothing. Use the pause/unpause functions instead and a loop that waits for the desired frame.")] - public void SeekFrame(int frame) - { - Log("Deprecated function client.seekframe() used. Replace the call with pause/unpause functions and a loop that waits for the desired frame."); - } - [LuaMethodExample("local sounds_terrible = client.get_approx_framerate() < 55;")] [LuaMethod("get_approx_framerate", "Gets the (host) framerate, approximated from frame durations.")] public int GetApproxFramerate() @@ -278,6 +268,20 @@ public int ScreenWidth() public void SetWindowSize(int size) => APIs.EmuClient.SetWindowSize(size); + [LuaMethodExample("client.show_future(function(frame) return frame == 1 end, 1)")] + [LuaMethod( + name: "show_future", + description: "Tell the client to display a frame from the future, instead of the current frame. The given lua function " + + "will be called before each future frame is emulated until the function returns true, at which point future " + + "emulation will be stopped and the most recently emulated frame will be displayed.")] + public string ShowFuture(LuaFunction luaf, int maxFrames, string name = null) + { + INamedLuaFunction nlf = CreateAndRegisterNamedFunction(luaf, NamedLuaFunction.EVENT_TYPE_FUTURE, LogOutputCallback, CurrentFile, name: name); + APIs.EmuClient.ShowFuture(nlf.FutureCallback, maxFrames); + nlf.OnRemove += () => APIs.EmuClient.ShowFuture(null, 0); + return nlf.GuidStr; + } + [LuaMethodExample("client.speedmode( 75 );")] [LuaMethod("speedmode", "Sets the speed of the emulator (in terms of percent)")] public void SpeedMode(int percent) diff --git a/src/BizHawk.Client.Common/lua/INamedLuaFunction.cs b/src/BizHawk.Client.Common/lua/INamedLuaFunction.cs index f8306cd2091..04023328c11 100644 --- a/src/BizHawk.Client.Common/lua/INamedLuaFunction.cs +++ b/src/BizHawk.Client.Common/lua/INamedLuaFunction.cs @@ -6,6 +6,8 @@ public interface INamedLuaFunction { Action InputCallback { get; } + Func FutureCallback { get; } + Guid Guid { get; } string GuidStr { get; } diff --git a/src/BizHawk.Client.Common/lua/NamedLuaFunction.cs b/src/BizHawk.Client.Common/lua/NamedLuaFunction.cs index d39b8a7b384..f823f79d2bb 100644 --- a/src/BizHawk.Client.Common/lua/NamedLuaFunction.cs +++ b/src/BizHawk.Client.Common/lua/NamedLuaFunction.cs @@ -28,6 +28,8 @@ public sealed class NamedLuaFunction : INamedLuaFunction public const string EVENT_TYPE_SAVESTATE = "OnSavestateSave"; + public const string EVENT_TYPE_FUTURE = "BeforeFutureFrame"; + private readonly LuaFunction _function; public Action/*?*/ OnRemove { get; set; } = null; @@ -81,6 +83,7 @@ public NamedLuaFunction(LuaFunction function, string theEvent, Action lo luaLibraries.IsInInputOrMemoryCallback = false; } }; + FutureCallback = (f) => Callback([ f ]) is [ bool r ] ? r : false; MemCallback = (addr, val, flags) => { luaLibraries.IsInInputOrMemoryCallback = true; @@ -135,6 +138,8 @@ public string GuidStr public Action InputCallback { get; } + public Func FutureCallback { get; } + public MemoryCallbackDelegate MemCallback { get; } public Action RandomCallback { get; } diff --git a/src/BizHawk.Client.EmuHawk/IMainFormForTools.cs b/src/BizHawk.Client.EmuHawk/IMainFormForTools.cs index 88f31370942..214cee5f709 100644 --- a/src/BizHawk.Client.EmuHawk/IMainFormForTools.cs +++ b/src/BizHawk.Client.EmuHawk/IMainFormForTools.cs @@ -24,9 +24,6 @@ public interface IMainFormForTools : IDialogController /// only referenced from bool HoldFrameAdvance { get; set; } - /// only referenced from - bool InvisibleEmulation { get; set; } - /// only referenced from bool IsFastForwarding { get; } diff --git a/src/BizHawk.Client.EmuHawk/MainForm.cs b/src/BizHawk.Client.EmuHawk/MainForm.cs index f0bef6b27ce..2fe147867fa 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.cs @@ -1102,27 +1102,6 @@ private set public bool HoldFrameAdvance { get; set; } // necessary for tastudio > button public bool PressRewind { get; set; } // necessary for tastudio < button - /// - /// Disables updates for video/audio, and enters "turbo" mode. - /// Can be used to replicate Gens-rr's "latency compensation" that involves: - /// - /// Saving a no-framebuffer state that is stored in RAM - /// Emulating forth for some frames with updates disabled - /// - /// Optionally hacking in-game memory - /// (like camera position, to show off-screen areas) - /// - /// Updating the screen - /// Loading the no-framebuffer state from RAM - /// - /// The most common use case is CamHack for Sonic games. - /// Accessing this from Lua allows to keep internal code hacks to minimum. - /// - /// - /// - /// - public bool InvisibleEmulation { get; set; } - private long MouseWheelTracker; private int? _pauseOnFrame; @@ -1146,7 +1125,7 @@ private set public bool IsSeeking => PauseOnFrame.HasValue; private bool IsTurboSeeking => PauseOnFrame.HasValue && Config.TurboSeek; - public bool IsTurboing => InputManager.ClientControls["Turbo"] || IsTurboSeeking || InvisibleEmulation; + public bool IsTurboing => InputManager.ClientControls["Turbo"] || IsTurboSeeking; public bool IsFastForwarding => InputManager.ClientControls["Fast Forward"] || IsTurboing; public bool IsRewinding { get; private set; } @@ -1207,6 +1186,10 @@ private set public event StateSavedEventHandler SavestateSaved; + public Func/*?*/ PreFutureFrameCallback { get; set; } + + public int MaxFutureFrames { get; set; } + private readonly InputManager InputManager; private IVideoProvider _currentVideoProvider = NullVideo.Instance; @@ -2894,14 +2877,6 @@ public void FrameAdvance(bool discardApiHawkSurfaces) } } - public void SeekFrameAdvance() - { - PressFrameAdvance = true; - StepRunLoop_Core(true); - DisplayManager.DiscardApiHawkSurfaces(); - PressFrameAdvance = false; - } - private void StepRunLoop_Core(bool force = false) { var runFrame = false; @@ -3006,13 +2981,10 @@ private void StepRunLoop_Core(bool force = false) Tools.UpdateToolsBefore(); } - if (!InvisibleEmulation) - { - CaptureRewind(isRewinding); - } + CaptureRewind(isRewinding); // Set volume, if enabled - if (Config.SoundEnabledNormal && !InvisibleEmulation) + if (Config.SoundEnabledNormal) { atten = Config.SoundVolume / 100.0f; @@ -3057,7 +3029,7 @@ private void StepRunLoop_Core(bool force = false) } bool atTurboSeekEnd = IsTurboSeeking && Emulator.Frame == PauseOnFrame.Value - 1; - bool render = !InvisibleEmulation && (!_throttle.skipNextFrame || _currAviWriter?.UsesVideo is true || atTurboSeekEnd); + bool render = !_throttle.skipNextFrame || _currAviWriter?.UsesVideo is true || atTurboSeekEnd; bool newFrame = Emulator.FrameAdvance(InputManager.ControllerOutput, render, renderSound); MovieSession.HandleFrameAfter(ToolBypassingMovieEndAction is not null); @@ -3098,11 +3070,6 @@ private void StepRunLoop_Core(bool force = false) } } - if (!PauseAvi && newFrame && !InvisibleEmulation) - { - AvFrameAdvance(); - } - if (newFrame) { _framesSinceLastFpsUpdate++; @@ -3120,6 +3087,33 @@ private void StepRunLoop_Core(bool force = false) } _wasRewinding = isRewinding; + + if (newFrame && PreFutureFrameCallback != null) + { + IStatable statable = Emulator.AsStatable(); + MemoryStream state = new(); + statable.SaveStateBinary(new(state)); + + int frameCount = 0; + while (!PreFutureFrameCallback(frameCount) && frameCount < MaxFutureFrames) + { + frameCount++; + MovieSession.HandleFrameBefore(); + Emulator.FrameAdvance(InputManager.ControllerOutput, true, false); + CheatList.Pulse(); + // No tools updates here. No existing tool (except Lua, but that gets the ShowFutureFrameCallback) needs to do anything. + // Maybe in the future we'll add a special update type, or add a callback for this. + // Note that other callbacks (e.g. memory hooks) are still being used. + } + + state.Seek(0, SeekOrigin.Begin); + statable.LoadStateBinary(new(state)); + } + + if (!PauseAvi && newFrame) + { + AvFrameAdvance(); + } } else if (isRewinding) { diff --git a/src/BizHawk.Client.EmuHawk/tools/BasicBot/BasicBot.Designer.cs b/src/BizHawk.Client.EmuHawk/tools/BasicBot/BasicBot.Designer.cs index 077cf892d3e..a889cf36cff 100644 --- a/src/BizHawk.Client.EmuHawk/tools/BasicBot/BasicBot.Designer.cs +++ b/src/BizHawk.Client.EmuHawk/tools/BasicBot/BasicBot.Designer.cs @@ -102,7 +102,6 @@ private void InitializeComponent() this.label8 = new BizHawk.WinForms.Controls.LocLabelEx(); this.StartFromSlotBox = new System.Windows.Forms.ComboBox(); this.ControlGroupBox = new System.Windows.Forms.GroupBox(); - this.InvisibleEmulationCheckBox = new System.Windows.Forms.CheckBox(); this.panel2 = new System.Windows.Forms.Panel(); this.StatsContextMenu = new System.Windows.Forms.ContextMenuStrip(this.components); this.ClearStatsContextMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx(); @@ -955,7 +954,6 @@ private void InitializeComponent() // this.ControlGroupBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.ControlGroupBox.Controls.Add(this.InvisibleEmulationCheckBox); this.ControlGroupBox.Controls.Add(this.panel2); this.ControlGroupBox.Controls.Add(this.StopBtn); this.ControlGroupBox.Controls.Add(this.RunBtn); @@ -968,17 +966,6 @@ private void InitializeComponent() this.ControlGroupBox.TabStop = false; this.ControlGroupBox.Text = "Control"; // - // InvisibleEmulationCheckBox - // - this.InvisibleEmulationCheckBox.AutoSize = true; - this.InvisibleEmulationCheckBox.Location = new System.Drawing.Point(88, 60); - this.InvisibleEmulationCheckBox.Name = "InvisibleEmulationCheckBox"; - this.InvisibleEmulationCheckBox.Size = new System.Drawing.Size(127, 17); - this.InvisibleEmulationCheckBox.TabIndex = 2004; - this.InvisibleEmulationCheckBox.Text = "Turn Off Audio/Video"; - this.InvisibleEmulationCheckBox.UseVisualStyleBackColor = true; - this.InvisibleEmulationCheckBox.CheckedChanged += new System.EventHandler(this.InvisibleEmulationCheckBox_CheckedChanged); - // // panel2 // this.panel2.ContextMenuStrip = this.StatsContextMenu; @@ -1149,6 +1136,5 @@ private void InitializeComponent() private System.Windows.Forms.Button btnCopyBestInput; private System.Windows.Forms.ToolTip toolTip1; private BizHawk.WinForms.Controls.ToolStripMenuItemEx helpToolStripMenuItem; - private System.Windows.Forms.CheckBox InvisibleEmulationCheckBox; } } diff --git a/src/BizHawk.Client.EmuHawk/tools/BasicBot/BasicBot.cs b/src/BizHawk.Client.EmuHawk/tools/BasicBot/BasicBot.cs index b32b7e5c327..57b36807041 100644 --- a/src/BizHawk.Client.EmuHawk/tools/BasicBot/BasicBot.cs +++ b/src/BizHawk.Client.EmuHawk/tools/BasicBot/BasicBot.cs @@ -58,7 +58,6 @@ private string CurrentFileName private Dictionary _cachedControlProbabilities; private bool _previousDisplayMessage; - private bool _previousInvisibleEmulation; [RequiredService] private IEmulator Emulator { get; set; } @@ -76,7 +75,6 @@ public class BasicBotSettings { public RecentFiles RecentBotFiles { get; set; } = new RecentFiles(); public bool TurboWhenBotting { get; set; } = true; - public bool InvisibleEmulation { get; set; } } private string _windowTitle = "Basic Bot"; @@ -142,7 +140,6 @@ private void BasicBot_Load(object sender, EventArgs e) if (OSTailoredCode.IsUnixHost) ClientSize = new(707, 587); - _previousInvisibleEmulation = InvisibleEmulationCheckBox.Checked = Settings.InvisibleEmulation; _previousDisplayMessage = Config.DisplayMessages; Closing += (_, _) => StopBot(); } @@ -1045,12 +1042,6 @@ private void StartBot() SetMaxSpeed(); } - if (InvisibleEmulationCheckBox.Checked) - { - _previousInvisibleEmulation = MainForm.InvisibleEmulation; - MainForm.InvisibleEmulation = true; - } - UpdateBotStatusIcon(); MessageLabel.Text = "Running..."; } @@ -1103,7 +1094,6 @@ private void StopBot() private void RestoreConfigFlags() { Config.DisplayMessages = _previousDisplayMessage; - MainForm.InvisibleEmulation = _previousInvisibleEmulation; var movie = MovieSession.Movie; if (movie.IsRecording()) movie.IsCountingRerecords = _oldCountingSetting; } @@ -1303,9 +1293,6 @@ private void HelpToolStripMenuItem_Click(object sender, EventArgs e) Util.OpenUrlExternal("https://tasvideos.org/Bizhawk/BasicBot"); } - private void InvisibleEmulationCheckBox_CheckedChanged(object sender, EventArgs e) - => Settings.InvisibleEmulation = !Settings.InvisibleEmulation; - private void MaximizeAddressBox_TextChanged(object sender, EventArgs e) { AssessRunButtonStatus(); diff --git a/src/BizHawk.Client.EmuHawk/tools/Lua/LuaLibraries.cs b/src/BizHawk.Client.EmuHawk/tools/Lua/LuaLibraries.cs index 57c2d91383a..65e4ff97cc1 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Lua/LuaLibraries.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Lua/LuaLibraries.cs @@ -90,6 +90,7 @@ void EnumerateLuaFunctions(string name, Type type, LuaLibraryBase instance) if (instance is ClientLuaLibrary clientLib) { clientLib.MainForm = _mainForm; + clientLib.CreateAndRegisterNamedFunction = CreateAndRegisterNamedFunction; } else if (instance is ConsoleLuaLibrary consoleLib) {