microsoft / ClearScript

A library for adding scripting to .NET applications. Supports V8 (Windows, Linux, macOS) and JScript/VBScript (Windows).
https://microsoft.github.io/ClearScript/
MIT License
1.77k stars 148 forks source link

How to implement setInterval with clearInterval #475

Closed MixaMega closed 1 year ago

MixaMega commented 1 year ago

There are a few issues that have been about setInterval, but none of them show how to implement clearInterval

Also another question, is it possible to run typescript

ClearScriptLib commented 1 year ago

Hi @MixaMega,

how to implement clearInterval

Here's an initial attempt at a decent timeout and interval implementation. We'll skip input validation for now.

First, we'll need a .NET timer that can call a script function:

public sealed class TimerImpl {
    private Timer _timer;
    private Func<double> _callback = () => Timeout.Infinite;
    public void Initialize(dynamic callback) => _callback = () => (double)callback();
    public void Schedule(double delay) {
        if (delay < 0) {
            if (_timer != null) {
                _timer.Dispose();
                _timer = null;
            }
        } else {
            if (_timer == null) _timer = new Timer(_ => Schedule(_callback()));
            _timer.Change(TimeSpan.FromMilliseconds(delay), Timeout.InfiniteTimeSpan);
        }
    }
}

Now let's set up the script code:

dynamic setup = engine.Evaluate(@"(impl => {
    let queue = [], nextId = 0;
    const maxId = 1000000000000, getNextId = () => nextId = (nextId % maxId) + 1;
    const add = entry => {
        const index = queue.findIndex(element => element.due > entry.due);
        index >= 0 ? queue.splice(index, 0, entry) : queue.push(entry);
    }
    function set(periodic, func, delay) {
        const id = getNextId(), now = Date.now(), args = [...arguments].slice(3);
        add({ id, periodic, func: () => func(...args), delay, due: now + delay });
        impl.Schedule(queue[0].due - now);
        return id;
    };
    function clear(id) {
        queue = queue.filter(entry => entry.id != id);
        impl.Schedule(queue.length > 0 ? queue[0].due - Date.now() : -1);
    };
    globalThis.setTimeout = set.bind(undefined, false);
    globalThis.setInterval = set.bind(undefined, true);
    globalThis.clearTimeout = globalThis.clearInterval = clear.bind();
    impl.Initialize(() => {
        const now = Date.now();
        while ((queue.length > 0) && (now >= queue[0].due)) {
            const entry = queue.shift();
            if (entry.periodic) add({ ...entry, due: now + entry.delay });
            entry.func();
        }
        return queue.length > 0 ? queue[0].due - now : -1;
    });
})");
setup(new TimerImpl());

And now, a quick test:

engine.AddHostType(typeof(Console));
engine.AddHostObject("done", new ManualResetEventSlim());
engine.Execute(@"
    const id = setInterval(() => Console.WriteLine('Hello, world!'), 1000);
    setTimeout(() => {
        clearInterval(id);
        Console.Write('Exiting in 5 seconds...');
        setTimeout(() => done.Set(), 5000);
    }, 5500);
");
engine.Script.done.Wait();
Console.WriteLine(" Bye!");

is it possible to run typescript

Sure, but you'll have to use tsc to convert your TypeScript code to JavaScript first.

Good luck!