Skip to content

Allow redrawing Lua while multiple scripts are open#4682

Open
SuuperW wants to merge 4 commits into
TASEmulators:masterfrom
SuuperW:api-drawing
Open

Allow redrawing Lua while multiple scripts are open#4682
SuuperW wants to merge 4 commits into
TASEmulators:masterfrom
SuuperW:api-drawing

Conversation

@SuuperW

@SuuperW SuuperW commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

Currently if a Lua script wants to re-draw it has to do one or more of:

gui.clearGraphics("client")
gui.clearGraphics("emucore")
gui.cleartext()

and then draw it's stuff. But this clears everything drawn by other scripts as well, without giving them an opportunity to re-draw.

Having multiple Lua scripts that draw was also made difficult by the fact that some things that Lua draws weren't being cleared on each new frame. This means if any Lua script wants to clear what it drew, it must call clearGraphics which again can interfere with other scripts.

So this PR does two things:

  1. Make it so that all things drawn by the Gui API are cleared at the start of each frame. (previously it only cleared things done with DrawString, which makes no sense)
  2. Replace the various clear*** Lua methods with an OnDraw event and a method that raises it.

EDIT: This now also updates other causes of clearing to also call the new draw event.

A Lua script can now do something like:

function onDraw()
	gui.text(30, 50, emu.framecount())
end
gui.addDrawCallback(onDraw)

and another script can call gui.draw() to clear its drawings. The gui.draw call will end up calling the first Lua's onDraw function so that it isn't affected by the second script's wanting to clear.

We could also make it so that the Lua Console calls IGuiApi.Draw when a script is stopped. But this should happen only if the user manually stops it, since a Lua script may want to just draw once and exit, leaving the drawings on screen until the next frame.

Check if completed:

@YoshiRulz YoshiRulz added re: Lua API/scripting Relating to EmuHawk's Lua API (not the Lua Console) re: APIHawk Relating to EmuHawk's public .NET API or to the creation of external tools labels Apr 15, 2026

@YoshiRulz YoshiRulz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

But this clears everything drawn by other scripts as well, without giving them an opportunity to re-draw.

Does that opportunity not come when the script yields? Scripts can clear in a event.onframestart callback then draw in a yield loop.


public void AddDrawCallback(Action callback) => _displayManager.OnDraw += callback;

public void RemoveDrawCallback(Action callback) => _displayManager.OnDraw -= callback;

@YoshiRulz YoshiRulz Apr 15, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ApiHawk already has a draw call batching/scoping method, WithSurface.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That's completely unrelated. WithSurface(surfaceId, action) is just a wrapper for UseSurface(surfaceId); action(); UseSurface(originalSurfaceId). The new callbacks don't care about which surface is being drawn, it's just a signal to draw.

@YoshiRulz YoshiRulz Apr 23, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I know, I'm saying if you're going to limit draw calls to callbacks, use the callback which already exists and which I've been promoting for this reason. edit: #4799

@SuuperW SuuperW Apr 23, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I don't understand how WithSurface could be used in a way that is similar to AddDrawCallback.
And if you were to change how WithSurface works, that would be an API change that I think is better done while changing the name, so that existing code will fail at compile time instead of run time. (better yet, have both so existing code keeps working, at least until the old way is deprecated)

EDIT:

if you're going to limit draw calls to callbacks

This PR does not do that. Old Lua scripts should continue to work exactly as they have before.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You unresolved this conversation. Why? Also I do not see how the PR mention you edited in is relevant to this PR, and I still don't know why you think WithSurface is relevant to this PR.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

My WithSurface PR is relevant because it makes the cited changes redundant.
Do not merge this PR before reverting the changes to IGuiApi.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Your PR does not address the issue that this PR addresses (the one referred to in the title and the one I explain in OP).

Here's a concrete example of the issue my PR addresses. Take these two Lua scripts that draw on alternating frames:

local y = 100
local function draw()
	-- We want to draw something when some condition is met
	local dummyCondition = emu.framecount() % 2 == 1
	if dummyCondition then
		gui.drawRectangle(10, y, 50, 50, "white", "white")
	else
		-- Since we aren't drawing anything, we have to manually clear.
		gui.clearGraphics()
	end
end
while true do
	draw()
	emu.frameadvance()
end
local y = 10
local function draw()
	-- We want to draw something when some condition is met
	local dummyCondition = emu.framecount() % 2 == 0
	if dummyCondition then
		gui.drawRectangle(10, y, 50, 50, "white", "white")
	else
		-- Since we aren't drawing anything, we have to manually clear.
		gui.clearGraphics()
	end
end
while true do
	draw()
	emu.frameadvance()
end

If you run both of them, you will only see the rectangle from one script. After my PR, gui.clearGraphics() will not work and is also not necessary, so you'll then be able to see both flashing rectangles.

For another example:

y_manual = 10
function draw_manual()
	-- a redraw being triggered by some user action
	-- since there's no new frame, we have to clear
	gui.clearGraphics()
	gui.drawRectangle(100, y_manual, 50, 50, "white", "white")
end
while true do
	draw_manual()
	emu.frameadvance()
end

Using non-local variable and function so that you can use it with the Lua Console text box. When some user action triggers a re-draw, it'll clear the previous graphics and draw again. You cannot ever see the rectangle of either of the first two scripts after manually calling draw_manual(). This will not be automatically fixed in the PR, but a user would see the deprecation warning pointing them to the method they need to use when updating the scripts.

@SuuperW

SuuperW commented Apr 15, 2026

Copy link
Copy Markdown
Contributor Author

Text written with gui.text (the one for OSD text) is not cleared in between yields, so they will build up indefinitely while paused. (a single script can of course call gui.cleartext but then that interferes with other scripts) Even if the text drawn is identical each time, this will quickly cause the client to lag while paused.

Drawing in yield instead of on frame advance also causes the Lua drawings to flicker, as the game will be rendered once without any Lua drawings before yield gets the chance to draw. You could probably get around this by drawing in the frame end callback, but then you are double-drawing or have to write extra code to avoid that. And even ignoring the flicker, writing a script this way is somewhat more difficult and not an obvious option.

Plus, drawing in a yield loop wastes a bit of CPU while paused.

SuuperW added 3 commits July 21, 2026 21:02
…hroughout the years such that the comment accidentally got put with the wrong code and the right code doesn't exist anymore
…awings (except clear APIs, about to be deprecated)
@SuuperW

SuuperW commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

Updated so that other things that cleared graphics also call the new draw event:

  • closing the Lua console (also changed to stop all Lua scripts, so they don't just redraw)
  • removing a Lua script
  • clicking the erase button (tooltip says this is specifically for "stale" or "stuck" stuff)
  • loading a savestate

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

re: APIHawk Relating to EmuHawk's public .NET API or to the creation of external tools re: Lua API/scripting Relating to EmuHawk's Lua API (not the Lua Console)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants