#include <ftl/timer.hpp> #include <ftl/threads.hpp> #include <loguru.hpp> #include <vector> #include <list> #include <chrono> #include <xmmintrin.h> using std::vector; using std::list; using std::function; using std::chrono::time_point_cast; using std::chrono::milliseconds; using std::chrono::high_resolution_clock; using std::this_thread::sleep_for; using namespace ftl::timer; static int64_t last_frame = 0; static int64_t mspf = 50; static bool hprec_ = false; static int64_t clock_adjust = 0; static bool active = false; static std::atomic<int> active_jobs = 0; static MUTEX mtx; static int last_id = 0; static bool clock_slave = true; static std::future<void> timer_future; struct TimerJob { int id=0; ftl::SingletonHandler<int64_t> job; std::atomic_bool active=false; // TODO: (Nick) Implement richer forms of timer //bool paused; //int multiplier; //int countdown; std::string name; }; static list<TimerJob> jobs[kTimerMAXLEVEL]; int64_t ftl::timer::get_time() { return time_point_cast<milliseconds>(high_resolution_clock::now()).time_since_epoch().count()+clock_adjust; } int64_t ftl::timer::get_time_micro() { return time_point_cast<std::chrono::microseconds>(high_resolution_clock::now()).time_since_epoch().count()+(clock_adjust*1000); } double ftl::timer::get_time_seconds() { return time_point_cast<std::chrono::microseconds>(high_resolution_clock::now()).time_since_epoch().count() / 1000000.0 + (static_cast<double>(clock_adjust) / 1000.0); } static void waitTimePoint() { int64_t now = get_time(); int64_t target = now / mspf; int64_t msdelay = mspf - (now % mspf); int64_t sincelast = now - last_frame*mspf; if (hprec_ && sincelast > mspf) LOG(WARNING) << "Frame " << "(" << (target-last_frame) << ") dropped by " << (sincelast-mspf) << "ms"; // Use sleep_for for larger delays //LOG(INFO) << "Required Delay: " << (now / 40) << " = " << msdelay; while (msdelay >= 35 && sincelast != mspf) { sleep_for(milliseconds(10)); now = get_time(); msdelay = mspf - (now % mspf); } // Still lots of time so do some idle jobs if (msdelay >= 10 && sincelast != mspf) { UNIQUE_LOCK(mtx, lk); auto idle_job = jobs[kTimerIdle10].begin(); while (idle_job != jobs[kTimerIdle10].end() && msdelay >= 10 && sincelast != mspf) { (*idle_job).active = true; bool doremove = !(*idle_job).job.trigger(now); if (doremove) { idle_job = jobs[kTimerIdle10].erase(idle_job); LOG(INFO) << "Timer job removed"; } else { (*idle_job++).active = false; } now = get_time(); msdelay = mspf - (now % mspf); } } /*while (msdelay >= 10 && sincelast != mspf) { sleep_for(milliseconds(5)); now = get_time(); msdelay = mspf - (now % mspf); }*/ if (msdelay >= 2 && sincelast != mspf) { UNIQUE_LOCK(mtx, lk); auto idle_job = jobs[kTimerIdle1].begin(); while (idle_job != jobs[kTimerIdle1].end() && msdelay >= 2 && sincelast != mspf) { (*idle_job).active = true; bool doremove = !(*idle_job).job.trigger(now); if (doremove) { idle_job = jobs[kTimerIdle1].erase(idle_job); LOG(INFO) << "Timer job removed"; } else { (*idle_job++).active = false; } now = get_time(); msdelay = mspf - (now % mspf); } } if (hprec_) { // Spin loop until exact grab time // Accurate to around 4 micro seconds. if (sincelast != mspf) { // TODO: Try using sleep_for for msdelay-1 target = now / mspf; while ((now/mspf) == target) { _mm_pause(); // SSE2 nano pause intrinsic now = get_time(); }; } } else { // Accurate to just under 1 millisecond usually if (sincelast != mspf) sleep_for(milliseconds(msdelay)); now = get_time(); } last_frame = now/mspf; int64_t over = now - (last_frame*mspf); if (over > 1) LOG(WARNING) << "Timer off by " << over << "ms"; } void ftl::timer::setInterval(int ms) { mspf = ms; } void ftl::timer::setHighPrecision(bool hp) { hprec_ = hp; } int ftl::timer::getInterval() { return static_cast<int>(mspf); } void ftl::timer::setClockAdjustment(int64_t ms) { clock_adjust += ms; } void ftl::timer::setClockSlave(bool s) { clock_slave = s; } bool ftl::timer::isClockSlave() { return clock_slave; } ftl::Handle ftl::timer::add(timerlevel_t l, const std::function<bool(int64_t ts)> &f) { if (l < 0 || l >= kTimerMAXLEVEL) return {}; UNIQUE_LOCK(mtx, lk); int newid = last_id++; auto &j = jobs[l].emplace_back(); j.id = newid; j.name = "NoName"; ftl::Handle h = j.job.on(f); return h; } static void removeJob(int id) { UNIQUE_LOCK(mtx, lk); if (id < 0) return; for (size_t j=0; j<kTimerMAXLEVEL; ++j) { for (auto i=jobs[j].begin(); i!=jobs[j].end(); i++) { if ((*i).id == id) { while ((*i).active) { sleep_for(milliseconds(10)); } jobs[j].erase(i); return; } } } } static void trigger_jobs() { UNIQUE_LOCK(mtx, lk); const int64_t ts = last_frame*mspf; if (active_jobs > 1) { LOG(WARNING) << "Previous timer incomplete, skipping " << ts; return; } // First do non-blocking high precision callbacks const int64_t before = get_time(); for (auto &j : jobs[kTimerHighPrecision]) { j.job.trigger(ts); } const int64_t after = get_time(); if (after - before > 1) LOG(WARNING) << "Precision jobs took too long (" << (after-before) << "ms)"; // Then do also non-blocking swap callbacks for (auto &j : jobs[kTimerSwap]) { j.job.trigger(ts); } // Now use thread jobs to do more intensive callbacks for (auto &j : jobs[kTimerMain]) { if (j.active) { LOG(WARNING) << "Timer job too slow ... skipped for " << ts; continue; } j.active = true; active_jobs++; auto *pj = &j; // If last job in list then do in this thread if (active_jobs == static_cast<int>(jobs[kTimerMain].size())+1) { lk.unlock(); bool doremove = true; try { doremove = !pj->job.trigger(ts); } catch(const std::exception &e) { LOG(ERROR) << "Exception in timer job: " << e.what(); } pj->active = false; active_jobs--; if (doremove) removeJob(pj->id); lk.lock(); break; } else { ftl::pool.push([pj,ts](int id) { bool doremove = true; try { doremove = !pj->job.trigger(ts); } catch(const std::exception &e) { LOG(ERROR) << "Exception in timer job: " << e.what(); } pj->active = false; active_jobs--; if (doremove) removeJob(pj->id); }); } } // Final cleanup of stale jobs for (size_t j=0; j<kTimerMAXLEVEL; ++j) { for (auto i=jobs[j].begin(); i!=jobs[j].end(); i++) { if ((bool)((*i).job) == false) { i = jobs[j].erase(i); } } } } namespace ftl { extern bool running; } //static MUTEX mtx; void ftl::timer::start(bool block) { if (active) return; active = true; if (block) { active_jobs++; while (ftl::running && active) { waitTimePoint(); trigger_jobs(); } active_jobs--; } else { timer_future = ftl::pool.push([](int id) { active_jobs++; while (ftl::running && active) { waitTimePoint(); trigger_jobs(); } active_jobs--; }); } } void ftl::timer::stop(bool wait) { active = false; if (wait) { try { if (timer_future.valid()) timer_future.get(); } catch (const std::exception &e) { LOG(ERROR) << "Timer exception: " << e.what(); } int attempts = 10; // All callbacks must complete before returning. while (active_jobs > 0 && attempts-- > 0) { sleep_for(milliseconds(10)); } if (active_jobs > 0) LOG(WARNING) << "Forced job stop: " << active_jobs; } } size_t ftl::timer::count(ftl::timer::timerlevel_t l) { if (l < 0 || l >= kTimerMAXLEVEL) return 0; return jobs[l].size(); } void ftl::timer::reset() { stop(true); for (int i=0; i<ftl::timer::kTimerMAXLEVEL; i++) { jobs[i].clear(); } }