windowjs / windowjs

Window.js is an open-source Javascript runtime for desktop graphics programming.

Home Page:https://windowjs.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Add setInterval and clearInterval

ArjixWasTaken opened this issue · comments

I noticed that setInterval is not a thing in windowjs, so I decided to write my own implementation:

function* getIntervalIdgen() {
    var i = 0;
    yield i++;
}

const intervalIdGenerator = getIntervalIdgen();

function getId() {
    return intervalIdGenerator.next.value;
}

{
    var g;
    if (typeof window !== "undefined") {
        g = window;
    } else if (typeof global !== "undefined") {
        g = global;
    } else if (typeof self !== "undefined") {
        g = self;
    } else {
        g = this;
    }

    g.setInterval = function (callback, timer) {
        let id = getId();

        g.intervalState.intervals.push({
            callback,
            timer,
            timeDelta: Date.now(),
            id,
        });

        return id;
    };
    g.clearInterval = function (id) {
        g.intervalState.intervals = g.intervalState.intervals.filter(function (
            item
        ) {
            return item.id != id;
        });
    };
    g.intervalState = {};
    g.intervalState.intervals = [];

    function fireIntervals() {
        requestAnimationFrame(fireIntervals);

        for (const interval of g.intervalState.intervals) {
            if (Date.now() - interval.timeDelta >= interval.timer) {
                interval.callback();
                interval.timeDelta = Date.now();
            }
        }
    }
    requestAnimationFrame(fireIntervals);
}

Would be nice if I could contribute that to the repo, but I don't want to mess with c.

Hi Arjix, thanks for the contribution!

Agreed that Window.js should also have setInterval and clearInterval.

Window.js doesn't preload a Javascript file, other than the native builtins, so this approach doesn't work right now. I don't think we want to add a preloaded Javascript file because it will take a bit longer to load compared to native code, so it would make every startup a bit slower than it could be.

Some comments about the code you wrote:

  1. The global object in Window.js is globalThis. See these docs:

https://windowjs.org/doc/global
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis

So you should just do

globalThis.setInterval = function(callback, interval) { ... }

  1. Date.now() is not a stable time source; for example, it may go back in time during daylight savings days (two times a year), or if the user adjusts the clock, or for any other reason.

Clocks for intervals like in setInterval should use a monotonic time source, that only advances forward and always at a constant rate. A good source for that in Javascript is performance.now():

https://developer.mozilla.org/en-US/docs/Web/API/Performance/now

So you could just take a timestamp when a callback is registered, and then compare its timestamp to performance.now() when firing callbacks.

  1. setInterval() may be called from inside a callback! So the for loop in fireIntervals() may end up mutating g.intervalState.intervals while it's still looping (same for clearInterval). This may cause strange bugs like skipping ahead when a callback gets removed.

--

I guess this change will really have to go in the C++ code. Here's a pointer to the table of setTimeout callbacks if you want to give this a try:

std::unordered_map<uint32_t, v8::Global<v8::Function>> timeouts_;

See also how it's implemented:

api->task_queue()->Post(timeout, [=] {

The task_queue is used internally to run Javascript callbacks once on every frame. I think that's the ideal rate to call setInterval: each callback gets called at most once per loop, and as fast as frames are getting pushed to the screen.

Let me know if you'd like to make that change, or if someone else should implement this.

I am not an experienced c++ dev, so I bet if I go ahead and do it myself I will introduce a bug or smth.
And that would place more work on you in order to troubleshoot it.

So yeah, someone else should implement this.

Sure, np. You can always have your own module with globals and import it manually in your programs:

import './path/to/stuff.js';

Just put anything you'd like in the globalThis object.

Some context to implement this in C++.

  • all Javascript code, and all of the API calls, execute in the main thread
  • all of the Javascript context is owned by the Js class, in js.cc
  • js.cc is mainly responsible for wrapping the v8 Isolate and Context classes, and loading Javascript modules
  • all of the Javascript APIs are registered via js_api.cc (which delegates bigger APIs to js_api_canvas.cc, js_api_file.cc, etc.)
  • js.h has a TaskQueue [1]. This is just a list of C++ functions to be called later. This TaskQueue is valid as long as the Js instance is valid too.
  • finally, the Js instance and the TaskQueue instance are owned by main.cc. See the main loop there to see when tasks execute.

To implement setInterval:

  • have a map of pending callbacks, similar to timeouts in [2]
  • when the API gets called, generate the next ID and put an entry in that map
  • to schedule the execution, post a task to the task queue like here: [3]
    (note that TaskQueue takes the timeout in seconds, not milliseconds)
  • TaskQueue will execute that task only once. After executing the Javascript code, check if the setInterval ID is still valid; if it is, schedule the task in the TaskQueue again to run it again in the future
  • if clearInterval is called, then just remove the callback from the map. The task in the TaskQueue should check that the ID is still valid before executing the callback.

Searching with grep for the SetTimeout implementation will find most of the places that need some code (probably js_strings.h is enough, and registering the API in js_api.cc)

[1]

TaskQueue* task_queue_;

[2]
std::unordered_map<uint32_t, v8::Global<v8::Function>> timeouts_;

[3]
api->task_queue()->Post(timeout, [=] {