#pragma once #include #include #include "RunningAverage.hpp" #include "Declarations.hpp" #include "Debug.hpp" #include "threadsafe.hpp" #include "boost.hpp" #include "CEvent.hpp" #include "atomic.hpp" #include "boost/thread.hpp" #include "boost/function.hpp" #include "boost/shared_ptr.hpp" #include "boost/scoped_ptr.hpp" #include "boost/noncopyable.hpp" #include "boost/intrusive/list.hpp" #ifdef _WIN32 #undef min #undef max #endif LOGGROUP(TaskSchedulerInit) LOGGROUP(TaskSchedulerRun) LOGGROUP(TaskSchedulerFindJob) namespace Aya { /// A singleton object responsible for scheduling the execution TaskScheduler.Jobs. class TaskScheduler { struct SleepingTag; typedef boost::intrusive::list_base_hook> SleepingHook; struct WaitingTag; typedef boost::intrusive::list_base_hook> WaitingHook; public: class Thread; typedef std::vector> Threads; class Job; bool DataModel30fpsThrottle; Time lastCyclcTimestamp; bool cyclicExecutiveWaitForNextFrame; int nonCyclicJobsToDo; int cyclicExecutiveLoopId; class AyaBaseClass Arbiter { protected: ActivityMeter<2> activityMeter; public: virtual std::string arbiterName() = 0; virtual bool areExclusive(Job* job1, Job* job2) = 0; virtual bool isThrottled() = 0; virtual void preStep(TaskScheduler::Job* job) { activityMeter.increment(); } virtual void postStep(TaskScheduler::Job* job) { activityMeter.decrement(); } double getAverageActivity() { return activityMeter.averageValue(); } virtual Arbiter* getSyncronizationArbiter() { return this; }; virtual int getNumPlayers() const { return 1; } }; typedef enum { Done, // The job will be removed from the TaskScheduler Stepped, // Another step will be scheduled } StepResult; static TaskScheduler& singleton(); typedef enum { LastError, AccumulatedError, FIFO } PriorityMethod; static PriorityMethod priorityMethod; #ifdef AYA_TEST_BUILD static int findJobFPS; static bool updateJobPriorityOnWake; #endif double threadAffinityPreference; typedef enum { PerCore4 = 104, PerCore3 = 103, PerCore2 = 102, PerCore1 = 101, Auto = 0, Threads1 = 1, Threads2 = 2, Threads3 = 3, Threads4 = 4, Threads8 = 8, Threads16 = 16 } ThreadPoolConfig; bool shouldDropThread() const; void dropThread(Thread* thread); size_t getThreadCount() { return threadCount; } void setThreadCount(ThreadPoolConfig threadConfig); void disableThreads(int count, Threads& threads); void enableThreads(Threads& threads); void add(boost::shared_ptr job); void reschedule(boost::shared_ptr job); void remove(boost::shared_ptr job) { remove(job, false, NULL); } // This version of remove might lead to deadlocks in some cases, so try not to use it void removeBlocking(boost::shared_ptr job) { remove(job, true, NULL); } // This version of remove calls back several times a second while waiting. // You might use this to process events in order to avoid deadlocks. void removeBlocking(boost::shared_ptr job, boost::function callbackPing) { remove(job, true, callbackPing); } void getJobsInfo(std::vector>& result); void getJobsByName(const std::string& name, std::vector>& result); // Performance counters double numSleepingJobs() const { return sleepingJobCount.value(); } double numWaitingJobs() const { return waitingJobCount.value(); } double numRunningJobs() const { return averageRunningJobCount.value(); } double threadAffinity() const { return averageThreadAffinity.value(); } size_t threadPoolSize() const { return threads.size(); } double schedulerRate() const { return schedulerDutyCycle.rate(); } double getSchedulerDutyCyclePerThread() const; double getErrorCalculationRate() const { return errorCalculationPerSec.rate(); } double getSortFrequency() const { return sortFrequency.rate(); } Aya::atomic taskCount; void printDiagnostics(bool aggregateJobs); void printJobs(); void setJobsExtendedStatsWindow(double seconds); // set seconds to 0.0 to disable. void cancelCyclicExecutive(); bool isCyclicExecutive() { return cyclicExecutiveEnabled; } void releaseCyclicExecutive(TaskScheduler::Job* job); private: // ** Here for thread saftey. ** struct CyclicExecutiveJob { boost::shared_ptr job; bool cyclicExecutiveExecuted; bool isRunning; CyclicExecutiveJob(const boost::shared_ptr& j) { job = j; cyclicExecutiveExecuted = false; isRunning = false; } // Allows for using std::find. bool operator==(const boost::shared_ptr& j) { return job == j; } bool operator==(const TaskScheduler::Job& j) { return job.get() == &j; } }; TaskScheduler(); ~TaskScheduler(); void endAllThreads(); void sampleRunningJobCount(); static bool jobCompare(const CyclicExecutiveJob& jobA, const CyclicExecutiveJob& jobB); void remove(boost::shared_ptr job, bool joinJob, boost::function callbackPing); void remove(const boost::shared_ptr& job, boost::shared_ptr joinEvent); void scheduleJob(Job& job); static bool areExclusive(Job* job1, Job* job2, const shared_ptr& arbiterHint); bool conflictsWithScheduledJob(Job* item) const; void incrementThreadCount(); void decrementThreadCount(); Aya::mutex mutex; typedef std::set> AllJobs; AllJobs allJobs; typedef boost::intrusive::list> SleepingJobs; SleepingJobs sleepingJobs; bool cyclicExecutiveEnabled; typedef std::vector CyclicExecutiveJobs; CyclicExecutiveJobs cyclicExecutiveJobs; typedef boost::intrusive::list> WaitingJobs; WaitingJobs waitingJobs; shared_ptr nextScheduledJob; void wakeSleepingJobs(); void enqueueWaitingJob(Job& job); Time::Interval getShortestSleepTime() const; boost::shared_ptr findJobToRun(boost::shared_ptr requestingThread); boost::shared_ptr findJobToRunNonCyclicJobs(boost::shared_ptr requestingThread, Aya::Time now); int numNonCyclicJobsWithWork(); void checkStillWaitingNextFrame(Time now); RunningAverage sleepingJobCount; RunningAverage waitingJobCount; RunningAverage averageRunningJobCount; RunningAverageDutyCycle schedulerDutyCycle; // time spent scheduling jobs RunningAverage averageThreadAffinity; RunningAverageTimeInterval<> errorCalculationPerSec; RunningAverageTimeInterval<> sortFrequency; Aya::atomic runningJobCount; Time nextWakeTime; Time lastSortTime; Threads threads; size_t desiredThreadCount; CEvent sampleRunningJobCountEvent; boost::scoped_ptr runningJobCounterThread; Aya::atomic threadCount; static Aya::thread_specific_reference currentJob; static void static_init(); }; // A simple arbiter that prevents all members of it to execute concurrently class ExclusiveArbiter : public TaskScheduler::Arbiter , boost::noncopyable { public: virtual bool areExclusive(TaskScheduler::Job* job1, TaskScheduler::Job* job2); virtual std::string arbiterName() { return "ExclusiveArbiter"; } virtual bool isThrottled() { return false; } static ExclusiveArbiter singleton; }; class SimpleThrottlingArbiter : public TaskScheduler::Arbiter { mutable bool throttled; Aya::atomic updatingThrottle; static Aya::atomic arbiterCount; public: static bool isThrottlingEnabled; SimpleThrottlingArbiter() : throttled(false) , updatingThrottle(0) { ++arbiterCount; } ~SimpleThrottlingArbiter() { --arbiterCount; } virtual bool isThrottled() { if (!isThrottlingEnabled) return false; long count = arbiterCount; if (count <= 1) return false; if (updatingThrottle.swap(1) == 0) { double cutoff = ((double)Aya::TaskScheduler::singleton().getThreadCount()) / (double)count; // hysteresis if (throttled) { throttled = getAverageActivity() >= cutoff; } else { throttled = getAverageActivity() >= 1.1 * cutoff; } --updatingThrottle; } return throttled; } }; } // namespace Aya