From 25f28dbb42809a713ade4bd14118830137b04bc0 Mon Sep 17 00:00:00 2001 From: Daniel Evans Date: Thu, 26 Feb 2015 03:57:28 +0000 Subject: [PATCH 01/22] New File handling implementation - move FileHandle into a separate header - Implement FileIndex, a system to normalize filenames and sources --- rwengine/include/core/FileArchive.hpp | 0 rwengine/include/core/FileHandle.hpp | 18 +++ rwengine/include/core/FileIndex.hpp | 50 +++++++ rwengine/include/engine/RWTypes.hpp | 13 -- .../include/loaders/LoaderCutsceneDAT.hpp | 2 +- rwengine/include/loaders/LoaderDFF.hpp | 2 +- rwengine/include/loaders/LoaderGXT.hpp | 2 +- rwengine/src/core/FileIndex.cpp | 130 ++++++++++++++++++ rwengine/src/loaders/LoaderIMG.cpp | 6 + rwengine/tests/test_FileIndex.cpp | 53 +++++++ tests/CMakeLists.txt | 5 +- 11 files changed, 264 insertions(+), 17 deletions(-) create mode 100644 rwengine/include/core/FileArchive.hpp create mode 100644 rwengine/include/core/FileHandle.hpp create mode 100644 rwengine/include/core/FileIndex.hpp create mode 100644 rwengine/src/core/FileIndex.cpp create mode 100644 rwengine/tests/test_FileIndex.cpp diff --git a/rwengine/include/core/FileArchive.hpp b/rwengine/include/core/FileArchive.hpp new file mode 100644 index 00000000..e69de29b diff --git a/rwengine/include/core/FileHandle.hpp b/rwengine/include/core/FileHandle.hpp new file mode 100644 index 00000000..9b5d15c8 --- /dev/null +++ b/rwengine/include/core/FileHandle.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +/** + * @brief Contains a pointer to a file's contents. + */ +struct FileContentsInfo +{ + char* data; + size_t length; + + ~FileContentsInfo() { + delete[] data; + } +}; + +typedef std::shared_ptr FileHandle; diff --git a/rwengine/include/core/FileIndex.hpp b/rwengine/include/core/FileIndex.hpp new file mode 100644 index 00000000..67d7b8e6 --- /dev/null +++ b/rwengine/include/core/FileIndex.hpp @@ -0,0 +1,50 @@ +#pragma once +#include "FileHandle.hpp" + +#include +#include + +class FileIndex +{ +public: + + struct IndexData + { + /// Lowercase identifying filename + std::string filename; + /// Original filename + std::string originalName; + /// Containing directory + std::string directory; + /// The archive filename (if applicable) + std::string archive; + }; + + /** + * Adds the files contained within the given directory to the + * file index. + */ + void indexDirectory(const std::string& directory); + + /** + * Adds the files contained within the given Archive file to the + * file index. + */ + void indexArchive(const std::string& archive); + + /** + * Returns true if the file identified by filename is found within + * the file index. If the file is found, the filedata parameter + * is populated. + */ + bool findFile(const std::string& filename, IndexData& filedata); + + /** + * Returns a FileHandle for the file if it can be found in the + * file index, otherwise an empty FileHandle is returned. + */ + FileHandle openFile(const std::string& filename); + +private: + std::map files; +}; \ No newline at end of file diff --git a/rwengine/include/engine/RWTypes.hpp b/rwengine/include/engine/RWTypes.hpp index ee975629..56af1a11 100644 --- a/rwengine/include/engine/RWTypes.hpp +++ b/rwengine/include/engine/RWTypes.hpp @@ -58,17 +58,4 @@ struct RGBA }; } -/** - * @brief Returned by GameData::loadFile() - */ -struct FileContentsInfo { - char* data; - size_t length; - - ~FileContentsInfo() { - delete[] data; - } -}; -typedef std::shared_ptr FileHandle; - #endif diff --git a/rwengine/include/loaders/LoaderCutsceneDAT.hpp b/rwengine/include/loaders/LoaderCutsceneDAT.hpp index 55fdb36f..54f9e45f 100644 --- a/rwengine/include/loaders/LoaderCutsceneDAT.hpp +++ b/rwengine/include/loaders/LoaderCutsceneDAT.hpp @@ -1,7 +1,7 @@ #pragma once #ifndef _LOADERCUTSCENEDAT_HPP_ #define _LOADERCUTSCENEDAT_HPP_ -#include +#include #include class LoaderCutsceneDAT diff --git a/rwengine/include/loaders/LoaderDFF.hpp b/rwengine/include/loaders/LoaderDFF.hpp index 5ce1804a..faaae83c 100644 --- a/rwengine/include/loaders/LoaderDFF.hpp +++ b/rwengine/include/loaders/LoaderDFF.hpp @@ -10,7 +10,7 @@ #include #include #include -#include +#include class Model; class GameData; diff --git a/rwengine/include/loaders/LoaderGXT.hpp b/rwengine/include/loaders/LoaderGXT.hpp index 519e6e2a..547792a0 100644 --- a/rwengine/include/loaders/LoaderGXT.hpp +++ b/rwengine/include/loaders/LoaderGXT.hpp @@ -1,7 +1,7 @@ #pragma once #ifndef _LOADERGXT_HPP_ #define _LOADERGXT_HPP_ -#include +#include #include class LoaderGXT diff --git a/rwengine/src/core/FileIndex.cpp b/rwengine/src/core/FileIndex.cpp new file mode 100644 index 00000000..281cbe63 --- /dev/null +++ b/rwengine/src/core/FileIndex.cpp @@ -0,0 +1,130 @@ +#include +#include + +#include +#include +#include +#include + +void FileIndex::indexDirectory(const std::string& directory) +{ + DIR* dp = opendir(directory.c_str()); + dirent* ep; + std::string realName, lowerName; + if ( dp == NULL ) { + throw std::runtime_error("Unable to open directory: " + directory); + } + while( (ep = readdir(dp)) ) + { + realName = ep->d_name; + lowerName = realName; + std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), ::tolower); + if ( ep->d_type == DT_REG ) + { + files[ lowerName ] = { + lowerName, + realName, + directory, + "" + }; + } + } +} + +void FileIndex::indexArchive(const std::string& archive) +{ + // Split directory from archive name + auto slash = archive.find_last_of('/'); + auto directory = archive.substr(0, slash); + auto archivebasename = archive.substr(slash+1); + auto archivepath = directory + "/" + archivebasename; + + LoaderIMG img; + + if( ! img.load( archivepath ) ) + { + throw std::runtime_error("Failed to load IMG archive: " + archivepath); + } + + std::string lowerName; + for( size_t i = 0; i < img.getAssetCount(); ++i ) + { + auto& asset = img.getAssetInfoByIndex(i); + + if( asset.size == 0 ) continue; + + lowerName = asset.name; + std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), ::tolower); + + files[ lowerName ] = { + lowerName, + asset.name, + directory, + archivebasename + }; + } +} + +bool FileIndex::findFile(const std::string& filename, FileIndex::IndexData& filedata) +{ + auto iterator = files.find( filename ); + if( iterator == files.end() ) + { + return false; + } + + filedata = iterator->second; + + return true; +} + +FileHandle FileIndex::openFile(const std::string& filename) +{ + auto iterator = files.find( filename ); + if( iterator == files.end() ) + { + return nullptr; + } + + IndexData& f = iterator->second; + bool isArchive = !f.archive.empty(); + + auto fsName = f.directory + "/" + f.originalName; + + char* data = nullptr; + size_t length = 0; + + if( isArchive ) + { + fsName = f.directory + "/" + f.archive; + + LoaderIMG img; + + if( ! img.load(fsName) ) + { + throw std::runtime_error("Failed to load IMG archive: " + fsName); + } + + LoaderIMGFile file; + if( img.findAssetInfo(f.originalName, file) ) + { + length = file.size * 2048; + data = img.loadToMemory(f.originalName); + } + } + else + { + std::ifstream dfile(fsName.c_str()); + if ( ! dfile.is_open()) { + throw std::runtime_error("Unable to open file: " + fsName); + } + + dfile.seekg(0, std::ios_base::end); + size_t length = dfile.tellg(); + dfile.seekg(0); + char *data = new char[length]; + dfile.read(data, length); + } + + return FileHandle( new FileContentsInfo{ data, length } ); +} diff --git a/rwengine/src/loaders/LoaderIMG.cpp b/rwengine/src/loaders/LoaderIMG.cpp index dea9b820..b2f214f6 100644 --- a/rwengine/src/loaders/LoaderIMG.cpp +++ b/rwengine/src/loaders/LoaderIMG.cpp @@ -12,6 +12,12 @@ LoaderIMG::LoaderIMG() bool LoaderIMG::load(const std::string& filename) { std::string dirName = filename; + auto extpos = dirName.find(".img"); + if( extpos != std::string::npos ) + { + dirName.erase(extpos); + } + dirName.append(".dir"); FILE* fp = fopen(dirName.c_str(), "rb"); diff --git a/rwengine/tests/test_FileIndex.cpp b/rwengine/tests/test_FileIndex.cpp new file mode 100644 index 00000000..4596aeea --- /dev/null +++ b/rwengine/tests/test_FileIndex.cpp @@ -0,0 +1,53 @@ +#include +#include +#include + +BOOST_AUTO_TEST_SUITE(FileIndexTests) + +BOOST_AUTO_TEST_CASE(test_index) +{ + FileIndex index; + + index.indexDirectory(Global::getGamePath()+"/data"); + + FileIndex::IndexData data; + BOOST_CHECK( index.findFile("cullzone.dat", data) ); + BOOST_CHECK_EQUAL( data.filename, "cullzone.dat" ); + BOOST_CHECK_EQUAL( data.originalName, "CULLZONE.DAT" ); + BOOST_CHECK( data.archive.empty() ); +} + +BOOST_AUTO_TEST_CASE(test_file) +{ + FileIndex index; + + index.indexDirectory(Global::getGamePath()+"/data"); + + auto handle = index.openFile("cullzone.dat"); + BOOST_CHECK( handle != nullptr ); +} + +BOOST_AUTO_TEST_CASE(test_index_archive) +{ + FileIndex index; + + index.indexArchive(Global::getGamePath()+"/models/gta3.img"); + + FileIndex::IndexData data; + BOOST_CHECK( index.findFile("landstal.dff", data) ); + BOOST_CHECK_EQUAL( data.filename, "landstal.dff" ); + BOOST_CHECK_EQUAL( data.originalName, "landstal.dff" ); + BOOST_CHECK_EQUAL( data.archive, "gta3.img" ); +} + +BOOST_AUTO_TEST_CASE(test_file_archive) +{ + FileIndex index; + + index.indexArchive(Global::getGamePath()+"/models/gta3.img"); + + auto handle = index.openFile("landstal.dff"); + BOOST_CHECK( handle != nullptr ); +} + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8b50c755..274e13e7 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,9 +1,12 @@ -FILE(GLOB TEST_SOURCES "*.cpp") +FILE(GLOB TEST_SOURCES + "*.cpp" + "${CMAKE_SOURCE_DIR}/rwengine/tests/*.cpp") ADD_DEFINITIONS(-DBOOST_TEST_DYN_LINK) add_executable(run_tests ${TEST_SOURCES}) include_directories(include) +include_directories("${CMAKE_SOURCE_DIR}/tests") find_package(Boost COMPONENTS unit_test_framework REQUIRED) From 536b3f9f0c3748185c83ea467b0e086b426ba6c7 Mon Sep 17 00:00:00 2001 From: Daniel Evans Date: Thu, 5 Mar 2015 03:37:13 +0000 Subject: [PATCH 02/22] Add new Logger system. - Supports multiple log recievers. - Onscreen log needs to be re-written. - Replaces GameWorld::logX(). --- rwengine/include/core/Logger.hpp | 63 +++++++++++++++++++++++++++ rwengine/include/engine/GameWorld.hpp | 19 ++------ rwengine/src/core/Logger.cpp | 59 +++++++++++++++++++++++++ rwengine/src/engine/GameData.cpp | 10 +---- rwengine/src/engine/GameWorld.cpp | 22 ++-------- rwengine/src/render/GameRenderer.cpp | 2 +- rwengine/tests/test_Logger.cpp | 38 ++++++++++++++++ rwgame/RWGame.cpp | 11 +++-- 8 files changed, 176 insertions(+), 48 deletions(-) create mode 100644 rwengine/include/core/Logger.hpp create mode 100644 rwengine/src/core/Logger.cpp create mode 100644 rwengine/tests/test_Logger.cpp diff --git a/rwengine/include/core/Logger.hpp b/rwengine/include/core/Logger.hpp new file mode 100644 index 00000000..414aea4c --- /dev/null +++ b/rwengine/include/core/Logger.hpp @@ -0,0 +1,63 @@ +#pragma once + +#include +#include + +/** + * Handles and stores messages from different components + * + * Dispatches recieved messages to logger outputs. + */ +class Logger +{ +public: + enum MessageSeverity + { + Verbose, + Info, + Warning, + Error + }; + + struct LogMessage + { + /// The component that produced the message + std::string component; + /// Severity of the message. + MessageSeverity severity; + /// Logged message + std::string message; + + LogMessage(const std::string& cc, + MessageSeverity ss, + const std::string& mm) + : component(cc), severity(ss), message(mm) { } + }; + + /** + * Interface for handling logged messages. + * + * The Logger class will not clean up allocated MessageRecievers. + */ + struct MessageReciever + { + virtual void messageRecieved(const LogMessage&) = 0; + }; + + void addReciever(MessageReciever* out); + void removeReciever(MessageReciever* out); + + void log(const std::string& component, Logger::MessageSeverity severity, const std::string& message); + + void verbose(const std::string& component, const std::string& message); + void info(const std::string& component, const std::string& message); + void warning(const std::string& component, const std::string& message); + void error(const std::string& component, const std::string& message); +private: + std::vector recievers; +}; + +class StdOutReciever : public Logger::MessageReciever +{ + virtual void messageRecieved(const Logger::LogMessage&); +}; diff --git a/rwengine/include/engine/GameWorld.hpp b/rwengine/include/engine/GameWorld.hpp index 95849269..526a29e6 100644 --- a/rwengine/include/engine/GameWorld.hpp +++ b/rwengine/include/engine/GameWorld.hpp @@ -2,6 +2,8 @@ #ifndef _GAMEWORLD_HPP_ #define _GAMEWORLD_HPP_ +#include + #include #include #include @@ -77,22 +79,7 @@ public: std::string message; }; - std::deque log; - - /** - * Displays an informative message - */ - void logInfo(const std::string& info); - - /** - * Displays an alarming error - */ - void logError(const std::string& error); - - /** - * Displays a comforting warning - */ - void logWarning(const std::string& warning); + Logger logger; /** * Loads an IDE into the game diff --git a/rwengine/src/core/Logger.cpp b/rwengine/src/core/Logger.cpp new file mode 100644 index 00000000..7b8c2f75 --- /dev/null +++ b/rwengine/src/core/Logger.cpp @@ -0,0 +1,59 @@ +#include + +#include +#include +#include + +void Logger::log(const std::string& component, Logger::MessageSeverity severity, const std::string& message) +{ + LogMessage m { component, severity, message }; + + for(MessageReciever* r : recievers) + { + r->messageRecieved( m ); + } +} + +void Logger::addReciever(Logger::MessageReciever* out) +{ + recievers.push_back(out); +} + +void Logger::removeReciever(Logger::MessageReciever* out) +{ + recievers.erase(std::remove(recievers.begin(), recievers.end(), out), recievers.end()); +} + +void Logger::error(const std::string& component, const std::string& message) +{ + log(component, Logger::Error, message); +} + +void Logger::info(const std::string& component, const std::string& message) +{ + log(component, Logger::Info, message); +} + +void Logger::warning(const std::string& component, const std::string& message) +{ + log(component, Logger::Warning, message); +} + +void Logger::verbose(const std::string& component, const std::string& message) +{ + log(component, Logger::Verbose, message); +} + +std::map severityStr { + {Logger::Error, 'E'}, + {Logger::Warning, 'W'}, + {Logger::Info, 'I'}, + {Logger::Verbose, 'V'} +}; + +void StdOutReciever::messageRecieved(const Logger::LogMessage& message) +{ + std::cout << severityStr[message.severity] << " [" << message.component << "] " << message.message << std::endl; +} + + diff --git a/rwengine/src/engine/GameData.cpp b/rwengine/src/engine/GameData.cpp index 02a099c1..6886642b 100644 --- a/rwengine/src/engine/GameData.cpp +++ b/rwengine/src/engine/GameData.cpp @@ -600,10 +600,7 @@ char* GameData::openFile(const std::string& name) } else { - std::stringstream err; - err << "Unable to locate file " << name; - engine->logError(err.str()); - std::cerr << err.str() << std::endl; + engine->logger.error("Data", "Unable to locate file: " + name); } return nullptr; @@ -654,10 +651,7 @@ FileHandle GameData::openFile2(const std::string &name) } else { - std::stringstream err; - err << "Unable to locate file " << name; - engine->logError(err.str()); - std::cerr << err.str() << std::endl; + engine->logger.error("Data", "Unable to locate file: " + name); } return nullptr; } diff --git a/rwengine/src/engine/GameWorld.cpp b/rwengine/src/engine/GameWorld.cpp index 2bcb070b..48ab6f7c 100644 --- a/rwengine/src/engine/GameWorld.cpp +++ b/rwengine/src/engine/GameWorld.cpp @@ -117,22 +117,6 @@ bool GameWorld::load() return true; } -void GameWorld::logInfo(const std::string& info) -{ - log.push_back({LogEntry::Info, gameTime, info}); - std::cout << info << std::endl; -} - -void GameWorld::logError(const std::string& error) -{ - log.push_back({LogEntry::Error, gameTime, error}); -} - -void GameWorld::logWarning(const std::string& warning) -{ - log.push_back({LogEntry::Warning, gameTime, warning}); -} - bool GameWorld::defineItems(const std::string& name) { auto i = gameData.ideLocations.find(name); @@ -185,7 +169,7 @@ void GameWorld::runScript(const std::string &name) script = new ScriptMachine(this, f, opcodes); } else { - logError("Failed to load SCM: " + name); + logger.error("World", "Failed to load SCM: " + name); } } @@ -267,7 +251,7 @@ InstanceObject *GameWorld::createInstance(const uint16_t id, const glm::vec3& po } if( modelname.empty() ) { - logWarning("Instance with missing model: " + std::to_string(id)); + logger.warning("World", "Instance with missing model: " + std::to_string(id)); } auto instance = new InstanceObject( @@ -428,7 +412,7 @@ VehicleObject *GameWorld::createVehicle(const uint16_t id, const glm::vec3& pos, sec = gameData.vehicleColours[palit->second[set].second]; } else { - logWarning("No colour palette for vehicle " + vti->modelName); + logger.warning("World", "No colour palette for vehicle " + vti->modelName); } auto wi = findObjectType(vti->wheelModelID); diff --git a/rwengine/src/render/GameRenderer.cpp b/rwengine/src/render/GameRenderer.cpp index 869fdc1e..9c6e9d99 100644 --- a/rwengine/src/render/GameRenderer.cpp +++ b/rwengine/src/render/GameRenderer.cpp @@ -82,7 +82,7 @@ GameRenderer::GameRenderer(GameWorld* engine) : engine(engine), renderer(new OpenGLRenderer), _renderAlpha(0.f), map(engine, renderer), text(engine, this) { - engine->logInfo("[DRAW] " + renderer->getIDString()); + engine->logger.info("Renderer", renderer->getIDString()); worldProg = renderer->createShader( GameShaders::WorldObject::VertexShader, diff --git a/rwengine/tests/test_Logger.cpp b/rwengine/tests/test_Logger.cpp new file mode 100644 index 00000000..a61c5e95 --- /dev/null +++ b/rwengine/tests/test_Logger.cpp @@ -0,0 +1,38 @@ +#include +#include +#include + +class CallbackReceiver : public Logger::MessageReciever +{ +public: + std::function func; + + CallbackReceiver(std::function func) + : func(func) { } + + virtual void messageRecieved(const Logger::LogMessage& message) + { + func(message); + } +}; + +BOOST_AUTO_TEST_SUITE(LoggerTests) + +BOOST_AUTO_TEST_CASE(test_reciever) +{ + Logger log; + + Logger::LogMessage lastMessage("", Logger::Error, ""); + + CallbackReceiver reciever([&](const Logger::LogMessage& m) { lastMessage = m; }); + + log.addReciever(&reciever); + + log.info("Tests", "Test"); + + BOOST_CHECK_EQUAL( lastMessage.component, "Tests" ); + BOOST_CHECK_EQUAL( lastMessage.severity, Logger::Info ); + BOOST_CHECK_EQUAL( lastMessage.message, "Test" ); +} + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/rwgame/RWGame.cpp b/rwgame/RWGame.cpp index 56b5ed26..9315f6c8 100644 --- a/rwgame/RWGame.cpp +++ b/rwgame/RWGame.cpp @@ -17,6 +17,8 @@ DebugDraw* debug; +StdOutReciever logPrinter; + RWGame::RWGame(const std::string& gamepath, int argc, char* argv[]) : engine(nullptr), inFocus(true), showDebugStats(false), accum(0.f), timescale(1.f) @@ -57,6 +59,7 @@ RWGame::RWGame(const std::string& gamepath, int argc, char* argv[]) glewInit(); engine = new GameWorld(gamepath); + engine->logger.addReciever(&logPrinter); // Initalize all the archives. engine->gameData.loadIMG("/models/gta3"); @@ -88,7 +91,7 @@ RWGame::RWGame(const std::string& gamepath, int argc, char* argv[]) StateManager::get().enter(new LoadingState(this)); - engine->logInfo("Started"); + engine->logger.info("Game", "Started"); } RWGame::~RWGame() @@ -202,7 +205,7 @@ void RWGame::tick(float dt) } catch( SCMException& ex ) { std::cerr << ex.what() << std::endl; - engine->logError( ex.what() ); + engine->logger.error( "Script", ex.what() ); throw; } } @@ -354,7 +357,7 @@ void RWGame::renderDebugStats(float time) ti.size = 15.f; engine->renderer.text.renderText(ti); - while( engine->log.size() > 0 && engine->log.front().time + 10.f < engine->gameTime ) { + /*while( engine->log.size() > 0 && engine->log.front().time + 10.f < engine->gameTime ) { engine->log.pop_front(); } @@ -380,7 +383,7 @@ void RWGame::renderDebugStats(float time) engine->renderer.text.renderText(ti); ti.screenPosition.y -= ti.size; - } + }*/ for( int i = 0; i < engine->state.text.size(); ) { From cf0c37dcc2e26e69d90709a86affbc1ef3c6b388 Mon Sep 17 00:00:00 2001 From: Daniel Evans Date: Thu, 5 Mar 2015 16:36:14 +0000 Subject: [PATCH 03/22] Remove GameRenderer from GameWorld - Particle rendering is broken, since objects can't access renderer --- rwengine/include/engine/GameWorld.hpp | 7 +----- rwengine/src/engine/GameWorld.cpp | 2 +- rwengine/src/items/WeaponItem.cpp | 4 +++- rwengine/src/objects/PickupObject.cpp | 7 +++--- rwengine/src/objects/ProjectileObject.cpp | 5 ++--- rwgame/DrawUI.cpp | 10 ++++----- rwgame/DrawUI.hpp | 3 ++- rwgame/RWGame.cpp | 26 +++++++++++++---------- rwgame/RWGame.hpp | 8 +++++++ rwgame/pausestate.cpp | 2 +- 10 files changed, 42 insertions(+), 32 deletions(-) diff --git a/rwengine/include/engine/GameWorld.hpp b/rwengine/include/engine/GameWorld.hpp index 526a29e6..29ee8304 100644 --- a/rwengine/include/engine/GameWorld.hpp +++ b/rwengine/include/engine/GameWorld.hpp @@ -6,12 +6,12 @@ #include #include -#include #include #include #include