228 lines
4.9 KiB
C++
228 lines
4.9 KiB
C++
#include "threaded_loader.h"
|
|
#include "threads.h"
|
|
#include <unordered_map>
|
|
#include <set>
|
|
#include <list>
|
|
#include "main/references.h"
|
|
#include "main/logging.h"
|
|
|
|
namespace Loading {
|
|
|
|
struct Task;
|
|
std::function<void(void)> threadSetup, threadCleanup;
|
|
|
|
threads::atomic_int workers(0);
|
|
bool finalized = false;
|
|
double startTime;
|
|
|
|
threads::Mutex taskLock;
|
|
threads::Signal processingTasks;
|
|
bool tasksFinished;
|
|
std::unordered_map<std::string, Task*> namedTasks;
|
|
std::list<Task*> tasks;
|
|
auto nextTask = tasks.end();
|
|
|
|
struct Task {
|
|
std::string name;
|
|
|
|
std::vector<Task*> dependencies;
|
|
|
|
bool finished;
|
|
double executionTime;
|
|
|
|
int threadRestriction;
|
|
|
|
std::function<void(void)> _execute;
|
|
|
|
Task(const std::string& Name, const char* depends) : name(Name), finished(false), threadRestriction(-1) {
|
|
while(depends && depends[0] != '\0') {
|
|
const char* end = strchr(depends+1, ',');
|
|
if(end == 0)
|
|
end = strchr(depends, '\0');
|
|
|
|
std::string dependencyName(depends, end - depends);
|
|
if(dependencyName == name)
|
|
throw "Cylic loading depedency";
|
|
|
|
auto dependency = namedTasks.find(dependencyName);
|
|
if(dependency != namedTasks.end())
|
|
dependencies.push_back(dependency->second);
|
|
else
|
|
throw "Missing dependency";
|
|
|
|
depends = *end == ',' ? end+1 : end;
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
if(namedTasks.find(Name) != namedTasks.end())
|
|
throw "Duplicate task";
|
|
#endif
|
|
|
|
namedTasks[Name] = this;
|
|
}
|
|
|
|
bool mayExecute() {
|
|
if(threadRestriction != threads::invalidThreadID && threadRestriction != threads::getThreadID())
|
|
return false;
|
|
for(unsigned i = 0; i < dependencies.size(); ++i)
|
|
if(!dependencies[i]->isFinished())
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool isMyJob() {
|
|
return threadRestriction == threads::getThreadID();
|
|
}
|
|
|
|
void execute() {
|
|
if(isFinished())
|
|
return;
|
|
double start = devices.driver->getAccurateTime();
|
|
info("%s started at %.1fs on thread %i", name.c_str(), start - startTime, threads::getThreadID());
|
|
|
|
_execute();
|
|
double end = devices.driver->getAccurateTime();
|
|
executionTime = end - start;
|
|
finished = true;
|
|
|
|
info("%s took %.1fms on thread %i", name.c_str(), executionTime * 1000.0, threads::getThreadID());
|
|
}
|
|
|
|
bool isFinished() {
|
|
return finished;
|
|
}
|
|
};
|
|
|
|
void addTask(const std::string& name, const char* depends, std::function<void(void)> execute, int threadRestriction) {
|
|
Task* task = new Task(name, depends);
|
|
|
|
task->_execute = execute;
|
|
task->threadRestriction = threadRestriction;
|
|
|
|
taskLock.lock();
|
|
tasks.push_back(task);
|
|
taskLock.release();
|
|
}
|
|
|
|
bool finished() {
|
|
return tasksFinished;
|
|
}
|
|
|
|
threads::threadreturn threadcall processLoad(void* arg) {
|
|
if(threadSetup)
|
|
threadSetup();
|
|
|
|
while(!finalized || !tasks.empty()) {
|
|
process();
|
|
threads::sleep(1);
|
|
}
|
|
--workers;
|
|
|
|
//This lets us have accurate timings, since we want
|
|
//to know when the last task finished, not when
|
|
//the loading period is over (ie for preloading)
|
|
if(workers == 0) {
|
|
processingTasks.wait(0);
|
|
double totalTime = 0;
|
|
auto iTask = namedTasks.begin();
|
|
while(iTask != namedTasks.end()) {
|
|
totalTime += iTask->second->executionTime;
|
|
|
|
delete iTask->second;
|
|
iTask = namedTasks.erase(iTask);
|
|
}
|
|
|
|
double time = devices.driver->getAccurateTime(), loadTime = time - startTime;
|
|
print("Loaded in %.1f seconds", loadTime);
|
|
info("Tasks used a total of %.1f seconds (%d%% faster)", totalTime, (int)(100.0*totalTime/loadTime)-100);
|
|
tasksFinished = true;
|
|
}
|
|
|
|
if(threadCleanup)
|
|
threadCleanup();
|
|
return 0;
|
|
}
|
|
|
|
void prepare(unsigned threads, std::function<void(void)> threadPrep, std::function<void(void)> threadExit) {
|
|
threadSetup = threadPrep;
|
|
threadCleanup = threadExit;
|
|
tasksFinished = false;
|
|
|
|
startTime = devices.driver->getAccurateTime();
|
|
workers = threads;
|
|
for(unsigned i = 0; i < threads; ++i)
|
|
threads::createThread(processLoad,0);
|
|
}
|
|
|
|
void finalize() {
|
|
double time = devices.driver->getAccurateTime();
|
|
info("Preparing tasks took %.1f ms", (time - startTime) * 1000.0);
|
|
|
|
finalized = true;
|
|
}
|
|
|
|
void finish() {
|
|
while(workers != 0)
|
|
threads::sleep(0);
|
|
finalized = false;
|
|
|
|
nextTask = tasks.end();
|
|
}
|
|
|
|
void process() {
|
|
processingTasks.signalUp();
|
|
if(tasks.empty()) {
|
|
processingTasks.signalDown();
|
|
return;
|
|
}
|
|
|
|
taskLock.lock();
|
|
|
|
Task* task = 0;
|
|
for(auto i = tasks.begin(), end = tasks.end(); i != end; ++i) {
|
|
Task* check = *i;
|
|
if(check->isMyJob() && check->mayExecute()) {
|
|
task = check;
|
|
if(nextTask == i)
|
|
nextTask = tasks.erase(i);
|
|
else
|
|
tasks.erase(i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(task == 0) {
|
|
while(tasks.empty() == false) {
|
|
if(nextTask == tasks.end())
|
|
nextTask = tasks.begin();
|
|
|
|
Task* check = *nextTask;
|
|
if(check->mayExecute()) {
|
|
nextTask = tasks.erase(nextTask);
|
|
task = check;
|
|
break;
|
|
}
|
|
|
|
++nextTask;
|
|
taskLock.release();
|
|
threads::sleep(0);
|
|
taskLock.lock();
|
|
}
|
|
}
|
|
|
|
taskLock.release();
|
|
|
|
if(task) {
|
|
task->execute();
|
|
if(!task->isFinished()) {
|
|
taskLock.lock();
|
|
tasks.push_back(task);
|
|
taskLock.release();
|
|
}
|
|
}
|
|
|
|
processingTasks.signalDown();
|
|
}
|
|
|
|
};
|