From 033378333f3cba5ab1f9db451cd8b6e056c0a9e3 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Wed, 31 Jan 2024 20:49:39 +0000 Subject: [PATCH] Standardise how we discard "char" events One common issue we get when a program exits after handling a "key" event is that it leaves the "char" event on the queue. This means that the shell (or whatever program we switch in to) then receives the "char" event, often displaying it to the screen. Previously we've got around this by doing sleep(0) before exiting the program. However, we also see this problem in edit's run handler script, and I'm less comfortable doing the same hack there. This adds a new internal discard_char function, which will either wait one tick or return when seeing a char/key_up event. Fixes #1705 --- .../rom/modules/main/cc/internal/event.lua | 37 ++++++++++++++++ .../computercraft/lua/rom/programs/edit.lua | 1 + .../lua/rom/programs/fun/advanced/paint.lua | 2 +- .../computercraft/lua/rom/programs/help.lua | 2 +- .../spec/modules/cc/internal/event_spec.lua | 43 +++++++++++++++++++ 5 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/cc/internal/event.lua create mode 100644 projects/core/src/test/resources/test-rom/spec/modules/cc/internal/event_spec.lua diff --git a/projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/cc/internal/event.lua b/projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/cc/internal/event.lua new file mode 100644 index 000000000..d0696cc8d --- /dev/null +++ b/projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/cc/internal/event.lua @@ -0,0 +1,37 @@ +-- SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers +-- +-- SPDX-License-Identifier: MPL-2.0 + +--[[- Utilities for working with events. + +> [!DANGER] +> This is an internal module and SHOULD NOT be used in your own code. It may +> be removed or changed at any time. + +@local +]] + +--[[- +Attempt to discard a [`event!char`] event that may follow a [`event!key`] event. + +This attempts to flush the event queue via a timer, stopping early if we observe +another key or char event. + +We flush the event queue by waiting a single tick. It is technically possible +the key and char events will be delivered in different ticks, but it should be +very rare, and not worth adding extra delay for. +]] +local function discard_char() + local timer = os.startTimer(0) + while true do + local event, id = os.pullEvent() + if event == "timer" and id == timer then break + elseif event == "char" or event == "key" or event == "key_up" then + os.cancelTimer(timer) + break + end + end +end + + +return { discard_char = discard_char } diff --git a/projects/core/src/main/resources/data/computercraft/lua/rom/programs/edit.lua b/projects/core/src/main/resources/data/computercraft/lua/rom/programs/edit.lua index 05c35d956..e05ec1766 100644 --- a/projects/core/src/main/resources/data/computercraft/lua/rom/programs/edit.lua +++ b/projects/core/src/main/resources/data/computercraft/lua/rom/programs/edit.lua @@ -88,6 +88,7 @@ for i = 1, #wrapped do term.write(wrapped[i]) end os.pullEvent('key') +require "cc.internal.event".discard_char() ]] -- Menus diff --git a/projects/core/src/main/resources/data/computercraft/lua/rom/programs/fun/advanced/paint.lua b/projects/core/src/main/resources/data/computercraft/lua/rom/programs/fun/advanced/paint.lua index 5e53367de..8b28a0851 100644 --- a/projects/core/src/main/resources/data/computercraft/lua/rom/programs/fun/advanced/paint.lua +++ b/projects/core/src/main/resources/data/computercraft/lua/rom/programs/fun/advanced/paint.lua @@ -300,7 +300,7 @@ local menu_choices = { return false end, Exit = function() - sleep(0) -- Super janky, but consumes stray "char" events from pressing Ctrl then E separately. + require "cc.internal.event".discard_char() -- Consume stray "char" events from pressing Ctrl then E separately. return true end, } diff --git a/projects/core/src/main/resources/data/computercraft/lua/rom/programs/help.lua b/projects/core/src/main/resources/data/computercraft/lua/rom/programs/help.lua index 3ae6ac7f7..1dba69ae0 100644 --- a/projects/core/src/main/resources/data/computercraft/lua/rom/programs/help.lua +++ b/projects/core/src/main/resources/data/computercraft/lua/rom/programs/help.lua @@ -257,7 +257,7 @@ while true do offset = print_height - content_height draw() elseif param == keys.q then - sleep(0) -- Super janky, but consumes stray "char" events. + require "cc.internal.event".discard_char() break end elseif event == "mouse_scroll" then diff --git a/projects/core/src/test/resources/test-rom/spec/modules/cc/internal/event_spec.lua b/projects/core/src/test/resources/test-rom/spec/modules/cc/internal/event_spec.lua new file mode 100644 index 000000000..1e9ebd2f1 --- /dev/null +++ b/projects/core/src/test/resources/test-rom/spec/modules/cc/internal/event_spec.lua @@ -0,0 +1,43 @@ +-- SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers +-- +-- SPDX-License-Identifier: MPL-2.0 + +local timeout = require "test_helpers".timeout + +describe("cc.internal.event", function() + local event = require "cc.internal.event" + describe("discard_char", function() + + local function test(events) + local unique_event = "flush_" .. math.random(2 ^ 30) + + -- Queue and pull to flush the queue once. + os.queueEvent(unique_event) + os.pullEvent(unique_event) + + -- Queue our desired events + for i = 1, #events do os.queueEvent(table.unpack(events[i])) end + + timeout(0.1, function() + event.discard_char() + + -- Then read the remainder of the event queue, and check there's + -- no char event. + os.queueEvent(unique_event) + while true do + local event = os.pullEvent() + if event == unique_event then break end + expect(event):ne("char") + end + end) + end + + it("discards char events", function() + test { { "char", "a" } } + end) + + it("handles an empty event queue", function() + test {} + end) + end) +end)