Skip to content

Commit 4d27e9a

Browse files
committed
LibWeb: Make setInterval() reuse the timer to reduce drift
Instead of creating a new single-shot timer every time a setInterval timer reschedules itself, we now use a repeating Core::Timer internally. This dramatically reduces timer drift, since the next timeout is now based on when the timer fired, rather than when the timer callback completed (which could take arbitrarily long since we run JS). It's not perfect, but it's a huge improvement and allows us to play DiabloWeb at full framerate (20 fps).
1 parent fb9286e commit 4d27e9a

File tree

4 files changed

+39
-19
lines changed

4 files changed

+39
-19
lines changed

Libraries/LibWeb/HTML/Timer.cpp

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,27 +13,26 @@ namespace Web::HTML {
1313

1414
GC_DEFINE_ALLOCATOR(Timer);
1515

16-
GC::Ref<Timer> Timer::create(JS::Object& window_or_worker_global_scope, i32 milliseconds, Function<void()> callback, i32 id)
16+
GC::Ref<Timer> Timer::create(JS::Object& window_or_worker_global_scope, i32 milliseconds, Function<void()> callback, i32 id, Repeating repeating)
1717
{
18-
auto heap_function_callback = GC::create_function(window_or_worker_global_scope.heap(), move(callback));
19-
return window_or_worker_global_scope.heap().allocate<Timer>(window_or_worker_global_scope, milliseconds, heap_function_callback, id);
18+
return window_or_worker_global_scope.heap().allocate<Timer>(window_or_worker_global_scope, milliseconds, move(callback), id, repeating);
2019
}
2120

22-
Timer::Timer(JS::Object& window_or_worker_global_scope, i32 milliseconds, GC::Ref<GC::Function<void()>> callback, i32 id)
21+
Timer::Timer(JS::Object& window_or_worker_global_scope, i32 milliseconds, Function<void()> callback, i32 id, Repeating repeating)
2322
: m_window_or_worker_global_scope(window_or_worker_global_scope)
24-
, m_callback(move(callback))
2523
, m_id(id)
2624
{
27-
m_timer = Core::Timer::create_single_shot(milliseconds, [this] {
28-
m_callback->function()();
29-
});
25+
if (repeating == Repeating::Yes)
26+
m_timer = Core::Timer::create_repeating(milliseconds, move(callback));
27+
else
28+
m_timer = Core::Timer::create_single_shot(milliseconds, move(callback));
3029
}
3130

3231
void Timer::visit_edges(Cell::Visitor& visitor)
3332
{
3433
Base::visit_edges(visitor);
3534
visitor.visit(m_window_or_worker_global_scope);
36-
visitor.visit(m_callback);
35+
visitor.visit_possible_values(m_timer->on_timeout.raw_capture_range());
3736
}
3837

3938
Timer::~Timer()
@@ -51,4 +50,9 @@ void Timer::stop()
5150
m_timer->stop();
5251
}
5352

53+
void Timer::set_callback(Function<void()> callback)
54+
{
55+
m_timer->on_timeout = move(callback);
56+
}
57+
5458
}

Libraries/LibWeb/HTML/Timer.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,26 @@ class Timer final : public JS::Cell {
2222
GC_DECLARE_ALLOCATOR(Timer);
2323

2424
public:
25-
static GC::Ref<Timer> create(JS::Object&, i32 milliseconds, Function<void()> callback, i32 id);
25+
enum class Repeating {
26+
No,
27+
Yes,
28+
};
29+
30+
static GC::Ref<Timer> create(JS::Object&, i32 milliseconds, Function<void()> callback, i32 id, Repeating);
2631
virtual ~Timer() override;
2732

2833
void start();
2934
void stop();
3035

36+
void set_callback(Function<void()>);
37+
3138
private:
32-
Timer(JS::Object& window, i32 milliseconds, GC::Ref<GC::Function<void()>> callback, i32 id);
39+
Timer(JS::Object& window, i32 milliseconds, Function<void()> callback, i32 id, Repeating);
3340

3441
virtual void visit_edges(Cell::Visitor&) override;
3542

3643
RefPtr<Core::Timer> m_timer;
3744
GC::Ref<JS::Object> m_window_or_worker_global_scope;
38-
GC::Ref<GC::Function<void()>> m_callback;
3945
i32 m_id { 0 };
4046
};
4147

Libraries/LibWeb/HTML/WindowOrWorkerGlobalScope.cpp

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include <LibWeb/HTML/Timer.h>
3838
#include <LibWeb/HTML/Window.h>
3939
#include <LibWeb/HTML/WindowOrWorkerGlobalScope.h>
40+
#include <LibWeb/HTML/WorkerGlobalScope.h>
4041
#include <LibWeb/HighResolutionTime/Performance.h>
4142
#include <LibWeb/HighResolutionTime/SupportedPerformanceTypes.h>
4243
#include <LibWeb/IndexedDB/IDBFactory.h>
@@ -672,7 +673,7 @@ i32 WindowOrWorkerGlobalScopeMixin::run_timer_initialization_steps(TimerHandler
672673
// 13. Set uniqueHandle to the result of running steps after a timeout given global, "setTimeout/setInterval",
673674
// timeout, and completionStep.
674675
// FIXME: run_steps_after_a_timeout() needs to be updated to return a unique internal value that can be used here.
675-
run_steps_after_a_timeout_impl(timeout, move(completion_step), id);
676+
run_steps_after_a_timeout_impl(timeout, move(completion_step), id, repeat);
676677

677678
// FIXME: 14. Set global's map of setTimeout and setInterval IDs[id] to uniqueHandle.
678679

@@ -1074,20 +1075,29 @@ WindowOrWorkerGlobalScopeMixin::AffectedAnyWebSockets WindowOrWorkerGlobalScopeM
10741075
// https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#run-steps-after-a-timeout
10751076
void WindowOrWorkerGlobalScopeMixin::run_steps_after_a_timeout(i32 timeout, Function<void()> completion_step)
10761077
{
1077-
return run_steps_after_a_timeout_impl(timeout, move(completion_step));
1078+
return run_steps_after_a_timeout_impl(timeout, move(completion_step), {}, Repeat::No);
10781079
}
10791080

1080-
void WindowOrWorkerGlobalScopeMixin::run_steps_after_a_timeout_impl(i32 timeout, Function<void()> completion_step, Optional<i32> timer_key)
1081+
void WindowOrWorkerGlobalScopeMixin::run_steps_after_a_timeout_impl(i32 timeout, Function<void()> completion_step, Optional<i32> timer_key, Repeat repeat)
10811082
{
10821083
// 1. Assert: if timerKey is given, then the caller of this algorithm is the timer initialization steps. (Other specifications must not pass timerKey.)
10831084
// Note: This is enforced by the caller.
10841085

1085-
// 2. If timerKey is not given, then set it to a new unique non-numeric value.
1086-
if (!timer_key.has_value())
1086+
// NB: We deviate from the spec here slightly by reusing existing timers if a timer_key is provided.
1087+
GC::Ptr<Timer> existing_timer;
1088+
if (timer_key.has_value()) {
1089+
auto result = m_timers.get(timer_key.value());
1090+
if (result.has_value()) {
1091+
existing_timer = result.value().ptr();
1092+
existing_timer->set_callback(move(completion_step));
1093+
}
1094+
} else {
1095+
// 2. If timerKey is not given, then set it to a new unique non-numeric value.
10871096
timer_key = m_timer_id_allocator.allocate();
1097+
}
10881098

10891099
// FIXME: 3. Let startTime be the current high resolution time given global.
1090-
auto timer = Timer::create(this_impl(), timeout, move(completion_step), timer_key.value());
1100+
auto timer = existing_timer ? GC::Ref { *existing_timer } : Timer::create(this_impl(), timeout, move(completion_step), timer_key.value(), repeat == Repeat::Yes ? Timer::Repeating::Yes : Timer::Repeating::No);
10911101

10921102
// FIXME: 4. Set global's map of active timers[timerKey] to startTime plus milliseconds.
10931103
m_timers.set(timer_key.value(), timer);

Libraries/LibWeb/HTML/WindowOrWorkerGlobalScope.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ class WEB_API WindowOrWorkerGlobalScopeMixin {
117117
No,
118118
};
119119
i32 run_timer_initialization_steps(TimerHandler handler, i32 timeout, GC::RootVector<JS::Value> arguments, Repeat repeat, Optional<i32> previous_id = {});
120-
void run_steps_after_a_timeout_impl(i32 timeout, Function<void()> completion_step, Optional<i32> timer_key = {});
120+
void run_steps_after_a_timeout_impl(i32 timeout, Function<void()> completion_step, Optional<i32> timer_key, Repeat);
121121

122122
GC::Ref<WebIDL::Promise> create_image_bitmap_impl(ImageBitmapSource& image, Optional<WebIDL::Long> sx, Optional<WebIDL::Long> sy, Optional<WebIDL::Long> sw, Optional<WebIDL::Long> sh, Optional<ImageBitmapOptions>& options) const;
123123

0 commit comments

Comments
 (0)