From 045601effb01ea4cc2efeab7f87041e45772a1fc Mon Sep 17 00:00:00 2001 From: lawnjelly Date: Sun, 14 May 2023 13:30:32 +0100 Subject: [PATCH] Timestamped input buffering - prevent stalling and improve timing Input was resulting in stalling and ANRs on Android and possibly other platforms. This PR separates buffered input into two parts to prevent stalls. It also timestamps input events and applies them on the relevant logical physics tick, rather than attempting to apply them "realtime". --- core/input/input.cpp | 293 +++++++++++++++++--- core/input/input.h | 93 ++++++- doc/classes/ProjectSettings.xml | 5 +- main/main.cpp | 27 +- main/main.h | 1 - main/main_timer_sync.cpp | 17 +- main/main_timer_sync.h | 6 + platform/android/display_server_android.cpp | 2 + platform/android/os_android.cpp | 4 +- 9 files changed, 378 insertions(+), 70 deletions(-) diff --git a/core/input/input.cpp b/core/input/input.cpp index 13483894813..e3052021596 100644 --- a/core/input/input.cpp +++ b/core/input/input.cpp @@ -80,10 +80,190 @@ void (*Input::warp_mouse_func)(const Vector2 &p_position) = nullptr; Input::CursorShape (*Input::get_current_cursor_shape_func)() = nullptr; void (*Input::set_custom_mouse_cursor_func)(const Ref &, Input::CursorShape, const Vector2 &) = nullptr; +// Accumulating immediately rather than deferred at flush +// is slightly more efficient (because of less allocations / transfer to the main buffer etc) +// but is less accurate timing wise, so should only be used in frame buffering mode. +void InputEventBuffer::accumulate_or_push_event(const Ref &p_event, uint64_t p_timestamp) { + // Events can come in any time, including when we are preparing to read the incoming queue, + // so we must lock to prevent race condition. + MutexLock lock(data.incoming_mutex); + + LocalVector &incoming = data.incoming[data.incoming_write]; + + // First, attempt to accumulate. + if (incoming.size()) { + Event &prev = incoming[incoming.size() - 1]; + if (prev.event->accumulate(p_event)) { + return; + } + } + + // Accumulate failed, fall back to push. + incoming.resize(incoming.size() + 1); + Event &e = incoming[incoming.size() - 1]; + e.event = p_event; + e.timestamp = p_timestamp; +} + +void InputEventBuffer::push_event(const Ref &p_event, uint64_t p_timestamp) { + // Events can come in any time, including when we are preparing to read the incoming queue, + // so we must lock to prevent race condition. + MutexLock lock(data.incoming_mutex); + + LocalVector &incoming = data.incoming[data.incoming_write]; + incoming.resize(incoming.size() + 1); + Event &e = incoming[incoming.size() - 1]; + e.event = p_event; + e.timestamp = p_timestamp; +} + +void InputEventBuffer::_try_accumulate(uint64_t p_timestamp) { + // Try and accumulate events after the current front + // until we fail or pass the current timestamp. + List::Element *front = data.buffer.front(); + Event &front_event = front->get(); + + while (List::Element *next = data.buffer.front()->next()) { + const Event &next_event = next->get(); + if (next_event.timestamp > p_timestamp) { + // Don't want to accumulate events that are on the next tick.. + // want to keep some resolution to the events. + break; + } + if (front_event.event->accumulate(next_event.event)) { + // Remove the accumulated event from the buffer. + data.buffer.swap(front, next); + data.buffer.pop_front(); + // Check this does not invalidate front and front_event. + DEV_ASSERT(front == data.buffer.front()); + DEV_ASSERT(&front_event == &front->get()); + } else { + break; + } + } +} + +void InputEventBuffer::flush_events(uint64_t p_current_timestamp, Input &r_input_handler, bool p_accumulate) { + // Flushing function is not re-entrant. + // This is unlikely to be called multithread, but this check should be cheap. + MutexLock lock(data.buffer_mutex); + + // Only allow one flush at a time. + // This *does* occur notably on starting android debugging + // from the editor, where Main::iteration is called recursively. + if (data.flushing) { + return; + } + data.flushing = true; + + data.incoming_mutex.lock(); + SWAP(data.incoming_write, data.incoming_read); + data.incoming_mutex.unlock(); + + LocalVector &incoming = data.incoming[data.incoming_read]; + + for (uint32_t n = 0; n < incoming.size(); n++) { + // Copy to main buffer. + data.buffer.push_back(incoming[n]); + } + + // Prepare for more input next time, prevent leak. + incoming.clear(); + + // Now we can read through the input buffer, up to the current time, and process. + while (data.buffer.front()) { + const Event &e = data.buffer.front()->get(); + + // Timestamp within range? + if (e.timestamp > p_current_timestamp) { + // We are up to date, process no more input on this tick / frame. + break; + } + + if (p_accumulate) { + _try_accumulate(p_current_timestamp); + } + + r_input_handler._parse_input_event_impl(e.event, false, false); + + // Event processed, remove from buffer. + data.buffer.pop_front(); + } + + data.flushing = false; +} + Input *Input::get_singleton() { return singleton; } +void Input::flush_buffered_events_post_frame() { + if (data.use_legacy_flushing) { + // Matches old logic - if buffering, but not agile. + if (data.buffering_mode == BUFFERING_MODE_FRAME) { + _flush_buffered_events_ex(UINT64_MAX); + } + } +} + +void Input::flush_buffered_events() { + _flush_buffered_events_ex(UINT64_MAX); +} + +void Input::set_use_accumulated_input(bool p_enable) { + data.use_accumulated_input = p_enable; + _update_buffering_mode(); +} + +void Input::set_use_input_buffering(bool p_enable) { + data.use_buffering = p_enable; + _update_buffering_mode(); +} + +bool Input::is_using_input_buffering() const { + return data.use_buffering; +} + +void Input::set_use_agile_flushing(bool p_enable) { + data.use_agile = p_enable; + _update_buffering_mode(); +} + +bool Input::is_agile_flushing() const { + return data.buffering_mode == BUFFERING_MODE_AGILE; +} + +void Input::set_use_legacy_flushing(bool p_enable) { + data.use_legacy_flushing = p_enable; +} + +void Input::set_has_input_thread(bool p_has_thread) { + data.has_input_thread = p_has_thread; + _update_buffering_mode(); +} + +void Input::_update_buffering_mode() { + // Logic here may appear confusing but it is historical, + // and to prevent compat breaking. + // Accumulated input was added in such a way as to override + // the use_buffering setting. + // i.e. Buffering is used if use_buffering is false, but accumulated is true, + // as accumulating needs the previous event in order to work. + + // Additionally for agile input, it currently only makes sense to activate when + // the platform has a separate input thread, otherwise the extra processing + // on flush on each tick just wastes CPU. + if (data.use_accumulated_input || data.use_buffering) { + if (data.use_agile && data.has_input_thread) { + data.buffering_mode = BUFFERING_MODE_AGILE; + } else { + data.buffering_mode = BUFFERING_MODE_FRAME; + } + } else { + data.buffering_mode = BUFFERING_MODE_NONE; + } +} + void Input::set_mouse_mode(MouseMode p_mode) { ERR_FAIL_INDEX((int)p_mode, 5); set_mouse_mode_func(p_mode); @@ -140,6 +320,7 @@ void Input::_bind_methods() { ClassDB::bind_method(D_METHOD("parse_input_event", "event"), &Input::parse_input_event); ClassDB::bind_method(D_METHOD("set_use_accumulated_input", "enable"), &Input::set_use_accumulated_input); ClassDB::bind_method(D_METHOD("is_using_accumulated_input"), &Input::is_using_accumulated_input); + ClassDB::bind_method(D_METHOD("flush_buffered_events"), &Input::flush_buffered_events); ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_mode"), "set_mouse_mode", "get_mouse_mode"); @@ -489,7 +670,7 @@ Vector3 Input::get_gyroscope() const { return gyroscope; } -void Input::_parse_input_event_impl(const Ref &p_event, bool p_is_emulated) { +void Input::_parse_input_event_impl(const Ref &p_event, bool p_is_emulated, bool p_unlock) { // This function does the final delivery of the input event to user land. // Regardless where the event came from originally, this has to happen on the main thread. DEV_ASSERT(Thread::get_caller_id() == Thread::get_main_id()); @@ -546,9 +727,13 @@ void Input::_parse_input_event_impl(const Ref &p_event, bool p_is_em touch_event->set_position(mb->get_position()); touch_event->set_double_tap(mb->is_double_click()); touch_event->set_device(InputEvent::DEVICE_ID_EMULATION); - _THREAD_SAFE_UNLOCK_ - event_dispatch_function(touch_event); - _THREAD_SAFE_LOCK_ + if (p_unlock) { + _THREAD_SAFE_UNLOCK_ + event_dispatch_function(touch_event); + _THREAD_SAFE_LOCK_ + } else { + event_dispatch_function(touch_event); + } } } @@ -574,9 +759,13 @@ void Input::_parse_input_event_impl(const Ref &p_event, bool p_is_em drag_event->set_velocity(get_last_mouse_velocity()); drag_event->set_device(InputEvent::DEVICE_ID_EMULATION); - _THREAD_SAFE_UNLOCK_ - event_dispatch_function(drag_event); - _THREAD_SAFE_LOCK_ + if (p_unlock) { + _THREAD_SAFE_UNLOCK_ + event_dispatch_function(drag_event); + _THREAD_SAFE_LOCK_ + } else { + event_dispatch_function(drag_event); + } } } @@ -626,7 +815,7 @@ void Input::_parse_input_event_impl(const Ref &p_event, bool p_is_em } button_event->set_button_mask(ev_bm); - _parse_input_event_impl(button_event, true); + _parse_input_event_impl(button_event, true, p_unlock); } } } @@ -652,7 +841,7 @@ void Input::_parse_input_event_impl(const Ref &p_event, bool p_is_em motion_event->set_velocity(sd->get_velocity()); motion_event->set_button_mask(mouse_button_mask); - _parse_input_event_impl(motion_event, true); + _parse_input_event_impl(motion_event, true, p_unlock); } } @@ -678,9 +867,13 @@ void Input::_parse_input_event_impl(const Ref &p_event, bool p_is_em if (ge.is_valid()) { if (event_dispatch_function) { - _THREAD_SAFE_UNLOCK_ - event_dispatch_function(ge); - _THREAD_SAFE_LOCK_ + if (p_unlock) { + _THREAD_SAFE_UNLOCK_ + event_dispatch_function(ge); + _THREAD_SAFE_LOCK_ + } else { + event_dispatch_function(ge); + } } } @@ -703,9 +896,13 @@ void Input::_parse_input_event_impl(const Ref &p_event, bool p_is_em } if (event_dispatch_function) { - _THREAD_SAFE_UNLOCK_ - event_dispatch_function(p_event); - _THREAD_SAFE_LOCK_ + if (p_unlock) { + _THREAD_SAFE_UNLOCK_ + event_dispatch_function(p_event); + _THREAD_SAFE_LOCK_ + } else { + event_dispatch_function(p_event); + } } } @@ -865,7 +1062,7 @@ void Input::ensure_touch_mouse_raised() { ev_bm.clear_flag(MouseButtonMask::LEFT); button_event->set_button_mask(ev_bm); - _parse_input_event_impl(button_event, true); + _parse_input_event_impl(button_event, true, true); } } @@ -941,47 +1138,48 @@ void Input::parse_input_event(const Ref &p_event) { } #endif - if (use_accumulated_input) { - if (buffered_events.is_empty() || !buffered_events.back()->get()->accumulate(p_event)) { - buffered_events.push_back(p_event); - } - } else if (use_input_buffering) { - buffered_events.push_back(p_event); + if (data.buffering_mode == Input::BUFFERING_MODE_NONE) { + _parse_input_event_impl(p_event, false, true); } else { - _parse_input_event_impl(p_event, false); + // We can accumulate immediately on input if in frame mode, + // but if in agile / logical mode accumulation is deferred until flushing, + // so we can just push directly. + if ((data.buffering_mode == Input::BUFFERING_MODE_FRAME) && data.use_accumulated_input) { + _event_buffer.accumulate_or_push_event(p_event, OS::get_singleton()->get_ticks_usec()); + } else { + _event_buffer.push_event(p_event, OS::get_singleton()->get_ticks_usec()); + } } } -void Input::flush_buffered_events() { - _THREAD_SAFE_METHOD_ - - while (buffered_events.front()) { - // The final delivery of the input event involves releasing the lock. - // While the lock is released, another thread may lock it and add new events to the back. - // Therefore, we get each event and pop it while we still have the lock, - // to ensure the list is in a consistent state. - List>::Element *E = buffered_events.front(); - Ref e = E->get(); - buffered_events.pop_front(); - - _parse_input_event_impl(e, false); - } +void Input::_flush_buffered_events_ex(uint64_t p_up_to_timestamp) { + _event_buffer.flush_events(p_up_to_timestamp, *this, data.use_accumulated_input); } -bool Input::is_using_input_buffering() { - return use_input_buffering; -} +void Input::flush_buffered_events_iteration() { + // legacy did not flush here. + if (data.use_legacy_flushing) { + return; + } -void Input::set_use_input_buffering(bool p_enable) { - use_input_buffering = p_enable; + if (data.buffering_mode == BUFFERING_MODE_FRAME) { + _flush_buffered_events_ex(UINT64_MAX); + } } -void Input::set_use_accumulated_input(bool p_enable) { - use_accumulated_input = p_enable; +void Input::flush_buffered_events_tick(uint64_t p_tick_timestamp) { + if (data.buffering_mode == BUFFERING_MODE_AGILE) { + _flush_buffered_events_ex(p_tick_timestamp); + } } -bool Input::is_using_accumulated_input() { - return use_accumulated_input; +void Input::flush_buffered_events_frame() { + // If we are in legacy mode, if not NONE or FRAME, + // then it will be AGILE, in which case legacy had a flush + // here, so the new logic works as before. + if (data.buffering_mode == BUFFERING_MODE_AGILE) { + _flush_buffered_events_ex(UINT64_MAX); + } } void Input::release_pressed_events() { @@ -1531,6 +1729,9 @@ Input::Input() { parse_mapping(entries[i]); } } + + // Make sure buffering mode is appropriate for the state. + _update_buffering_mode(); } Input::~Input() { diff --git a/core/input/input.h b/core/input/input.h index c254650ef88..014d9fa4a90 100644 --- a/core/input/input.h +++ b/core/input/input.h @@ -38,9 +38,37 @@ #include "core/templates/rb_set.h" #include "core/variant/typed_array.h" +class Input; + +class InputEventBuffer { + struct Event { + uint64_t timestamp; + Ref event; + }; + + struct Data { + LocalVector incoming[2]; + uint32_t incoming_read = 0; + uint32_t incoming_write = 1; + Mutex incoming_mutex; + + List buffer; + Mutex buffer_mutex; + bool flushing = false; + } data; + + void _try_accumulate(uint64_t p_timestamp); + +public: + void accumulate_or_push_event(const Ref &p_event, uint64_t p_timestamp); + void push_event(const Ref &p_event, uint64_t p_timestamp); + void flush_events(uint64_t p_current_timestamp, Input &r_input_handler, bool p_accumulate); +}; + class Input : public Object { GDCLASS(Input, Object); _THREAD_SAFE_CLASS_ + friend class InputEventBuffer; static Input *singleton; @@ -53,6 +81,35 @@ class Input : public Object { MOUSE_MODE_CONFINED_HIDDEN, }; + // There are three buffering modes. + // * No buffering (immediately process input) + // * or buffering and flush the input every frame, + // * or agile - buffering and flush the input every physics tick and every frame. + // The mode is decided logically in _update_buffering_mode() + // depending on the settings for + // use_accumulated_input, use_buffering, use_agile, and has_input_thread. + enum BufferingMode { + BUFFERING_MODE_NONE, + BUFFERING_MODE_FRAME, + BUFFERING_MODE_AGILE, + }; + +protected: + struct Data { + BufferingMode buffering_mode = BUFFERING_MODE_FRAME; + bool use_accumulated_input = true; + bool use_buffering = false; + bool use_agile = false; + bool use_legacy_flushing = false; + bool has_input_thread = false; + } data; + + bool has_input_thread() const { + return data.has_input_thread; + } + void _update_buffering_mode(); + +public: #undef CursorShape enum CursorShape { CURSOR_ARROW, @@ -110,8 +167,6 @@ class Input : public Object { bool emulate_touch_from_mouse = false; bool emulate_mouse_from_touch = false; - bool use_input_buffering = false; - bool use_accumulated_input = true; int mouse_from_touch_index = -1; @@ -220,9 +275,11 @@ class Input : public Object { void _button_event(int p_device, JoyButton p_index, bool p_pressed); void _axis_event(int p_device, JoyAxis p_axis, float p_value); - void _parse_input_event_impl(const Ref &p_event, bool p_is_emulated); + void _parse_input_event_impl(const Ref &p_event, bool p_is_emulated, bool p_unlock); + void _flush_buffered_events_ex(uint64_t p_up_to_timestamp); + + InputEventBuffer _event_buffer; - List> buffered_events; #ifdef DEBUG_ENABLED HashSet> frame_parsed_events; uint64_t last_parsed_frame = UINT64_MAX; @@ -327,11 +384,33 @@ class Input : public Object { String get_joy_guid(int p_device) const; void set_fallback_mapping(String p_guid); + // Note calling this function will interfere with agile input, + // so should be avoided (for regular use) + // on platforms that do support agile input, + // by checking is_agile_flushing() is false before calling. void flush_buffered_events(); - bool is_using_input_buffering(); - void set_use_input_buffering(bool p_enable); + + // Prefer calling these versions from Main::Iteration + // or custom gameloop, as these support agile input. + void flush_buffered_events_iteration(); + void flush_buffered_events_tick(uint64_t p_tick_timestamp); + void flush_buffered_events_frame(); + void flush_buffered_events_post_frame(); + void set_use_accumulated_input(bool p_enable); - bool is_using_accumulated_input(); + bool is_using_accumulated_input() const { return data.use_accumulated_input; } + + void set_use_input_buffering(bool p_enable); + bool is_using_input_buffering() const; + + // Note that use_agile_flushing is a request, + // and doesn't guarantee that agile flushing will be + // active. (Depends on the platform) + void set_use_agile_flushing(bool p_enable); + bool is_agile_flushing() const; + + void set_use_legacy_flushing(bool p_enable); + void set_has_input_thread(bool p_has_thread); void release_pressed_events(); diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index add2faa962f..3a75448b000 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -1247,12 +1247,15 @@ Default [InputEventAction] to move up in the UI. [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. - + If [code]true[/code], key/touch/joystick events will be flushed just before every idle and physics frame. If [code]false[/code], such events will be flushed only once per process frame, between iterations of the engine. Enabling this can greatly improve the responsiveness to input, specially in devices that need to run multiple physics frames per visible (process) frame, because they can't run at the target frame rate. [b]Note:[/b] Currently implemented only on Android. + + If [code]true[/code], input events will be flushed at the end of the mainloop, instead of at the start. + Specifies the tablet driver to use. If left empty, the default driver will be used. [b]Note:[/b] The driver in use can be overridden at runtime via the [code]--tablet-driver[/code] [url=$DOCS_URL/tutorials/editor/command_line_tutorial.html]command line argument[/url]. diff --git a/main/main.cpp b/main/main.cpp index ec35b333213..993bd36a804 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -2374,7 +2374,8 @@ Error Main::setup2() { Input *id = Input::get_singleton(); if (id) { - agile_input_event_flushing = GLOBAL_DEF("input_devices/buffering/agile_event_flushing", false); + id->set_use_agile_flushing(GLOBAL_DEF("input_devices/buffering/agile_event_flushing", true)); + id->set_use_legacy_flushing(GLOBAL_DEF("input_devices/buffering/legacy_event_flushing", false)); if (bool(GLOBAL_DEF_BASIC("input_devices/pointing/emulate_touch_from_mouse", false)) && !(editor || project_manager)) { @@ -3282,7 +3283,6 @@ uint32_t Main::hide_print_fps_attempts = 3; uint32_t Main::frame = 0; bool Main::force_redraw_requested = false; int Main::iterating = 0; -bool Main::agile_input_event_flushing = false; bool Main::is_iterating() { return iterating > 0; @@ -3332,18 +3332,21 @@ bool Main::iteration() { advance.physics_steps = max_physics_steps; } + // Ensure with frame input buffering, that input is flushed prior to ticks, + // so reasonably up to date input is available in both ticks and frame. + Input::get_singleton()->flush_buffered_events_iteration(); + bool exit = false; // process all our active interfaces XRServer::get_singleton()->_process(); for (int iters = 0; iters < advance.physics_steps; ++iters) { - if (Input::get_singleton()->is_using_input_buffering() && agile_input_event_flushing) { - Input::get_singleton()->flush_buffered_events(); - } - Engine::get_singleton()->_in_physics = true; + // With agile input buffering, new input is potentially available per tick. + Input::get_singleton()->flush_buffered_events_tick(advance.get_logical_tick_time(iters)); + uint64_t physics_begin = OS::get_singleton()->get_ticks_usec(); PhysicsServer3D::get_singleton()->sync(); @@ -3384,9 +3387,8 @@ bool Main::iteration() { Engine::get_singleton()->_in_physics = false; } - if (Input::get_singleton()->is_using_input_buffering() && agile_input_event_flushing) { - Input::get_singleton()->flush_buffered_events(); - } + // Flush any remaining buffered input that has not been flushed already. + Input::get_singleton()->flush_buffered_events_frame(); uint64_t process_begin = OS::get_singleton()->get_ticks_usec(); @@ -3456,10 +3458,9 @@ bool Main::iteration() { iterating--; - // Needed for OSs using input buffering regardless accumulation (like Android) - if (Input::get_singleton()->is_using_input_buffering() && !agile_input_event_flushing) { - Input::get_singleton()->flush_buffered_events(); - } + // This is only included for legacy compatibility. + // Not clear whether this is a sensible time to flush input. + Input::get_singleton()->flush_buffered_events_post_frame(); if (movie_writer) { movie_writer->add_frame(); diff --git a/main/main.h b/main/main.h index cc0655cd027..79a18561c3e 100644 --- a/main/main.h +++ b/main/main.h @@ -46,7 +46,6 @@ class Main { static uint32_t frame; static bool force_redraw_requested; static int iterating; - static bool agile_input_event_flushing; public: static bool is_cmdline_tool(); diff --git a/main/main_timer_sync.cpp b/main/main_timer_sync.cpp index 569930d4277..1997d7e5dbd 100644 --- a/main/main_timer_sync.cpp +++ b/main/main_timer_sync.cpp @@ -521,5 +521,20 @@ void MainTimerSync::set_fixed_fps(int p_fixed_fps) { MainFrameTime MainTimerSync::advance(double p_physics_step, int p_physics_ticks_per_second) { double cpu_process_step = get_cpu_process_step(); - return advance_checked(p_physics_step, p_physics_ticks_per_second, cpu_process_step); + MainFrameTime mft = advance_checked(p_physics_step, p_physics_ticks_per_second, cpu_process_step); + + // Now backcalculate the logical timing of the first physics tick. + // This is used for processing input. + // It is approximate, but should be fine for input. + mft.usec_per_tick = 1000000 / p_physics_ticks_per_second; + uint64_t leftover_usec = mft.interpolation_fraction * mft.usec_per_tick; + + // Note we are using the ACTUAL CPU time for this estimate, + // NOT the smoothed accumulated time. + // This is because the input timestamps are measured in realtime, + // and smoothed time / realtime can get out of sync. + mft.first_physics_tick_logical_time_usecs = current_cpu_ticks_usec; + mft.first_physics_tick_logical_time_usecs -= (mft.physics_steps * mft.usec_per_tick) + leftover_usec; + + return mft; } diff --git a/main/main_timer_sync.h b/main/main_timer_sync.h index d8b5d4a02d6..e4e95bf9635 100644 --- a/main/main_timer_sync.h +++ b/main/main_timer_sync.h @@ -41,7 +41,13 @@ struct MainFrameTime { int physics_steps; // number of times to iterate the physics engine double interpolation_fraction; // fraction through the current physics tick + // Used for segmenting input processing to different physics ticks + // based on timestamps. + uint64_t first_physics_tick_logical_time_usecs = 0; + uint64_t usec_per_tick = 0; + void clamp_process_step(double min_process_step, double max_process_step); + uint64_t get_logical_tick_time(uint32_t p_tick) const { return first_physics_tick_logical_time_usecs + (p_tick * usec_per_tick); } }; class MainTimerSync { diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp index e07e0e11497..d1621874223 100644 --- a/platform/android/display_server_android.cpp +++ b/platform/android/display_server_android.cpp @@ -569,7 +569,9 @@ DisplayServerAndroid::DisplayServerAndroid(const String &p_rendering_driver, Dis #endif Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events); + Input::get_singleton()->set_use_input_buffering(true); // Needed because events will come directly from the UI thread + Input::get_singleton()->set_has_input_thread(true); r_error = OK; } diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index 5c6c8454ec3..2e20909358d 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -269,7 +269,9 @@ bool OS_Android::main_loop_iterate(bool *r_should_swap_buffers) { return false; } DisplayServerAndroid::get_singleton()->reset_swap_buffers_flag(); - DisplayServerAndroid::get_singleton()->process_events(); + if (!Input::get_singleton()->is_agile_flushing()) { + DisplayServerAndroid::get_singleton()->process_events(); + } uint64_t current_frames_drawn = Engine::get_singleton()->get_frames_drawn(); bool exit = Main::iteration();