Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 21 additions & 22 deletions Assets/Lua/GBA/SonicAdvance_CamHack.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
end
15 changes: 6 additions & 9 deletions src/BizHawk.Client.Common/Api/Classes/EmuClientApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -218,6 +209,12 @@ public void SetWindowSize(int size)
}
}

public void ShowFuture(Func<int, bool> preFrameCallback, int maxFrames)
{
_mainForm.PreFutureFrameCallback = preFrameCallback;
_mainForm.MaxFutureFrames = maxFrames;
}

public void SpeedMode(int percent)
{
if (percent is > 0 and <= 6400) _mainForm.ClickSpeedItem(percent);
Expand Down
19 changes: 7 additions & 12 deletions src/BizHawk.Client.Common/Api/Interfaces/IEmuClientApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,6 @@ public interface IEmuClientApi : IDisposable, IExternalApi

int GetWindowSize();

/// <summary>
/// Use with <see cref="SeekFrame(int)"/> for CamHack.
/// Refer to <c>MainForm.InvisibleEmulation</c> for the workflow details.
/// </summary>
void InvisibleEmulation(bool invisible);

bool IsPaused();

bool IsSeeking();
Expand Down Expand Up @@ -116,12 +110,6 @@ public interface IEmuClientApi : IDisposable, IExternalApi

int ScreenWidth();

/// <summary>
/// Use with <see cref="InvisibleEmulation(bool)"/> for CamHack.
/// Refer to <c>MainForm.InvisibleEmulation</c> for the workflow details.
/// </summary>
void SeekFrame(int frame);

/// <summary>
/// Sets the extra padding added to the 'native' surface so that you can draw HUD elements in predictable placements
/// </summary>
Expand All @@ -148,6 +136,13 @@ public interface IEmuClientApi : IDisposable, IExternalApi

void SetWindowSize(int size);

/// <summary>
/// Tell the client to display a frame from the future, instead of the current frame.
/// </summary>
/// <param name="preFrameCallback">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.</param>
/// <param name="maxFrames">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.</param>
void ShowFuture(Func<int, bool> preFrameCallback, int maxFrames);

void SpeedMode(int percent);

void TogglePause();
Expand Down
12 changes: 6 additions & 6 deletions src/BizHawk.Client.Common/IMainFormForApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@ public interface IMainFormForApi
/// <remarks>only referenced from <see cref="EmuClientApi"/></remarks>
bool EmulatorPaused { get; }

/// <remarks>only referenced from <see cref="EmuClientApi"/></remarks>
bool InvisibleEmulation { get; set; }

/// <remarks>only referenced from <see cref="EmuClientApi"/></remarks>
bool IsSeeking { get; }

Expand All @@ -28,12 +25,18 @@ public interface IMainFormForApi
/// <remarks>only referenced from <see cref="EmuClientApi"/></remarks>
bool IsRewinding { get; }

/// <remarks>only referenced from <see cref="EmuClientApi"/></remarks>
public int MaxFutureFrames { get; set; }

/// <remarks>only referenced from <see cref="CommApi"/></remarks>
(HttpCommunication HTTP, MemoryMappedFiles MMF, SocketServer Sockets) NetworkingHelpers { get; }

/// <remarks>only referenced from <see cref="EmuClientApi"/></remarks>
bool PauseAvi { get; set; }

/// <remarks>only referenced from <see cref="EmuClientApi"/></remarks>
public Func<int, bool>/*?*/ PreFutureFrameCallback { get; set; }

/// <remarks>only referenced from <see cref="EmuClientApi"/></remarks>
void ClearHolds();

Expand Down Expand Up @@ -103,9 +106,6 @@ public interface IMainFormForApi
/// <remarks>referenced from <see cref="EmuClientApi"/> and <see cref="SaveStateApi"/></remarks>
void SaveState(string path, string userFriendlyStateName, bool fromLua = false, bool suppressOSD = false);

/// <remarks>only referenced from <see cref="EmuClientApi"/></remarks>
void SeekFrameAdvance();

/// <remarks>only referenced from <see cref="EmuClientApi"/></remarks>
void StepRunLoop_Throttle();

Expand Down
28 changes: 16 additions & 12 deletions src/BizHawk.Client.Common/lua/CommonLibs/ClientLuaLibrary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions src/BizHawk.Client.Common/lua/INamedLuaFunction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ public interface INamedLuaFunction
{
Action InputCallback { get; }

Func<int, bool> FutureCallback { get; }

Guid Guid { get; }

string GuidStr { get; }
Expand Down
5 changes: 5 additions & 0 deletions src/BizHawk.Client.Common/lua/NamedLuaFunction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -81,6 +83,7 @@ public NamedLuaFunction(LuaFunction function, string theEvent, Action<string> lo
luaLibraries.IsInInputOrMemoryCallback = false;
}
};
FutureCallback = (f) => Callback([ f ]) is [ bool r ] ? r : false;
MemCallback = (addr, val, flags) =>
{
luaLibraries.IsInInputOrMemoryCallback = true;
Expand Down Expand Up @@ -135,6 +138,8 @@ public string GuidStr

public Action InputCallback { get; }

public Func<int, bool> FutureCallback { get; }

public MemoryCallbackDelegate MemCallback { get; }

public Action<int> RandomCallback { get; }
Expand Down
3 changes: 0 additions & 3 deletions src/BizHawk.Client.EmuHawk/IMainFormForTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@ public interface IMainFormForTools : IDialogController
/// <remarks>only referenced from <see cref="PlaybackBox"/></remarks>
bool HoldFrameAdvance { get; set; }

/// <remarks>only referenced from <see cref="BasicBot"/></remarks>
bool InvisibleEmulation { get; set; }

/// <remarks>only referenced from <see cref="TAStudio"/></remarks>
bool IsFastForwarding { get; }

Expand Down
76 changes: 35 additions & 41 deletions src/BizHawk.Client.EmuHawk/MainForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1102,27 +1102,6 @@ private set
public bool HoldFrameAdvance { get; set; } // necessary for tastudio > button
public bool PressRewind { get; set; } // necessary for tastudio < button

/// <summary>
/// Disables updates for video/audio, and enters "turbo" mode.
/// Can be used to replicate Gens-rr's "latency compensation" that involves:
/// <list type="bullet">
/// <item><description>Saving a no-framebuffer state that is stored in RAM</description></item>
/// <item><description>Emulating forth for some frames with updates disabled</description></item>
/// <item><description><list type="bullet">
/// <item><description>Optionally hacking in-game memory
/// (like camera position, to show off-screen areas)</description></item>
/// </list></description></item>
/// <item><description>Updating the screen</description></item>
/// <item><description>Loading the no-framebuffer state from RAM</description></item>
/// </list>
/// The most common use case is CamHack for Sonic games.
/// Accessing this from Lua allows to keep internal code hacks to minimum.
/// <list type="bullet">
/// <item><description><see cref="ClientLuaLibrary.InvisibleEmulation(bool)"/></description></item>
/// </list>
/// </summary>
public bool InvisibleEmulation { get; set; }

private long MouseWheelTracker;

private int? _pauseOnFrame;
Expand All @@ -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; }

Expand Down Expand Up @@ -1207,6 +1186,10 @@ private set

public event StateSavedEventHandler SavestateSaved;

public Func<int, bool>/*?*/ PreFutureFrameCallback { get; set; }

public int MaxFutureFrames { get; set; }

private readonly InputManager InputManager;

private IVideoProvider _currentVideoProvider = NullVideo.Instance;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -3098,11 +3070,6 @@ private void StepRunLoop_Core(bool force = false)
}
}

if (!PauseAvi && newFrame && !InvisibleEmulation)
{
AvFrameAdvance();
}

if (newFrame)
{
_framesSinceLastFpsUpdate++;
Expand All @@ -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)
{
Expand Down
Loading