TASEmulators / BizHawk

BizHawk is a multi-system emulator written in C#. BizHawk provides nice features for casual gamers such as full screen, and joypad support in addition to full rerecording and debugging tools for all system cores.
http://tasvideos.org/BizHawk.html
Other
2.2k stars 385 forks source link

Lua: gui.drawRectangle() no longer working in event.onframeend() handler #2595

Closed Isotarge closed 3 years ago

Isotarge commented 3 years ago

Summary

On 2.6 (and the dev build of adca19c30a816c1627a7c436cc8776a918ba5f36) the following Lua script no longer draws the rectangle when the emulator is unpaused. On 2.5.2 and before it draws a rectangle every frame when the emulator is unpaused as expected.

local function drawRect()
    gui.drawText(32, 32, "Rect");
    gui.drawRectangle(32, 32, 32, 16);
end

local function everyFrame()
    if not client.ispaused() then
        drawRect();
    end
end

event.onframeend(everyFrame);

while true do
    if client.ispaused() then
        drawRect();
    end
    emu.yield();
end

Repro

  1. Run the script above with any ROM (I used Taz-Mania (E) (SMS))
  2. Pause and unpause emulation to toggle the rectangle (expected to be rendered every frame, like 2.5.2 and before)

Host env.

xy2i commented 3 years ago

Can confirm this. Furthermore, nothing in gui works with event.onframeend. Here's a script to repro:

local function main()
    print("output every frame")
    gui.pixelText(20, 20, "draw every frame")
end

event.onframeend(main)

When run, this script prints to the console every frame, but it doesn't draw the text.

YoshiRulz commented 3 years ago

I think there are some events that will still have this problem. If drawing in a callback still isn't working for you, but you believe your use-case is reasonable, let me know.

Isotarge commented 3 years ago

Thanks for taking a look at this, I just downloaded the dev build of 643d7b12dd8a69e3318e10bbdc6e64a0a622e3b7 and unfortunately the script I posted up the top of this issue report still doesn't draw a rectangle when the emulator is unpaused.

YoshiRulz commented 3 years ago

That's working as intended. If I refactor the script and include the implied DrawNew/DrawFinish, it's clear what's actually happening:

local drawRect = function()
    gui.drawText(32, 32, "Rect");
    gui.drawRectangle(32, 32, 32, 16);
end

event.onframeend(function()
--  gui.DrawNew("emu", true);

    if not client.ispaused() then
        drawRect();
    end

--  gui.DrawFinish();
end);

while true do
--  gui.DrawNew("emu", true); -- not guarded by `if client.ispaused()` so will always clear scripts' drawing when the Lua Console updates

    if client.ispaused() then
        drawRect();
    end

--  gui.DrawFinish();
    emu.yield();
end

With blessings from @adelikat I have an idea for making the automatic locking (DrawNew) less aggressive, so nothing will be cleared until the next gui lib call.

Isotarge commented 3 years ago

I see what you mean, and I like the idea of making the automatic locking less aggressive. If it helps I can explain why we've set it up this way for ScriptHawk. We're using the onframeend hook to read memory and redraw everything exactly once per frame while the emulator is running, but the while true emu.yield() hook reads & redraws as fast as possible while emulation is paused to make sure the OSD doesn't desync from RAM if you poke & prod stuff while paused. It's been a long time, but if I recall correctly, doing everything in the while true loop (even while emulation is unpaused) caused performance issues. Perhaps a reasonable compromise could be something like this (eliminating the onframeend hook altogether)?

while true do
--  gui.DrawNew("emu", true); -- not guarded by `if client.ispaused()` so will always clear scripts' drawing when the Lua Console updates
    drawRect();
    if client.ispaused() then
        emu.yield();
    else
        emu.frameadvance();
    end
--  gui.DrawFinish();
end
Isotarge commented 3 years ago

Tried this and no luck sadly :( It never prints paused for me, on screen or in the console.

while true do
    gui.drawRectangle(32, 32, 32, 16);
    if client.ispaused() then
        print("Paused");
        gui.text(32, 32, "PAUSED");
        gui.drawText(32, 32, "PAUSED");
        emu.yield();
    else
        print("Running");
        gui.text(32, 40, "RUNNING");
        gui.drawText(32, 40, "RUNNING");
        emu.frameadvance();
    end
end
YoshiRulz commented 3 years ago

Looking at the script in OP again (using 2.5.2), the rectangle isn't actually drawn every frame while paused, even though the draw calls are every frame. Only when unpausing do the draw calls from the while loop take effect, before the draw calls from the event clear them on the next frame. What I mean by that is if you make them draw in red and blue, as below, you'll see a flash of red when unpausing. While paused, you see the stale blue rect from the last frame before you paused.

local drawRect = function(b)
    local y = b and 32 or 64;
    local c = b and 0xFF0000FF or 0xFFFF0000;
    console.write(b and "B" or "A");
    gui.drawText(32, y, "Rect", c);
    gui.drawRectangle(32, y, 32, 16, c);
end

event.onframeend(function()
    if not client.ispaused() then
        drawRect(true);
    end
end);

while true do
    if client.ispaused() then
        drawRect(false);
    end
    emu.yield();
end

I've committed a8b1e06e4 with my suggested change from above. Though it doesn't restore the old behaviour, your script will now work as you originally intended.