Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
75e6f68
Make loading of EmuHawk Lua libraries optional, from the perspective …
SuuperW Dec 11, 2025
b75184d
Remove final references to EmuHawk types in LuaLibraries.
SuuperW Dec 11, 2025
fd38382
Make an interface for API use of ToolManager.
SuuperW Dec 11, 2025
be0b47b
Move things to BizHawk.Client.Common
SuuperW Dec 11, 2025
1d88d33
Create tests for Lua events.
SuuperW Dec 11, 2025
4156f2d
remove unused properties and unnecessary parameter
SuuperW Dec 4, 2025
c5565b6
simplify constructors
SuuperW Dec 4, 2025
a912e2e
Make current file tracking non-static by moving it to LuaLibraries in…
SuuperW Dec 7, 2025
7c9bec0
Make Sandbox method non-static, in preparation for future commits.
SuuperW Dec 9, 2025
7d8f63f
Remove static DefaultLogger, use exception callback parameter instead.
SuuperW Dec 9, 2025
3407d40
Handle setting CurrentFile in Sandbox. Fixes some issues where callba…
SuuperW Dec 9, 2025
6d5b13f
Catch all exceptions in sandbox, there's no reason to differentiate b…
SuuperW Dec 9, 2025
cfe976b
Use the sandbox exception logic.
SuuperW Dec 9, 2025
13d75d9
Make each LuaFile own its own registered functions.
SuuperW Dec 4, 2025
7830f2e
Don't stop scripts when they have registered functions. Passes most p…
SuuperW Dec 10, 2025
32c8a62
Make LuaFile.State setter private, and handle state change through me…
SuuperW Dec 4, 2025
f135c93
Move handling of special Lua callbacks out of NamedLuaFunction.
SuuperW Dec 5, 2025
cfda77d
Keep Lua scripts running if a LuaWinform is open, like if an event is…
SuuperW Dec 10, 2025
5d8228f
Make TAStudio callbacks behave like other Lua callbacks: support more…
SuuperW Dec 10, 2025
7790c71
Fix forms.destroy; don't attempt to update/clear current file/thread …
SuuperW Dec 10, 2025
1b5a5af
return event IDs for TAStudio Lua callbacks and accept name
SuuperW Feb 11, 2026
579f828
fix: functions registered without a file weren't handled
SuuperW Feb 12, 2026
c3f27c9
fixes for Lua forms
SuuperW Feb 14, 2026
0dd564b
Inform users who try to use yeilding Lua methods from callbacks that …
SuuperW Feb 14, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,29 @@
using System.Linq;
using System.Reflection;

using BizHawk.Client.Common;
using BizHawk.Emulation.Common;

namespace BizHawk.Client.EmuHawk
namespace BizHawk.Client.Common
{
public static class ApiManager
{
private static readonly IReadOnlyList<(Type ImplType, Type InterfaceType, ConstructorInfo Ctor, Type[] CtorTypes)> _apiTypes;
private static readonly List<(Type ImplType, Type InterfaceType, ConstructorInfo Ctor, Type[] CtorTypes)> _apiTypes = new();

static ApiManager()
{
var list = new List<(Type, Type, ConstructorInfo, Type[])>();
foreach (var implType in ReflectionCache_Biz_Cli_Com.Types.Concat(ReflectionCache.Types)
foreach (var implType in ReflectionCache_Biz_Cli_Com.Types
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
foreach (var implType in ReflectionCache_Biz_Cli_Com.Types
foreach (var implType in ReflectionCache.Types

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree. I think the explicit type name is better.

.Where(t => /*t.IsClass &&*/t.IsSealed)) // small optimisation; api impl. types are all sealed classes
{
var interfaceType = implType.GetInterfaces().FirstOrDefault(t => typeof(IExternalApi).IsAssignableFrom(t) && t != typeof(IExternalApi));
if (interfaceType == null) continue; // if we couldn't determine what it's implementing, then it's not an api impl. type
var ctor = implType.GetConstructors().Single();
list.Add((implType, interfaceType, ctor, ctor.GetParameters().Select(pi => pi.ParameterType).ToArray()));
AddApiType(implType);
}
_apiTypes = list.ToArray();
}

public static void AddApiType(Type type)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't like this (used in EmuHawk.ToolApi..cctor). public static state should be immutable, and certainly not affected by call order.

Copy link
Contributor Author

@SuuperW SuuperW Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not affected by call order. The order of things in the list doesn't matter. And if it did matter, it would be an existing problem, not introduced in this PR. (Since the order given by ReflectionCache is not defined.)

The only place it is called is in other static constructors. It is not mutated after static initialization.

I do agree that this solution isn't ideal, but I do not think it's any worse than using reflection to find the types. Finding a good solution to this is outside the scope of this PR.

EDIT: There was an issue here actually, with the static constructor not running. That has been fixed by moving it to ToolManager.

{
var interfaceType = type.GetInterfaces().FirstOrDefault(t => typeof(IExternalApi).IsAssignableFrom(t) && t != typeof(IExternalApi));
if (interfaceType == null) return; // if we couldn't determine what it's implementing, then it's not an api impl. type
var ctor = type.GetConstructors().Single();
_apiTypes.Add((type, interfaceType, ctor, ctor.GetParameters().Select(pi => pi.ParameterType).ToArray()));
}

private static ApiContainer? _container;
Expand All @@ -38,7 +40,7 @@ private static ApiContainer Register(
DisplayManagerBase displayManager,
InputManager inputManager,
IMovieSession movieSession,
ToolManager toolManager,
IToolLoader toolManager,
Config config,
IEmulator emulator,
IGameInfo game,
Expand All @@ -52,7 +54,7 @@ private static ApiContainer Register(
[typeof(DisplayManagerBase)] = displayManager,
[typeof(InputManager)] = inputManager,
[typeof(IMovieSession)] = movieSession,
[typeof(ToolManager)] = toolManager,
[typeof(IToolLoader)] = toolManager,
[typeof(Config)] = config,
[typeof(IEmulator)] = emulator,
[typeof(IGameInfo)] = game,
Expand All @@ -74,7 +76,7 @@ public static IExternalApiProvider Restart(
DisplayManagerBase displayManager,
InputManager inputManager,
IMovieSession movieSession,
ToolManager toolManager,
IToolLoader toolManager,
Config config,
IEmulator emulator,
IGameInfo game,
Expand All @@ -92,7 +94,7 @@ public static ApiContainer RestartLua(
DisplayManagerBase displayManager,
InputManager inputManager,
IMovieSession movieSession,
ToolManager toolManager,
IToolLoader toolManager,
Config config,
IEmulator emulator,
IGameInfo game,
Expand Down
105 changes: 105 additions & 0 deletions src/BizHawk.Client.Common/IMainFormForTools.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
using BizHawk.Bizware.Graphics;
using BizHawk.Emulation.Common;

namespace BizHawk.Client.Common
{
public interface IMainFormForTools : IDialogController
{
/// <remarks>referenced by 3 or more tools</remarks>
CheatCollection CheatList { get; }

/// <remarks>referenced by 3 or more tools</remarks>
string CurrentlyOpenRom { get; }

/// <remarks>referenced from HexEditor and RetroAchievements</remarks>
LoadRomArgs CurrentlyOpenRomArgs { get; }

/// <remarks>only referenced from TAStudio</remarks>
bool EmulatorPaused { get; }

/// <remarks>only referenced from PlaybackBox</remarks>
bool HoldFrameAdvance { get; set; }

/// <remarks>only referenced from BasicBot</remarks>
bool InvisibleEmulation { get; set; }

/// <remarks>only referenced from LuaConsole</remarks>
bool IsTurboing { get; }

/// <remarks>only referenced from TAStudio</remarks>
bool IsFastForwarding { get; }

/// <remarks>referenced from PlayMovie and TAStudio</remarks>
int? PauseOnFrame { get; set; }

/// <remarks>only referenced from PlaybackBox</remarks>
bool PressRewind { get; set; }

/// <remarks>referenced from BookmarksBranchesBox and VideoWriterChooserForm</remarks>
BitmapBuffer CaptureOSD();

/// <remarks>only referenced from TAStudio</remarks>
void DisableRewind();

/// <remarks>only referenced from TAStudio</remarks>
void EnableRewind(bool enabled);

/// <remarks>only referenced from TAStudio</remarks>
bool EnsureCoreIsAccurate();

/// <remarks>only referenced from TAStudio</remarks>
void FrameAdvance(bool discardApiHawkSurfaces = true);

/// <remarks>only referenced from LuaConsole</remarks>
/// <param name="forceWindowResize">Override <see cref="Common.Config.ResizeWithFramebuffer"/></param>
void FrameBufferResized(bool forceWindowResize = false);

/// <remarks>only referenced from BasicBot</remarks>
bool LoadQuickSave(int slot, bool suppressOSD = false);

/// <remarks>referenced from MultiDiskBundler and RetroAchievements</remarks>
bool LoadRom(string path, LoadRomArgs args);

/// <remarks>only referenced from BookmarksBranchesBox</remarks>
BitmapBuffer MakeScreenshotImage();

/// <remarks>referenced from ToolFormBase</remarks>
void MaybePauseFromMenuOpened();

/// <remarks>referenced from ToolFormBase</remarks>
void MaybeUnpauseFromMenuClosed();

/// <remarks>referenced by 3 or more tools</remarks>
void PauseEmulator();

/// <remarks>only referenced from TAStudio</remarks>
bool BlockFrameAdvance { get; set; }

/// <remarks>referenced from PlaybackBox and TAStudio</remarks>
void SetMainformMovieInfo();

/// <remarks>referenced by 3 or more tools</remarks>
bool StartNewMovie(IMovie movie, bool newMovie);

/// <remarks>only referenced from BasicBot</remarks>
void Throttle();

/// <remarks>only referenced from TAStudio</remarks>
void TogglePause();

/// <remarks>referenced by 3 or more tools</remarks>
void UnpauseEmulator();

/// <remarks>only referenced from BasicBot</remarks>
void Unthrottle();

/// <remarks>only referenced from LogWindow</remarks>
void UpdateDumpInfo(RomStatus? newStatus = null);

/// <remarks>only referenced from BookmarksBranchesBox</remarks>
void UpdateStatusSlots();

/// <remarks>only referenced from TAStudio</remarks>
void UpdateWindowTitle();
}
}
7 changes: 7 additions & 0 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 Lazy<string> AllAPINames { get; set; }

[OptionalService]
private IVideoProvider VideoProvider { get; set; }

Expand Down Expand Up @@ -111,6 +113,11 @@ public void SeekFrame(int frame)
public int GetApproxFramerate()
=> APIs.EmuClient.GetApproxFramerate();

[LuaMethodExample("local stconget = client.getluafunctionslist( );")]
[LuaMethod("getluafunctionslist", "returns a list of implemented functions")]
public string GetLuaFunctionsList()
=> AllAPINames.Value;

[LuaMethodExample("local incliget = client.gettargetscanlineintensity( );")]
[LuaMethod("gettargetscanlineintensity", "Gets the current scanline intensity setting, used for the scanline display filter")]
public int GetTargetScanlineIntensity()
Expand Down
4 changes: 4 additions & 0 deletions src/BizHawk.Client.Common/lua/ILuaLibraries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ namespace BizHawk.Client.Common
{
public interface ILuaLibraries
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this interface exist, again? b3c7f0f eliminated the multiple-implementations aspect, so is this just an abstraction to make it usable from Client.Common? I feel like we should be able to get rid of it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why should those callback properties stay? I do not see any value in keeping such callback logic in NamedLuaFunction. They logically belong with the API code, and are clutter in NamedLuaFunction. I also do not see any difference between these two and any of the other special callbacks. (And there are a good number of them, including some for TAStudio that were never put in NamedLuaFunction.)

It could make sense to keep them if these two are combined, as a way of putting all the sets of IsInInputOrMemoryCallback in one place. Although I think that could use a better name, such as IsInCallbackDuringFrame. Then again, an even better solution might be to set it at the start and end of every frame (and call it FrameIsRunning or something like that) rather than waiting until a callback happens. Since the purpose appears to be preventing calls to certain API methods that should not run mid-frame.

I am not sure why ILuaLibraries exists. Your guess is the same thing I would guess. I can remove it if you want, but I think that change can just as well be done after/outside this PR.

{
LuaFile CurrentFile { get; }

/// <remarks>pretty hacky... we don't want a lua script to be able to restart itself by rebooting the core</remarks>
bool IsRebootingCore { get; set; }

Expand All @@ -13,5 +15,7 @@ public interface ILuaLibraries
PathEntryCollection PathEntries { get; }

NLuaTableHelper GetTableHelper();

void Sandbox(LuaFile luaFile, Action callback, Action<string> exceptionCallback = null);
}
}
26 changes: 14 additions & 12 deletions src/BizHawk.Client.Common/lua/INamedLuaFunction.cs
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
using BizHawk.Emulation.Common;

namespace BizHawk.Client.Common
{
public interface INamedLuaFunction
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait, if LuaFunctionList : IEnumerable<NamedLuaFunction>, why does this interface exist?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not know. Looks like a good thing to remove to clean up the code. The git log shows you created the interface, and a quick look makes me think it was something to do with Unix/Win32 but I don't think that separation exists anymore. e4a0175

I will remove it if you think that's a good idea.

{
Action InputCallback { get; }

Guid Guid { get; }

string GuidStr { get; }

MemoryCallbackDelegate MemCallback { get; }

/// <summary>for <c>doom.on_prandom</c>; single param: caller of RNG, per categories <see href="https://github.com/TASEmulators/dsda-doom/blob/7f03360ce0e9000c394fb99869d78adf4603ade5/prboom2/src/m_random.h#L63-L133">in source</see></summary>
Action<int> RandomCallback { get; }

/// <summary>for <c>doom.on_use and doom.on_cross</c>; two params: pointers to activated line and to mobj that triggered it</summary>
Action<long, long> LineCallback { get; }

string Name { get; }

/// <summary>
/// Will be called when the Lua function is unregistered / removed from the list of active callbacks.
/// The intended use case is to support callback systems that don't directly support Lua.
/// Here's what that looks like:
/// 1) A NamedLuaFunction is created and added to it's owner's list of registered functions, as normal with all Lua functions.
/// 2) A C# function is created for this specific NamedLuaFunction, which calls the Lua function via <see cref="Call(object[])"/> and possibly does other related Lua setup and cleanup tasks.
/// 3) That C# function is added to the non-Lua callback system.
/// 4) <see cref="OnRemove"/> is assigned an <see cref="Action"/> that removes the C# function from the non-Lua callback.
/// </summary>
Action OnRemove { get; set; }

/// <summary>
/// Calls the Lua function with the given arguments.
/// </summary>
object[] Call(object[] args);
}
}
7 changes: 7 additions & 0 deletions src/BizHawk.Client.Common/lua/IPrintingLibrary.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace BizHawk.Client.Common
{
public interface IPrintingLibrary
{
void Log(params object[] outputs);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be in LuaLibraryBase with the other log function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why LuaLibraryBase? If you're thinking we can then remove the LogCallback property in the various API classes, that won't work. (For one, that's in the base API classes, not the Lua wrappers. For two, it is set differently for Lua Console, external tools, and tests.)
Anyway it cannot be done right now because the Lua log method references LuaConsole, an EmuHawk type.

}
}
7 changes: 7 additions & 0 deletions src/BizHawk.Client.Common/lua/IRegisterFunctions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace BizHawk.Client.Common
{
public interface IRegisterFunctions
{
LuaLibraryBase.NLFAddCallback CreateAndRegisterNamedFunction { get; set; }
}
}
Loading