Skip to content

File timersFeature.h

File List > features > timersFeature.h

Go to the documentation of this file

#pragma once

#include <jac/machine/functionFactory.h>
#include <jac/machine/machine.h>

#include <atomic>
#include <chrono>
#include <condition_variable>
#include <mutex>
#include <queue>
#include <thread>
#include <unordered_map>


namespace jac {


template<>
struct ConvTraits<std::chrono::milliseconds> {
    static std::chrono::milliseconds from(ContextRef, ValueWeak value) {
        return std::chrono::milliseconds(value.to<int>());
    }

    static Value to(ContextRef ctx, std::chrono::milliseconds value) {
        return Value::from(ctx, static_cast<int>(value.count()));
    }
};

template<class Next>
class TimersFeature : public Next {
private:
    class Timer {
    private:
        std::chrono::time_point<std::chrono::steady_clock> _startTime;
        std::chrono::milliseconds _duration;
        std::function<void()> _callback;
        int _id;
        bool _isRepeating = false;
        bool cancelled = false;

    public:
        Timer(std::function<void()> callback, std::chrono::milliseconds duration, int id, bool isRepeating = false):
            _duration(duration),
            _callback(callback),
            _id(id),
            _isRepeating(isRepeating)
        {
            _startTime = std::chrono::steady_clock::now();
        }

        bool operator<(const Timer& other) const {
            return getEndTime() > other.getEndTime();
        }

        void update() {
            if (_isRepeating) {
                _startTime = std::chrono::steady_clock::now();
            }
        }

        std::function<void()> getCallback() {
            return _callback;
        }

        std::chrono::time_point<std::chrono::steady_clock> getEndTime() const {
            return _startTime + _duration;
        }

        bool isRepeating() const {
            return _isRepeating;
        }

        bool isCancelled() const {
            return cancelled;
        }

        int getId() const {
            return _id;
        }

        void cancel() {
            cancelled = true;
        }
    };

    class CompareTimer {
    public:
        bool operator()(const std::shared_ptr<Timer>& a, const std::shared_ptr<Timer>& b) const {
            return *a < *b;
        }
    };

    std::priority_queue<std::shared_ptr<Timer>, std::vector<std::shared_ptr<Timer>>, CompareTimer> _timers;
    std::unordered_map<int, std::shared_ptr<Timer>> _timersById;
    std::mutex _timersMutex;
    std::condition_variable _timersCondition;
    std::thread _timerThread;
    std::atomic<bool> _stop = false;

    int nextId = 1;

    int createTimer(std::function<void()> func, std::chrono::milliseconds millis, bool isRepeating) {
        std::lock_guard<std::mutex> lock(_timersMutex);
        auto timer = std::make_shared<Timer>(func, millis, nextId++, isRepeating);
        _timers.emplace(timer);
        _timersById[nextId - 1] = std::move(timer);

        _timersCondition.notify_one();
        return nextId - 1;
    }

    void clearTimer(int id) {
        std::lock_guard<std::mutex> lock(_timersMutex);
        auto it = _timersById.find(id);
        if (it != _timersById.end()) {
            it->second->cancel();
        }
    }
public:
    int setInterval(std::function<void()> func, std::chrono::milliseconds millis) {
        return createTimer(func, millis, true);
    }

    int setTimeout(std::function<void()> func, std::chrono::milliseconds millis) {
        return createTimer(func, millis, false);
    }

    void clearInterval(int id) {
        clearTimer(id);
    }

    void clearTimeout(int id) {
        clearTimer(id);
    }

    void initialize() {
        Next::initialize();

        _stop = false;
        _timerThread = std::thread([this]() {
            while (!_stop) {
                std::unique_lock<std::mutex> lock(_timersMutex);
                if (_timers.empty()) {
                    _timersCondition.wait(lock);
                    if (_timers.empty()) {
                        continue;
                    }
                }

                auto timer = _timers.top();

                if (timer->getEndTime() > std::chrono::steady_clock::now()) {
                    _timersCondition.wait_until(lock, timer->getEndTime());
                    continue;
                }

                _timers.pop();

                if (timer->isCancelled()) {
                    int id = timer->getId();
                    _timersById.erase(id);
                    continue;
                }

                lock.unlock();

                this->scheduleEvent([timer, this]() mutable {
                    if (timer->isCancelled()) {
                        std::lock_guard<std::mutex> lock_(_timersMutex);
                        int id = timer->getId();
                        _timersById.erase(id);
                        return;
                    }
                    timer->getCallback()();

                    std::lock_guard<std::mutex> lock_(_timersMutex);
                    if (timer->isRepeating()) {
                        timer->update();
                        _timers.push(timer);
                        _timersCondition.notify_one();
                    }
                    else {
                        int id = timer->getId();
                        _timersById.erase(id);
                    }
                });
            }
        });

        FunctionFactory ff(this->context());
        Object global = this->context().getGlobalObject();

        global.defineProperty("setInterval", ff.newFunction([this](Function func, std::chrono::milliseconds millis) {
            return setInterval([func]() mutable {
                func.call<void>();
            }, millis);
        }), PropFlags::Enumerable);

        global.defineProperty("setTimeout", ff.newFunction([this](Function func, std::chrono::milliseconds millis) {
            return setTimeout([func]() mutable {
                func.call<void>();
            }, millis);
        }), PropFlags::Enumerable);

        global.defineProperty("clearInterval", ff.newFunction([this](int id) {
            clearInterval(id);
        }), PropFlags::Enumerable);

        global.defineProperty("clearTimeout", ff.newFunction([this](int id) {
            clearTimeout(id);
        }), PropFlags::Enumerable);

        global.defineProperty("sleep", ff.newFunction([this](int millis) {
            auto [promise, resolve, _] = Promise::create(this->context());
            setTimeout([resolve_ = resolve]() mutable {
                static_cast<Function&>(resolve_).call<void>();
            }, std::chrono::milliseconds(millis));
            return promise;
        }));
    }

    ~TimersFeature() {
        {
            std::scoped_lock lock(_timersMutex);
            _timers = {};
        }
        _stop = true;
        _timersCondition.notify_one();

        if (_timerThread.joinable()) {
            _timerThread.join();
        }
    }
};


} // namespace jac