Skip to content

Commit

Permalink
#5911: Build the ThreadedDeclParser on top of the ThreadedDefLoader c…
Browse files Browse the repository at this point in the history
…lass, handling all the VFS specifics.

Since requiring a finished callback is the exception rather than the rule, offer a finished signal for client code to subscribe to.
  • Loading branch information
codereader committed Mar 2, 2022
1 parent 689e06d commit f5d9a92
Show file tree
Hide file tree
Showing 13 changed files with 131 additions and 96 deletions.
81 changes: 19 additions & 62 deletions libs/ThreadedDefLoader.h
Expand Up @@ -3,8 +3,8 @@
#include <future>
#include <functional>
#include <algorithm>
#include <sigc++/signal.h>
#include <vector>
#include "ifilesystem.h"

namespace util
{
Expand All @@ -19,22 +19,17 @@ namespace util
*
* Client code (even from multiple threads) can retrieve (and wait for) the result
* by calling the get() method.
*
* The loader will invoke the functor passed to the constructor for each encountered
* def file, in the same order as the idTech4/TDM engine (alphabetically).
*/
template <typename ReturnType>
class ThreadedDefLoader
{
private:
std::string _baseDir;
std::string _extension;
std::size_t _depth;

typedef std::function<ReturnType()> LoadFunction;
public:
using LoadFunction = std::function<ReturnType()>;
using FinishedSignal = sigc::signal<void>;

private:
LoadFunction _loadFunc;
std::function<void()> _finishedFunc;
FinishedSignal _finishedSignal;

std::shared_future<ReturnType> _result;
std::shared_future<void> _finisher;
Expand All @@ -43,25 +38,12 @@ class ThreadedDefLoader
bool _loadingStarted;

public:
ThreadedDefLoader(const std::string& baseDir, const std::string& extension, const LoadFunction& loadFunc) :
ThreadedDefLoader(baseDir, extension, 0, loadFunc)
{}

ThreadedDefLoader(const std::string& baseDir, const std::string& extension, std::size_t depth, const LoadFunction& loadFunc) :
ThreadedDefLoader(baseDir, extension, depth, loadFunc, std::function<void()>())
{}

ThreadedDefLoader(const std::string& baseDir, const std::string& extension, std::size_t depth,
const LoadFunction& loadFunc, const std::function<void()>& finishedFunc) :
_baseDir(baseDir),
_extension(extension),
_depth(depth),
ThreadedDefLoader(const LoadFunction& loadFunc) :
_loadFunc(loadFunc),
_finishedFunc(finishedFunc),
_loadingStarted(false)
{}

~ThreadedDefLoader()
virtual ~ThreadedDefLoader()
{
// wait for any worker thread to finish
reset();
Expand Down Expand Up @@ -120,50 +102,25 @@ class ThreadedDefLoader
}
}

protected:
void loadFiles(const vfs::VirtualFileSystem::VisitorFunc& visitor)
FinishedSignal& signal_finished()
{
loadFiles(GlobalFileSystem(), visitor);
}

void loadFiles(vfs::VirtualFileSystem& vfs, const vfs::VirtualFileSystem::VisitorFunc& visitor)
{
// Accumulate all the files and sort them before calling the visitor
std::vector<vfs::FileInfo> _incomingFiles;
_incomingFiles.reserve(200);

vfs.forEachFile(_baseDir, _extension, [&](const vfs::FileInfo& info)
{
_incomingFiles.push_back(info);
}, _depth);

// Sort the files by name
std::sort(_incomingFiles.begin(), _incomingFiles.end(), [](const vfs::FileInfo& a, const vfs::FileInfo& b)
{
return a.name < b.name;
});

// Dispatch the sorted list to the visitor
std::for_each(_incomingFiles.begin(), _incomingFiles.end(), visitor);
return _finishedSignal;
}

private:
struct FinishFunctionCaller
struct FinishSignalEmitter
{
std::function<void()> _function;
FinishedSignal& _signal;
std::shared_future<void>& _targetFuture;

FinishFunctionCaller(const std::function<void()>& function, std::shared_future<void>& targetFuture) :
_function(function),
FinishSignalEmitter(FinishedSignal& signal, std::shared_future<void>& targetFuture) :
_signal(signal),
_targetFuture(targetFuture)
{}

~FinishFunctionCaller()
~FinishSignalEmitter()
{
if (_function)
{
_targetFuture = std::async(std::launch::async, _function);
}
_targetFuture = std::async(std::launch::async, std::bind(&FinishedSignal::emit, _signal));
}
};

Expand All @@ -176,8 +133,8 @@ class ThreadedDefLoader
_loadingStarted = true;
_result = std::async(std::launch::async, [&]()
{
// When going out of scope, this instance invokes the finished callback in a separate thread
FinishFunctionCaller finisher(_finishedFunc, _finisher);
// When going out of scope, this instance invokes the finished signal in a separate thread
FinishSignalEmitter finisher(_finishedSignal, _finisher);
return _loadFunc();
});
}
Expand Down
70 changes: 70 additions & 0 deletions libs/parser/ThreadedDeclParser.h
@@ -0,0 +1,70 @@
#pragma once

#include "ifilesystem.h"
#include "ThreadedDefLoader.h"

namespace parser
{

/**
* Threaded declaration parser, visiting all files associated to the given
* decl type, processing the files in the correct order.
*/
template <typename ReturnType>
class ThreadedDeclParser :
public util::ThreadedDefLoader<ReturnType>
{
public:
using LoadFunction = util::ThreadedDefLoader<ReturnType>::LoadFunction;

private:
std::string _baseDir;
std::string _extension;
std::size_t _depth;

public:
ThreadedDeclParser(const std::string& baseDir, const std::string& extension,
const LoadFunction& loadFunc) :
ThreadedDeclParser(baseDir, extension, 0, loadFunc)
{}

ThreadedDeclParser(const std::string& baseDir, const std::string& extension, std::size_t depth,
const LoadFunction& loadFunc) :
util::ThreadedDefLoader<typename ReturnType>(loadFunc),
_baseDir(baseDir),
_extension(extension),
_depth(depth)
{}

virtual ~ThreadedDeclParser()
{}

protected:
void loadFiles(const vfs::VirtualFileSystem::VisitorFunc& visitor)
{
loadFiles(GlobalFileSystem(), visitor);
}

void loadFiles(vfs::VirtualFileSystem& vfs, const vfs::VirtualFileSystem::VisitorFunc& visitor)
{
// Accumulate all the files and sort them before calling the visitor
std::vector<vfs::FileInfo> _incomingFiles;
_incomingFiles.reserve(200);

vfs.forEachFile(_baseDir, _extension, [&](const vfs::FileInfo& info)
{
_incomingFiles.push_back(info);
}, _depth);

// Sort the files by name
std::sort(_incomingFiles.begin(), _incomingFiles.end(), [](const vfs::FileInfo& a, const vfs::FileInfo& b)
{
return a.name < b.name;
});

// Dispatch the sorted list to the visitor
std::for_each(_incomingFiles.begin(), _incomingFiles.end(), visitor);
}
};

}
4 changes: 2 additions & 2 deletions plugins/dm.gui/gui/GuiManager.h
Expand Up @@ -5,7 +5,7 @@
#include <map>
#include "ifilesystem.h"
#include "string/string.h"
#include "ThreadedDefLoader.h"
#include "parser/ThreadedDeclParser.h"

namespace gui
{
Expand Down Expand Up @@ -50,7 +50,7 @@ class GuiManager :
typedef std::map<std::string, GuiInfo> GuiInfoMap;
GuiInfoMap _guis;

util::ThreadedDefLoader<void> _guiLoader;
parser::ThreadedDeclParser<void> _guiLoader;

// A List of all the errors occuring lastly.
StringList _errorList;
Expand Down
4 changes: 2 additions & 2 deletions plugins/sound/SoundManager.h
Expand Up @@ -6,7 +6,7 @@
#include "isound.h"
#include "icommandsystem.h"

#include "ThreadedDefLoader.h"
#include "parser/ThreadedDeclParser.h"
#include <map>

namespace sound {
Expand All @@ -26,7 +26,7 @@ class SoundManager :

// Shaders are loaded asynchronically, this loader
// takes care of the worker thread
util::ThreadedDefLoader<void> _defLoader;
parser::ThreadedDeclParser<void> _defLoader;

SoundShader::Ptr _emptyShader;

Expand Down
9 changes: 6 additions & 3 deletions radiantcore/eclass/EClassManager.cpp
Expand Up @@ -24,10 +24,13 @@ namespace eclass {
// Constructor
EClassManager::EClassManager() :
_realised(false),
_defLoader("def/", "def", 1, std::bind(&EClassManager::loadDefAndResolveInheritance, this),
std::bind(&EClassManager::onDefLoadingCompleted, this)),
_defLoader("def/", "def", 1, std::bind(&EClassManager::loadDefAndResolveInheritance, this)),
_curParseStamp(0)
{}
{
_defLoader.signal_finished().connect(
sigc::mem_fun(this, &EClassManager::onDefLoadingCompleted)
);
}

sigc::signal<void> EClassManager::defsLoadingSignal() const
{
Expand Down
8 changes: 5 additions & 3 deletions radiantcore/eclass/EClassManager.h
@@ -1,11 +1,12 @@
#pragma once

#include <sigc++/connection.h>
#include <sigc++/trackable.h>
#include "ieclass.h"
#include "icommandsystem.h"
#include "ifilesystem.h"
#include "itextstream.h"
#include "ThreadedDefLoader.h"
#include "parser/ThreadedDeclParser.h"

#include "EntityClass.h"
#include "Doom3ModelDef.h"
Expand All @@ -30,7 +31,8 @@ namespace eclass
*/
class EClassManager :
public IEntityClassManager,
public vfs::VirtualFileSystem::Observer
public vfs::VirtualFileSystem::Observer,
public sigc::trackable
{
// Whether the entity classes have been realised
bool _realised;
Expand All @@ -43,7 +45,7 @@ class EClassManager :
Models _models;

// The worker thread loading the eclasses will be managed by this
util::ThreadedDefLoader<void> _defLoader;
parser::ThreadedDeclParser<void> _defLoader;

// A unique parse pass identifier, used to check when existing
// definitions have been parsed
Expand Down
6 changes: 3 additions & 3 deletions radiantcore/fonts/FontLoader.h
Expand Up @@ -3,15 +3,15 @@
#include "ifonts.h"

#include "ifilesystem.h"
#include "ThreadedDefLoader.h"
#include "parser/ThreadedDeclParser.h"

namespace fonts
{

class FontManager;

class FontLoader :
public util::ThreadedDefLoader<void>
public parser::ThreadedDeclParser<void>
{
private:
// The manager for registering the fonts
Expand All @@ -22,7 +22,7 @@ class FontLoader :
public:
// Constructor. Set the base path of the search.
FontLoader(const std::string& path, const std::string& extension, FontManager& manager) :
ThreadedDefLoader(path, extension, 2, std::bind(&FontLoader::loadFonts, this)),
ThreadedDeclParser(path, extension, 2, std::bind(&FontLoader::loadFonts, this)),
_manager(manager),
_numFonts(0)
{}
Expand Down
9 changes: 4 additions & 5 deletions radiantcore/particles/ParticleLoader.h
Expand Up @@ -3,23 +3,22 @@
#include <functional>
#include <iosfwd>
#include "ParticleDef.h"
#include "ThreadedDefLoader.h"
#include "parser/ThreadedDeclParser.h"
#include "parser/DefTokeniser.h"

namespace particles
{

class ParticleLoader :
public util::ThreadedDefLoader<void>
public parser::ThreadedDeclParser<void>
{
private:
std::function<ParticleDefPtr(const std::string&)> _findOrInsert;
std::function<void()> _onFinished;

public:
ParticleLoader(const std::function<ParticleDefPtr(const std::string&)>& findOrInsert,
const std::function<void()>& onFinished) :
ThreadedDefLoader(PARTICLES_DIR, PARTICLES_EXT, 1, std::bind(&ParticleLoader::load, this), onFinished),
ParticleLoader(const std::function<ParticleDefPtr(const std::string&)>& findOrInsert) :
parser::ThreadedDeclParser<void>(PARTICLES_DIR, PARTICLES_EXT, 1, std::bind(&ParticleLoader::load, this)),
_findOrInsert(findOrInsert)
{}

Expand Down
22 changes: 11 additions & 11 deletions radiantcore/particles/ParticlesManager.cpp
Expand Up @@ -43,9 +43,11 @@ namespace
}

ParticlesManager::ParticlesManager() :
_defLoader(std::bind(&ParticlesManager::findOrInsertParticleDefInternal, this, std::placeholders::_1),
std::bind(&ParticlesManager::onParticlesLoaded, this))
{}
_defLoader(std::bind(&ParticlesManager::findOrInsertParticleDefInternal, this, std::placeholders::_1))
{
_defLoader.signal_finished().connect(
sigc::mem_fun(this, &ParticlesManager::onParticlesLoaded));
}

sigc::signal<void> ParticlesManager::signal_particlesReloaded() const
{
Expand Down Expand Up @@ -162,14 +164,12 @@ const std::string& ParticlesManager::getName() const

const StringSet& ParticlesManager::getDependencies() const
{
static StringSet _dependencies;

if (_dependencies.empty())
{
_dependencies.insert(MODULE_VIRTUALFILESYSTEM);
_dependencies.insert(MODULE_COMMANDSYSTEM);
_dependencies.insert(MODULE_FILETYPES);
}
static StringSet _dependencies
{
MODULE_VIRTUALFILESYSTEM,
MODULE_COMMANDSYSTEM,
MODULE_FILETYPES,
};

return _dependencies;
}
Expand Down

0 comments on commit f5d9a92

Please sign in to comment.