/* * OpenClonk, http://www.openclonk.org * * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/ * Copyright (c) 2009-2016, The OpenClonk Team and contributors * * Distributed under the terms of the ISC license; see accompanying file * "COPYING" for details. * * "Clonk" is a registered trademark of Matthes Bender, used with permission. * See accompanying file "TRADEMARK" for details. * * To redistribute this file separately, substitute the full license texts * for the above references. */ /* A simple scheduler for ccoperative multitasking */ #ifndef STDSCHEDULER_H #define STDSCHEDULER_H #include "platform/StdSync.h" // Events are Windows-specific #ifdef _WIN32 #define STDSCHEDULER_USE_EVENTS #define HAVE_WINTHREAD #define STDSCHEDULER_EVENT_MESSAGE INVALID_HANDLE_VALUE struct pollfd; #ifndef STDSCHEDULER_USE_EVENTS #include "platform/C4windowswrapper.h" #include #endif // STDSCHEDULER_USE_EVENTS #else // _WIN32 #ifdef HAVE_POLL_H #include #else // HAVE_POLL_H #include #endif // HAVE_POLL_H #ifdef HAVE_PTHREAD #include #endif // HAVE_PTHREAD #ifdef __APPLE__ #include #endif #endif // _WIN32 typedef struct _GMainLoop GMainLoop; // Abstract class for a process class StdSchedulerProc { private: class StdScheduler *scheduler{nullptr}; protected: void Changed(); public: StdSchedulerProc() = default; virtual ~StdSchedulerProc() = default; // Do whatever the process wishes to do. Should not block longer than the timeout value. // Is called whenever the process is signaled or a timeout occurs. virtual bool Execute(int iTimeout = -1, pollfd * readyfds = nullptr) = 0; // As Execute, but won't return until the given timeout value has elapsed or a failure occurs bool ExecuteUntil(int iTimeout = -1); // Signal for calling Execute() #ifdef STDSCHEDULER_USE_EVENTS virtual HANDLE GetEvent() { return nullptr; } #else virtual void GetFDs(std::vector &) { } #endif // Call Execute() after this time has elapsed virtual C4TimeMilliseconds GetNextTick(C4TimeMilliseconds tNow); // Is the process signal currently set? bool IsSignaled(); // Is this the expensive game tick? virtual bool IsLowPriority() { return false; } virtual bool IsNotify() { return false; } virtual uint32_t TimerInterval() { return 0; } friend class StdScheduler; }; // A simple timer proc class CStdTimerProc : public StdSchedulerProc { public: CStdTimerProc(uint32_t iDelay) : tLastTimer(C4TimeMilliseconds::NegativeInfinity), iDelay(iDelay) { } ~CStdTimerProc() override { Set(); } private: C4TimeMilliseconds tLastTimer; uint32_t iDelay; public: void Set() { tLastTimer = C4TimeMilliseconds::NegativeInfinity; } void SetDelay(uint32_t inDelay) { iDelay = inDelay; Changed(); } bool CheckAndReset() { C4TimeMilliseconds tTime = C4TimeMilliseconds::Now(); if (tTime < tLastTimer + iDelay) return false; // Compensate light drifting int32_t iDrift = tTime - (tLastTimer + iDelay); // a positive time difference because of above check tLastTimer = tTime - std::min(iDrift, (int32_t) iDelay / 2); return true; } // StdSchedulerProc override C4TimeMilliseconds GetNextTick(C4TimeMilliseconds tNow) override { return tLastTimer + iDelay; } uint32_t TimerInterval() override { return iDelay; } }; class C4ApplicationSec1Timer : protected CStdTimerProc { public: C4ApplicationSec1Timer() : CStdTimerProc(1000) { } virtual void OnSec1Timer() = 0; protected: bool Execute(int, pollfd *) override { if (CheckAndReset()) OnSec1Timer(); return true; } }; // A simple alertable proc class CStdNotifyProc : public StdSchedulerProc { public: CStdNotifyProc(); void Notify(); bool CheckAndReset(); bool IsNotify() override { return true; } #ifdef STDSCHEDULER_USE_EVENTS ~CStdNotifyProc() override = default; // StdSchedulerProc override HANDLE GetEvent() override { return Event.GetEvent(); } private: CStdEvent Event; #else // STDSCHEDULER_USE_EVENTS ~CStdNotifyProc() override; // StdSchedulerProc override void GetFDs(std::vector & checkfds) override; private: int fds[2]; #endif }; #ifdef STDSCHEDULER_USE_EVENTS class CStdMultimediaTimerProc : public CStdNotifyProc { public: CStdMultimediaTimerProc(uint32_t iDelay); ~CStdMultimediaTimerProc() override; private: static int iTimePeriod; uint32_t uCriticalTimerDelay; UINT idCriticalTimer,uCriticalTimerResolution; CStdEvent Event; bool Check() { return Event.WaitFor(0); } public: void SetDelay(uint32_t iDelay); void Set() { Event.Set(); } bool CheckAndReset(); // StdSchedulerProc overrides HANDLE GetEvent() override { return Event.GetEvent(); } }; #elif defined(HAVE_SYS_TIMERFD_H) // timer proc using a timerfd class CStdMultimediaTimerProc : public StdSchedulerProc { public: CStdMultimediaTimerProc(uint32_t iDelay); ~CStdMultimediaTimerProc() override; private: int fd; public: void Set(); void SetDelay(uint32_t inDelay); bool CheckAndReset(); // StdSchedulerProc overrides void GetFDs(std::vector & checkfds) override; }; #else #define CStdMultimediaTimerProc CStdTimerProc #endif // A simple process scheduler class StdScheduler { public: StdScheduler(); virtual ~StdScheduler(); private: // Process list std::vector procs; bool isInManualLoop{false}; // Unblocker class NoopNotifyProc : public CStdNotifyProc { public: bool Execute(int, pollfd *) override { CheckAndReset(); return true; } }; NoopNotifyProc Unblocker; // Dummy lists (preserved to reduce allocs) #ifdef STDSCHEDULER_USE_EVENTS std::vector eventHandles; std::vector eventProcs; #endif public: int getProcCnt() const { return procs.size()-1; } // ignore internal NoopNotifyProc bool hasProc(StdSchedulerProc *pProc) { return std::find(procs.begin(), procs.end(), pProc) != procs.end(); } bool IsInManualLoop() { return isInManualLoop; } void Clear(); void Set(StdSchedulerProc **ppProcs, int iProcCnt); void Add(StdSchedulerProc *pProc); void Remove(StdSchedulerProc *pProc); // extra events for above Add/Remove methods void Added(StdSchedulerProc *pProc); void Removing(StdSchedulerProc *pProc); // called by StdSchedulerProcs when something important about their configuration changed void Changed(StdSchedulerProc *pProc); // needs to be called on thread tasks for this scheduler are meant to be run on void StartOnCurrentThread(); C4TimeMilliseconds GetNextTick(C4TimeMilliseconds tNow); bool ScheduleProcs(int iTimeout = 1000/36); void UnBlock(); protected: // overridable virtual void OnError(StdSchedulerProc *) { } virtual bool DoScheduleProcs(int iTimeout); }; // A simple process scheduler thread class StdSchedulerThread : public StdScheduler { public: StdSchedulerThread(); ~StdSchedulerThread() override; private: // thread control bool fRunThreadRun; bool fThread{false}; #ifdef HAVE_WINTHREAD uintptr_t iThread; #elif defined(HAVE_PTHREAD) pthread_t Thread; #endif public: void Clear(); void Set(StdSchedulerProc **ppProcs, int iProcCnt); void Add(StdSchedulerProc *pProc); void Remove(StdSchedulerProc *pProc); bool Start(); void Stop(); private: // thread func #ifdef HAVE_WINTHREAD static void __cdecl _ThreadFunc(void *); #elif defined(HAVE_PTHREAD) static void *_ThreadFunc(void *); #endif unsigned int ThreadFunc(); }; class StdThread { private: bool fStarted{false}; bool fStopSignaled{false}; #ifdef HAVE_WINTHREAD uintptr_t iThread; #elif defined(HAVE_PTHREAD) pthread_t Thread; #endif public: StdThread(); virtual ~StdThread() { Stop(); } bool Start(); void SignalStop(); // mark thread to stop but don't wait void Stop(); bool IsStarted() { return fStarted; } protected: virtual void Execute() = 0; bool IsStopSignaled(); virtual bool IsSelfDestruct() { return false; } // whether thread should delete itself after execution finished private: // thread func #ifdef HAVE_WINTHREAD static void __cdecl _ThreadFunc(void *); #elif defined(HAVE_PTHREAD) static void *_ThreadFunc(void *); #endif unsigned int ThreadFunc(); }; #endif