367 lines
		
	
	
	
		
			7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			367 lines
		
	
	
	
		
			7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
| This file is part of Telegram Desktop,
 | |
| the official desktop application for the Telegram messaging service.
 | |
| 
 | |
| For license and copyright information please follow this link:
 | |
| https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | |
| */
 | |
| #include "base/concurrent_timer.h"
 | |
| 
 | |
| #include <QtCore/QThread>
 | |
| #include <QtCore/QCoreApplication>
 | |
| 
 | |
| using namespace base::details;
 | |
| 
 | |
| namespace base {
 | |
| namespace details {
 | |
| namespace {
 | |
| 
 | |
| constexpr auto kCallDelayedEvent = QEvent::Type(QEvent::User + 1);
 | |
| constexpr auto kCancelTimerEvent = QEvent::Type(QEvent::User + 2);
 | |
| static_assert(kCancelTimerEvent < QEvent::MaxUser);
 | |
| 
 | |
| ConcurrentTimerEnvironment *Environment/* = nullptr*/;
 | |
| QMutex EnvironmentMutex;
 | |
| 
 | |
| class CallDelayedEvent : public QEvent {
 | |
| public:
 | |
| 	CallDelayedEvent(
 | |
| 		crl::time timeout,
 | |
| 		Qt::TimerType type,
 | |
| 		FnMut<void()> method);
 | |
| 
 | |
| 	crl::time timeout() const;
 | |
| 	Qt::TimerType type() const;
 | |
| 	FnMut<void()> takeMethod();
 | |
| 
 | |
| private:
 | |
| 	crl::time _timeout = 0;
 | |
| 	Qt::TimerType _type = Qt::PreciseTimer;
 | |
| 	FnMut<void()> _method;
 | |
| 
 | |
| };
 | |
| 
 | |
| class CancelTimerEvent : public QEvent {
 | |
| public:
 | |
| 	CancelTimerEvent();
 | |
| 
 | |
| };
 | |
| 
 | |
| CallDelayedEvent::CallDelayedEvent(
 | |
| 	crl::time timeout,
 | |
| 	Qt::TimerType type,
 | |
| 	FnMut<void()> method)
 | |
| : QEvent(kCallDelayedEvent)
 | |
| , _timeout(timeout)
 | |
| , _type(type)
 | |
| , _method(std::move(method)) {
 | |
| 	Expects(_timeout >= 0 && _timeout < std::numeric_limits<int>::max());
 | |
| }
 | |
| 
 | |
| crl::time CallDelayedEvent::timeout() const {
 | |
| 	return _timeout;
 | |
| }
 | |
| 
 | |
| Qt::TimerType CallDelayedEvent::type() const {
 | |
| 	return _type;
 | |
| }
 | |
| 
 | |
| FnMut<void()> CallDelayedEvent::takeMethod() {
 | |
| 	return base::take(_method);
 | |
| }
 | |
| 
 | |
| CancelTimerEvent::CancelTimerEvent() : QEvent(kCancelTimerEvent) {
 | |
| }
 | |
| 
 | |
| } // namespace
 | |
| 
 | |
| class TimerObject : public QObject {
 | |
| public:
 | |
| 	TimerObject(
 | |
| 		not_null<QThread*> thread,
 | |
| 		not_null<QObject*> adjuster,
 | |
| 		Fn<void()> adjust);
 | |
| 
 | |
| protected:
 | |
| 	bool event(QEvent *e) override;
 | |
| 
 | |
| private:
 | |
| 	void callDelayed(not_null<CallDelayedEvent*> e);
 | |
| 	void callNow();
 | |
| 	void cancel();
 | |
| 	void adjust();
 | |
| 
 | |
| 	FnMut<void()> _next;
 | |
| 	Fn<void()> _adjust;
 | |
| 	int _timerId = 0;
 | |
| 
 | |
| };
 | |
| 
 | |
| TimerObject::TimerObject(
 | |
| 		not_null<QThread*> thread,
 | |
| 		not_null<QObject*> adjuster,
 | |
| 		Fn<void()> adjust)
 | |
| : _adjust(std::move(adjust)) {
 | |
| 	moveToThread(thread);
 | |
| 	connect(
 | |
| 		adjuster,
 | |
| 		&QObject::destroyed,
 | |
| 		this,
 | |
| 		&TimerObject::adjust,
 | |
| 		Qt::DirectConnection);
 | |
| }
 | |
| 
 | |
| bool TimerObject::event(QEvent *e) {
 | |
| 	const auto type = e->type();
 | |
| 	switch (type) {
 | |
| 	case kCallDelayedEvent:
 | |
| 		callDelayed(static_cast<CallDelayedEvent*>(e));
 | |
| 		return true;
 | |
| 	case kCancelTimerEvent:
 | |
| 		cancel();
 | |
| 		return true;
 | |
| 	case QEvent::Timer:
 | |
| 		callNow();
 | |
| 		return true;
 | |
| 	}
 | |
| 	return QObject::event(e);
 | |
| }
 | |
| 
 | |
| void TimerObject::callDelayed(not_null<CallDelayedEvent*> e) {
 | |
| 	cancel();
 | |
| 
 | |
| 	const auto timeout = e->timeout();
 | |
| 	const auto type = e->type();
 | |
| 	_next = e->takeMethod();
 | |
| 	if (timeout > 0) {
 | |
| 		_timerId = startTimer(timeout, type);
 | |
| 	} else {
 | |
| 		base::take(_next)();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void TimerObject::cancel() {
 | |
| 	if (const auto id = base::take(_timerId)) {
 | |
| 		killTimer(id);
 | |
| 	}
 | |
| 	_next = nullptr;
 | |
| }
 | |
| 
 | |
| void TimerObject::callNow() {
 | |
| 	auto next = base::take(_next);
 | |
| 	cancel();
 | |
| 	next();
 | |
| }
 | |
| 
 | |
| void TimerObject::adjust() {
 | |
| 	if (_adjust) {
 | |
| 		_adjust();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| TimerObjectWrap::TimerObjectWrap(Fn<void()> adjust) {
 | |
| 	QMutexLocker lock(&EnvironmentMutex);
 | |
| 
 | |
| 	if (Environment) {
 | |
| 		_value = Environment->createTimer(std::move(adjust));
 | |
| 	}
 | |
| }
 | |
| 
 | |
| TimerObjectWrap::~TimerObjectWrap() {
 | |
| 	if (_value) {
 | |
| 		QMutexLocker lock(&EnvironmentMutex);
 | |
| 
 | |
| 		if (Environment) {
 | |
| 			_value.release()->deleteLater();
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void TimerObjectWrap::call(
 | |
| 		crl::time timeout,
 | |
| 		Qt::TimerType type,
 | |
| 		FnMut<void()> method) {
 | |
| 	sendEvent(std::make_unique<CallDelayedEvent>(
 | |
| 		timeout,
 | |
| 		type,
 | |
| 		std::move(method)));
 | |
| }
 | |
| 
 | |
| void TimerObjectWrap::cancel() {
 | |
| 	sendEvent(std::make_unique<CancelTimerEvent>());
 | |
| }
 | |
| 
 | |
| void TimerObjectWrap::sendEvent(std::unique_ptr<QEvent> event) {
 | |
| 	if (!_value) {
 | |
| 		return;
 | |
| 	}
 | |
| 	QCoreApplication::postEvent(
 | |
| 		_value.get(),
 | |
| 		event.release(),
 | |
| 		Qt::HighEventPriority);
 | |
| }
 | |
| 
 | |
| } // namespace details
 | |
| 
 | |
| ConcurrentTimerEnvironment::ConcurrentTimerEnvironment() {
 | |
| 	_thread.start();
 | |
| 	_adjuster.moveToThread(&_thread);
 | |
| 
 | |
| 	acquire();
 | |
| }
 | |
| 
 | |
| ConcurrentTimerEnvironment::~ConcurrentTimerEnvironment() {
 | |
| 	_thread.quit();
 | |
| 	release();
 | |
| 	_thread.wait();
 | |
| 	QObject::disconnect(&_adjuster, &QObject::destroyed, nullptr, nullptr);
 | |
| }
 | |
| 
 | |
| std::unique_ptr<TimerObject> ConcurrentTimerEnvironment::createTimer(
 | |
| 		Fn<void()> adjust) {
 | |
| 	return std::make_unique<TimerObject>(
 | |
| 		&_thread,
 | |
| 		&_adjuster,
 | |
| 		std::move(adjust));
 | |
| }
 | |
| 
 | |
| void ConcurrentTimerEnvironment::Adjust() {
 | |
| 	QMutexLocker lock(&EnvironmentMutex);
 | |
| 	if (Environment) {
 | |
| 		Environment->adjustTimers();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void ConcurrentTimerEnvironment::adjustTimers() {
 | |
| 	QObject emitter;
 | |
| 	QObject::connect(
 | |
| 		&emitter,
 | |
| 		&QObject::destroyed,
 | |
| 		&_adjuster,
 | |
| 		&QObject::destroyed,
 | |
| 		Qt::QueuedConnection);
 | |
| }
 | |
| 
 | |
| void ConcurrentTimerEnvironment::acquire() {
 | |
| 	Expects(Environment == nullptr);
 | |
| 
 | |
| 	QMutexLocker lock(&EnvironmentMutex);
 | |
| 	Environment = this;
 | |
| }
 | |
| 
 | |
| void ConcurrentTimerEnvironment::release() {
 | |
| 	Expects(Environment == this);
 | |
| 
 | |
| 	QMutexLocker lock(&EnvironmentMutex);
 | |
| 	Environment = nullptr;
 | |
| }
 | |
| 
 | |
| ConcurrentTimer::ConcurrentTimer(
 | |
| 	Fn<void(FnMut<void()>)> runner,
 | |
| 	Fn<void()> callback)
 | |
| : _runner(std::move(runner))
 | |
| , _object(createAdjuster())
 | |
| , _callback(std::move(callback))
 | |
| , _type(Qt::PreciseTimer)
 | |
| , _adjusted(false) {
 | |
| 	setRepeat(Repeat::Interval);
 | |
| }
 | |
| 
 | |
| Fn<void()> ConcurrentTimer::createAdjuster() {
 | |
| 	auto guards = base::make_binary_guard();
 | |
| 	_guard = std::make_shared<bool>(true);
 | |
| 	return [=, runner = _runner, guard = std::weak_ptr<bool>(_guard)] {
 | |
| 		runner([=] {
 | |
| 			if (!guard.lock()) {
 | |
| 				return;
 | |
| 			}
 | |
| 			adjust();
 | |
| 		});
 | |
| 	};
 | |
| }
 | |
| 
 | |
| void ConcurrentTimer::start(
 | |
| 		crl::time timeout,
 | |
| 		Qt::TimerType type,
 | |
| 		Repeat repeat) {
 | |
| 	_type = type;
 | |
| 	setRepeat(repeat);
 | |
| 	_adjusted = false;
 | |
| 	setTimeout(timeout);
 | |
| 
 | |
| 	cancelAndSchedule(_timeout);
 | |
| 	_next = crl::now() + _timeout;
 | |
| }
 | |
| 
 | |
| void ConcurrentTimer::cancelAndSchedule(int timeout) {
 | |
| 	auto guards = base::make_binary_guard();
 | |
| 	_running = std::move(guards.first);
 | |
| 	auto method = [
 | |
| 		=,
 | |
| 		runner = _runner,
 | |
| 		guard = std::move(guards.second)
 | |
| 	]() mutable {
 | |
| 		if (!guard) {
 | |
| 			return;
 | |
| 		}
 | |
| 		runner([=, guard = std::move(guard)] {
 | |
| 			if (!guard) {
 | |
| 				return;
 | |
| 			}
 | |
| 			timerEvent();
 | |
| 		});
 | |
| 	};
 | |
| 	_object.call(timeout, _type, std::move(method));
 | |
| }
 | |
| 
 | |
| void ConcurrentTimer::timerEvent() {
 | |
| 	if (repeat() == Repeat::Interval) {
 | |
| 		if (_adjusted) {
 | |
| 			start(_timeout, _type, repeat());
 | |
| 		} else {
 | |
| 			_next = crl::now() + _timeout;
 | |
| 		}
 | |
| 	} else {
 | |
| 		cancel();
 | |
| 	}
 | |
| 
 | |
| 	if (_callback) {
 | |
| 		_callback();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void ConcurrentTimer::cancel() {
 | |
| 	_running = {};
 | |
| 	if (isActive()) {
 | |
| 		_running = base::binary_guard();
 | |
| 		_object.cancel();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| crl::time ConcurrentTimer::remainingTime() const {
 | |
| 	if (!isActive()) {
 | |
| 		return -1;
 | |
| 	}
 | |
| 	const auto now = crl::now();
 | |
| 	return (_next > now) ? (_next - now) : crl::time(0);
 | |
| }
 | |
| 
 | |
| void ConcurrentTimer::adjust() {
 | |
| 	auto remaining = remainingTime();
 | |
| 	if (remaining >= 0) {
 | |
| 		cancelAndSchedule(remaining);
 | |
| 		_adjusted = true;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void ConcurrentTimer::setTimeout(crl::time timeout) {
 | |
| 	Expects(timeout >= 0 && timeout <= std::numeric_limits<int>::max());
 | |
| 
 | |
| 	_timeout = static_cast<unsigned int>(timeout);
 | |
| }
 | |
| 
 | |
| int ConcurrentTimer::timeout() const {
 | |
| 	return _timeout;
 | |
| }
 | |
| 
 | |
| } // namespace base
 | 
