Skip to content

Commit

Permalink
Merge pull request godotengine#37 from lawnjelly/plus_input_buffer
Browse files Browse the repository at this point in the history
Timestamped input buffering - prevent stalling and improve timing
  • Loading branch information
lawnjelly authored May 15, 2023
2 parents 455ff1f + a98ca50 commit c90152b
Show file tree
Hide file tree
Showing 14 changed files with 413 additions and 78 deletions.
87 changes: 86 additions & 1 deletion core/os/input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,88 @@ 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) {
force_flush_buffered_events();
}
}
}

void Input::flush_buffered_events() {
// This function was called in hacky fashion
// all over the shop on different platforms,
// and will interfere with agile input,
// so is now a NOOP, unless the user chooses
// legacy flushing.
// Using legacy flushing will muck up
// agile input, so is not recommended,
// and is only included temporarily to
// allow fixing bugs in beta.
if (data.use_legacy_flushing) {
force_flush_buffered_events();
}
}

void Input::set_use_accumulated_input(bool p_enable) {
data.use_accumulated_input = p_enable;
_update_buffering_mode();
}

bool Input::is_using_accumulated_input() const {
return data.use_accumulated_input;
}

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_using_agile_flushing() const {
return data.use_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);
OS::get_singleton()->set_mouse_mode((OS::MouseMode)p_mode);
Expand Down Expand Up @@ -104,7 +186,10 @@ 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);

// For backward compatibility this calls the force method.
// Use this at your peril as it will mess up agile input any frame it is called.
ClassDB::bind_method(D_METHOD("flush_buffered_events"), &Input::force_flush_buffered_events);

ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_mode"), "set_mouse_mode", "get_mouse_mode");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_accumulated_input"), "set_use_accumulated_input", "is_using_accumulated_input");
Expand Down
56 changes: 51 additions & 5 deletions core/os/input.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,33 @@ 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,
Expand Down Expand Up @@ -142,11 +169,30 @@ class Input : public Object {
virtual int get_joy_axis_index_from_string(String p_axis) = 0;

virtual void parse_input_event(const Ref<InputEvent> &p_event) = 0;
virtual void flush_buffered_events() = 0;
virtual bool is_using_input_buffering() = 0;
virtual void set_use_input_buffering(bool p_enable) = 0;
virtual bool is_using_accumulated_input() = 0;
virtual void set_use_accumulated_input(bool p_enable) = 0;

// DO NOT call force_flush_buffered_events() in normal course of events,
// as it will break agile input on any frame it is called.
// Instead rely on flush_buffered_events_tick() and
// flush_buffered_events_frame() which are called from
// Main::iteration().
virtual void force_flush_buffered_events() = 0;
virtual void flush_buffered_events_iteration() = 0;
virtual void flush_buffered_events_tick(uint64_t p_tick_timestamp) = 0;
virtual void flush_buffered_events_frame() = 0;
void flush_buffered_events();
void flush_buffered_events_post_frame();

void set_use_accumulated_input(bool p_enable);
bool is_using_accumulated_input() const;

void set_use_input_buffering(bool p_enable);
bool is_using_input_buffering() const;

void set_use_agile_flushing(bool p_enable);
bool is_using_agile_flushing() const;

void set_use_legacy_flushing(bool p_enable);
void set_has_input_thread(bool p_has_thread);

Input();
};
Expand Down
2 changes: 1 addition & 1 deletion core/os/os.h
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,7 @@ class OS {
virtual int get_power_seconds_left();
virtual int get_power_percent_left();

virtual void force_process_input(){};
virtual void force_process_input() {}
bool has_feature(const String &p_feature);

void set_has_server_feature_callback(HasServerFeatureCallback p_callback);
Expand Down
6 changes: 5 additions & 1 deletion doc/classes/ProjectSettings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -667,12 +667,16 @@
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.
</member>
<member name="input_devices/buffering/agile_event_flushing" type="bool" setter="" getter="" default="false">
<member name="input_devices/buffering/agile_event_flushing" type="bool" setter="" getter="" default="true">
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 idle 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 (idle) frame, because they can't run at the target frame rate.
[b]Note:[/b] Currently implemented only in Android.
</member>
<member name="input_devices/buffering/legacy_event_flushing" type="bool" setter="" getter="" default="false">
If [code]true[/code], input events will be flushed on demand according to platform, instead of the new platform independent method.
[b]Note:[/b] Enabling this will break agile event flushing, which may reduce input timing resolution on some platforms.
</member>
<member name="input_devices/pointing/emulate_mouse_from_touch" type="bool" setter="" getter="" default="true">
If [code]true[/code], sends mouse input events when tapping or swiping on the touchscreen.
</member>
Expand Down
Loading

0 comments on commit c90152b

Please sign in to comment.