diff --git a/rwengine/include/WorkContext.hpp b/rwengine/include/WorkContext.hpp new file mode 100644 index 00000000..657e533d --- /dev/null +++ b/rwengine/include/WorkContext.hpp @@ -0,0 +1,85 @@ +#pragma once +#ifndef _LOADCONTEXT_HPP_ +#define _LOADCONTEXT_HPP_ + +#include +#include +#include +#include +#include + +class WorkContext; + +class LoadWorker +{ + WorkContext* _context; + +public: + + bool _running; + std::thread _thread; + void start(); + + LoadWorker( WorkContext* context ) + : _context( context ), _running(true), + _thread( std::bind(&LoadWorker::start, this) ) { } + + ~LoadWorker( ) + { + _running = false; + _thread.join(); + } +}; + +class WorkJob +{ + WorkContext* _context; + +public: + + WorkJob(WorkContext* context) + : _context(context) {} + + virtual ~WorkJob() {} + + /** + * @brief getContext + * @return The loading context for this Loader + */ + WorkContext* getContext() const { return _context; } + + virtual void work() = 0; + virtual void complete() {} +}; + +class WorkContext +{ + std::queue _workQueue; + std::queue _completeQueue; + + LoadWorker _worker; + + std::mutex _inMutex; + std::mutex _outMutex; + +public: + + WorkContext() + : _worker(this) { } + + void queueJob( WorkJob* job ) + { + std::lock_guard guard(_inMutex); + _workQueue.push( job ); + } + + // Called by the worker thread - don't touch; + void workNext(); + + const std::queue getWorkQueue() const { return _workQueue; } + const std::queue getCompleteQueue() const { return _completeQueue; } + + void update(); +}; + +#endif diff --git a/rwengine/include/loaders/DataLoader.hpp b/rwengine/include/loaders/DataLoader.hpp index cea75b22..a33a419a 100644 --- a/rwengine/include/loaders/DataLoader.hpp +++ b/rwengine/include/loaders/DataLoader.hpp @@ -4,15 +4,15 @@ #include -class LoadContext; +class WorkContext; class DataLoader { - LoadContext* _context; + WorkContext* _context; public: - DataLoader(LoadContext* context) + DataLoader(WorkContext* context) : _context(context) {} virtual ~DataLoader() {} @@ -21,7 +21,7 @@ public: * @brief getContext * @return The loading context for this Loader */ - LoadContext* getContext() const { return _context; } + WorkContext* getContext() const { return _context; } /** * @brief load the data contained in a set of bytes diff --git a/rwengine/include/loaders/LoadContext.hpp b/rwengine/include/loaders/LoadContext.hpp deleted file mode 100644 index f087dc57..00000000 --- a/rwengine/include/loaders/LoadContext.hpp +++ /dev/null @@ -1,86 +0,0 @@ -#pragma once -#ifndef _LOADCONTEXT_HPP_ -#define _LOADCONTEXT_HPP_ - -#include -#include -#include -#include - -class DataLoader; -class LoadContext; - -struct LoadRequest -{ - DataLoader* loader; - const char* data; - size_t size; - std::string filename; -}; - -class LoadWorker -{ - LoadContext* _context; - -public: - - bool _started; - bool _running; - std::thread _thread; - void start(); - - LoadWorker( LoadContext* context ) - : _context( context ), _started(false), _running(true), - _thread( std::bind(&LoadWorker::start, this) ) { } - - ~LoadWorker( ) - { - _started = true; - _running = false; - _thread.join(); - } -}; - -class LoadContext -{ - std::queue _loadQueue; - std::queue _createQueue; - unsigned int _loaded; - unsigned int _loading; - - LoadWorker _worker; - -public: - - LoadContext() - : _loaded(0), _loading(0), _worker(this) { } - - void add(DataLoader* loader, const char* data, size_t size) - { - _loadQueue.push({loader, data, size, ""}); - } - - void add(DataLoader* loader, const std::string& filename) - { - _loadQueue.push({loader, nullptr, 0, filename}); - } - - void loadNext(); - - const std::queue getLoadQueue() const { return _loadQueue; } - - unsigned int getLoaded() const { return _createQueue.size(); } - - unsigned int getIncomplete() const { return _loadQueue.size() + _loading + _createQueue.size(); } - - unsigned int getComplete() const { return _loaded; } - - unsigned int getTotal() const { return getIncomplete() + getComplete(); } - - void flushCreation(); - - void wait(); - -}; - -#endif diff --git a/rwengine/include/loaders/LoaderDFF.hpp b/rwengine/include/loaders/LoaderDFF.hpp index a81b363f..f4267f2a 100644 --- a/rwengine/include/loaders/LoaderDFF.hpp +++ b/rwengine/include/loaders/LoaderDFF.hpp @@ -9,7 +9,7 @@ #include #include -#include +#include class Model; @@ -25,4 +25,28 @@ public: Model* loadFromMemory(char *data, GameData* gameData); }; +#include + +class LoaderIMG; + +class LoadModelJob : public WorkJob +{ +public: + typedef std::function ModelCallback; + +private: + GameData* _gameData; + LoaderIMG* _archive; + std::string _file; + ModelCallback _callback; + char* _data; +public: + + LoadModelJob(WorkContext* context, GameData* gd, LoaderIMG& archive, const std::string& file, ModelCallback cb); + + void work(); + + void complete(); +}; + #endif diff --git a/rwengine/src/WorkContext.cpp b/rwengine/src/WorkContext.cpp new file mode 100644 index 00000000..4d9445ab --- /dev/null +++ b/rwengine/src/WorkContext.cpp @@ -0,0 +1,38 @@ +#include +#include + +void LoadWorker::start() +{ + while( _running ) { + _context->workNext(); + std::this_thread::yield(); + } +} + +void WorkContext::workNext() +{ + WorkJob* j = nullptr; + { + std::lock_guard guard( _inMutex ); + if( _workQueue.empty() ) return; + j = _workQueue.front(); _workQueue.pop(); + } + + j->work(); + + { + std::lock_guard guard( _outMutex ); + _completeQueue.push( j ); + } +} + +void WorkContext::update() +{ + std::lock_guard guard( _outMutex ); + while( ! _completeQueue.empty() ) { + WorkJob* j = _completeQueue.front(); _completeQueue.pop(); + j->complete(); + delete j; + } +} + diff --git a/rwengine/src/loaders/LoadContext.cpp b/rwengine/src/loaders/LoadContext.cpp deleted file mode 100644 index 992389c5..00000000 --- a/rwengine/src/loaders/LoadContext.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#include -#include - -void LoadWorker::start() -{ - while( !_started ) { - std::this_thread::yield(); - } - while( _running ) { - _context->loadNext(); - std::this_thread::yield(); - } -} - - -void LoadContext::loadNext() -{ - // MUTEX - if( _loadQueue.size() == 0 ) { - // UMUTEX - return; - } - _loading++; - auto tl = _loadQueue.front(); - _loadQueue.pop(); - // UMUTEX - - if( tl.data == nullptr ) { - // TODO: instigate opening of the data bytes. - } - - bool result = tl.loader->load(tl.data, tl.size); - - delete tl.data; - - // MUTEX - - if( result ) { - _createQueue.push(tl); - } - else { - // Create is rip - _loaded++; - } - _loading--; - - // UMUTEX -} - -void LoadContext::flushCreation() -{ - // MUTEX - - while( ! _createQueue.empty() ) { - auto tl = _createQueue.front(); - _createQueue.pop(); - - tl.loader->create(); - _loaded++; - } - - // UMUTEX -} - -void LoadContext::wait() -{ - while( _loadQueue.size() > 0 || _loading > 0 ) std::this_thread::yield(); -} - diff --git a/rwengine/src/loaders/LoaderDFF.cpp b/rwengine/src/loaders/LoaderDFF.cpp index df569068..45b70301 100644 --- a/rwengine/src/loaders/LoaderDFF.cpp +++ b/rwengine/src/loaders/LoaderDFF.cpp @@ -311,3 +311,28 @@ RW::BSSectionHeader LoaderDFF::readHeader(char *data, size_t &dataI) { return readStructure(data, dataI); } + +#include + +LoadModelJob::LoadModelJob(WorkContext *context, GameData* gd, LoaderIMG &archive, const std::string &file, ModelCallback cb) + : WorkJob(context), _gameData(gd), _archive(&archive), _file(file), _callback(cb), _data(nullptr) +{ + +} + +void LoadModelJob::work() +{ + _data = _archive->loadToMemory(_file); +} + +void LoadModelJob::complete() +{ + // TODO allow some of the loading to process in a seperate thread. + LoaderDFF loader; + + Model* m = loader.loadFromMemory(_data, _gameData); + + _callback(m); + + delete[] _data; +} diff --git a/tests/test_loader.cpp b/tests/test_loader.cpp deleted file mode 100644 index e2b30a5b..00000000 --- a/tests/test_loader.cpp +++ /dev/null @@ -1,147 +0,0 @@ -#include -#include -#include - - - -class TestLoader : public DataLoader -{ -public: - bool _loaded, _loadResult, _created; - - TestLoader(LoadContext* context, bool result ) - : DataLoader(context), _loaded(false), _loadResult(result), - _created(false) { } - - bool load( const char* bytes, size_t size ) - { std::this_thread::sleep_for(std::chrono::milliseconds(5)); _loaded = true; return _loadResult; } - - void create() { _created = true; } -}; - - -BOOST_AUTO_TEST_SUITE(LoaderTests) - - -BOOST_AUTO_TEST_CASE(test_interface) -{ - { - LoadContext context; - - TestLoader loader( &context, true ); - - BOOST_CHECK_EQUAL( loader.getContext(), &context ); - - // Test that the TestLoader works as expected. - const char* databytes = new const char[1]{ 0x00 }; - - BOOST_CHECK( ! loader._loaded ); - BOOST_CHECK( ! loader._created ); - - BOOST_CHECK( loader.load(databytes, 1) ); - - BOOST_CHECK( loader._loaded ); - BOOST_CHECK( ! loader._created ); - - loader.create(); - - BOOST_CHECK( loader._created ); - } -} - -BOOST_AUTO_TEST_CASE(test_context) -{ - { - LoadContext context; - - TestLoader test(&context, true); - const char* databytes = new const char[1]{ 0x00 }; - - context.add(&test, databytes, 1); - - BOOST_CHECK_EQUAL( context.getIncomplete(), 1 ); - BOOST_CHECK_EQUAL( context.getTotal(), 1 ); - BOOST_CHECK_EQUAL( context.getComplete(), 0 ); - - auto& queue = context.getLoadQueue(); - BOOST_REQUIRE( queue.size() == 1 ); - BOOST_CHECK_EQUAL( queue.front().loader, &test ); - BOOST_CHECK_EQUAL( queue.front().data, databytes ); - BOOST_CHECK_EQUAL( queue.front().size, 1 ); - - // Worker thread tier. - context.loadNext(); - - BOOST_CHECK_EQUAL( context.getIncomplete(), 1 ); - BOOST_CHECK_EQUAL( context.getTotal(), 1 ); - BOOST_CHECK_EQUAL( context.getComplete(), 0 ); - - // Back on main thread - BOOST_CHECK_EQUAL( context.getLoaded(), 1 ); - - context.flushCreation(); - - BOOST_CHECK_EQUAL( context.getIncomplete(), 0 ); - BOOST_CHECK_EQUAL( context.getTotal(), 1 ); - BOOST_CHECK_EQUAL( context.getComplete(), 1 ); - - } -} - -BOOST_AUTO_TEST_CASE(test_thread) -{ - { - LoadContext context; - - LoadWorker worker(&context); - - TestLoader test(&context, true); - const char* databytes = new const char[1]{ 0x00 }; - - context.add(&test, databytes, 1); - - BOOST_CHECK_EQUAL( context.getIncomplete(), 1 ); - BOOST_CHECK_EQUAL( context.getLoaded(), 0 ); - - worker._started = true; - - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - - BOOST_CHECK_EQUAL( context.getLoaded(), 1 ); - BOOST_CHECK_EQUAL( context.getIncomplete(), 1 ); - - context.flushCreation(); - - BOOST_CHECK_EQUAL( context.getLoaded(), 0 ); - BOOST_CHECK_EQUAL( context.getIncomplete(), 0 ); - BOOST_CHECK_EQUAL( context.getComplete(), 1 ); - } - { - LoadContext context; - - LoadWorker worker(&context); - - TestLoader test(&context, true); - const char* databytes = new const char[1]{ 0x00 }; - - context.add(&test, databytes, 1); - - BOOST_CHECK_EQUAL( context.getIncomplete(), 1 ); - BOOST_CHECK_EQUAL( context.getLoaded(), 0 ); - - worker._started = true; - - context.wait(); - - BOOST_CHECK_EQUAL( context.getLoaded(), 1 ); - BOOST_CHECK_EQUAL( context.getIncomplete(), 1 ); - - context.flushCreation(); - - BOOST_CHECK_EQUAL( context.getLoaded(), 0 ); - BOOST_CHECK_EQUAL( context.getIncomplete(), 0 ); - BOOST_CHECK_EQUAL( context.getComplete(), 1 ); - } -} -BOOST_AUTO_TEST_SUITE_END() - diff --git a/tests/test_loaderdff.cpp b/tests/test_loaderdff.cpp index b7eeea11..8f2652ed 100644 --- a/tests/test_loaderdff.cpp +++ b/tests/test_loaderdff.cpp @@ -1,26 +1,60 @@ #include #include "test_globals.hpp" #include +#include BOOST_AUTO_TEST_SUITE(LoaderDFFTests) BOOST_AUTO_TEST_CASE(test_open_dff) { - LoaderIMG loader; + LoaderIMG archive; - BOOST_REQUIRE( loader.load(Global::getGamePath() + "/models/gta3") ); + BOOST_REQUIRE( archive.load(Global::getGamePath() + "/models/gta3") ); + + { + auto d = archive.loadToMemory("landstal.dff"); + + LoaderDFF loader; + + Model* m = loader.loadFromMemory(d, &Global::get().e->gameData); + + BOOST_REQUIRE( m != nullptr ); + + BOOST_CHECK( m->frames.size() > 0 ); + + delete m; + } + +} + +BOOST_AUTO_TEST_CASE(test_modeljob) +{ + LoaderIMG archive; + + BOOST_REQUIRE( archive.load(Global::getGamePath() + "/models/gta3") ); + + { + WorkContext ctx; + + Model* m = nullptr; + bool done = false; + LoadModelJob* lmj = new LoadModelJob(&ctx, &Global::get().e->gameData, archive, "landstal.dff", + [&](Model* model) { m = model; done = true; }); + + ctx.queueJob(lmj); + + while( ! done ) { + ctx.update(); + std::this_thread::yield(); + } + + BOOST_REQUIRE( m != nullptr ); + + BOOST_CHECK( m->frames.size() > 0 ); + + delete m; + } - auto d = loader.loadToMemory("landstal.dff"); - - LoaderDFF dffloader; - Model* m = dffloader.loadFromMemory(d, &Global::get().e->gameData); - - BOOST_REQUIRE( m != nullptr ); - - BOOST_CHECK( m->frames.size() > 0 ); - - delete d; - delete m; } BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/test_worker.cpp b/tests/test_worker.cpp new file mode 100644 index 00000000..b366fe3f --- /dev/null +++ b/tests/test_worker.cpp @@ -0,0 +1,48 @@ +#include +#include + +class TestJob : public WorkJob +{ +public: + bool *_worked, *_completed; + + TestJob( WorkContext* context, bool *w, bool *c ) + : WorkJob(context), _worked(w), _completed(c) + {} + + void work() { *_worked = true; } + + void complete() { *_completed = true; } +}; + + +BOOST_AUTO_TEST_SUITE(WorkTests) + +BOOST_AUTO_TEST_CASE(test_interface) +{ + { + WorkContext context; + + bool worked = false, completed = false; + + TestJob* job = new TestJob(&context, &worked, &completed); + + BOOST_CHECK( ! worked ); + BOOST_CHECK( ! completed ); + + context.queueJob(job); + + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + + BOOST_CHECK( worked ); + BOOST_CHECK( ! completed ); + + context.update();; + + BOOST_CHECK( worked ); + BOOST_CHECK( completed ); + } +} + +BOOST_AUTO_TEST_SUITE_END() +