From 4d8dd022d082bb117a770abc9985fdfe83d297e3 Mon Sep 17 00:00:00 2001 From: Manuel Maier Date: Mon, 2 Nov 2015 16:37:29 +0000 Subject: [PATCH 01/23] Add GameLoop class. Also util to get the current working dir. --- build/CMake/platforms/Windows.cmake | 46 ++-- build/CMake/targets/krepel.cmake | 14 ++ code/CMakeLists.txt | 1 + code/krEngine/CMakeLists.txt | 2 +- code/krEngine/common/implementation/utils.cpp | 7 + .../common/implementation/utils_Windows.inl | 21 ++ code/krEngine/common/utils.h | 7 + code/krEngine/game.h | 4 + code/krEngine/game/gameLoop.h | 78 ++++++ .../krEngine/game/implementation/gameLoop.cpp | 113 +++++++++ code/krGameLauncher/CMakeLists.txt | 25 ++ code/krGameLauncher/main.cpp | 108 +++++++++ code/krGameLauncher/pch.h | 3 + ezEngine.natvis | 223 ++++++++++++++++++ tests/CMakeLists.txt | 1 + tests/krEngineTests/CMakeLists.txt | 13 +- tests/krEngineTests/test_game.cpp | 69 ++++++ tests/krGameTest1/CMakeLists.txt | 19 ++ tests/krGameTest1/game.cpp | 6 + tests/krGameTest1/pch.h | 3 + 20 files changed, 723 insertions(+), 40 deletions(-) create mode 100644 code/krEngine/common/implementation/utils.cpp create mode 100644 code/krEngine/common/implementation/utils_Windows.inl create mode 100644 code/krEngine/game.h create mode 100644 code/krEngine/game/gameLoop.h create mode 100644 code/krEngine/game/implementation/gameLoop.cpp create mode 100644 code/krGameLauncher/CMakeLists.txt create mode 100644 code/krGameLauncher/main.cpp create mode 100644 code/krGameLauncher/pch.h create mode 100644 ezEngine.natvis create mode 100644 tests/krEngineTests/test_game.cpp create mode 100644 tests/krGameTest1/CMakeLists.txt create mode 100644 tests/krGameTest1/game.cpp create mode 100644 tests/krGameTest1/pch.h diff --git a/build/CMake/platforms/Windows.cmake b/build/CMake/platforms/Windows.cmake index b2782f1..88be907 100644 --- a/build/CMake/platforms/Windows.cmake +++ b/build/CMake/platforms/Windows.cmake @@ -1,37 +1,29 @@ # Windows specific settings. if(MSVC) - # Enable multi-threaded compilation. - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") + add_compile_options( + # Enable multi-threaded compilation. + #"/MP" - # Treat warnings as errors. - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /WX") + # Treat warnings as errors. + "/WX" - # Disable RTTI. - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /GR-") + # Disable RTTI. + "/GR-" - # Use fast floating point model. - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /fp:fast") + # Use fast floating point model. + "/fp:fast" - # Enable floating point exceptions. - #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /fp:except") + # Enable floating point exceptions. + #"/fp:except" - # Enable debugging local variables in optimized code. - set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /Zo") + # Enable debugging local variables in optimized code. + $<$:"/Zo"> - - # Enable debugging local variables in optimized code. - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /Zo") - - set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /INCREMENTAL:NO") - set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /INCREMENTAL:NO") - set(CMAKE_STATIC_LINKER_FLAGS_RELEASE "${CMAKE_STATIC_LINKER_FLAGS_RELEASE} /INCREMENTAL:NO") - - set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /OPT:REF") - set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /OPT:REF") - set(CMAKE_STATIC_LINKER_FLAGS_RELEASE "${CMAKE_STATIC_LINKER_FLAGS_RELEASE} /OPT:REF") - - set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /OPT:ICF") - set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /OPT:ICF") - set(CMAKE_STATIC_LINKER_FLAGS_RELEASE "${CMAKE_STATIC_LINKER_FLAGS_RELEASE} /OPT:ICF") + $<$:"/INCREMENTAL:NO"> + $<$:"/OPT:REF"> + $<$:"/OPT:ICF"> + ) +else() + message(WARNING "Unknown compiler on Windows.") endif() diff --git a/build/CMake/targets/krepel.cmake b/build/CMake/targets/krepel.cmake index 1008afe..71878e5 100644 --- a/build/CMake/targets/krepel.cmake +++ b/build/CMake/targets/krepel.cmake @@ -14,3 +14,17 @@ set_property(TARGET IMPORTED_krEngine PROPERTY IMPORTED_IMPLIB_RELWITHDEBINFO add_library(krEngine INTERFACE) target_link_libraries(krEngine INTERFACE IMPORTED_krEngine glew;ezThirdParty;ezFoundation;ezCore;ezCoreUtils;ezSystem;glu32;opengl32) target_include_directories(krEngine INTERFACE "${KREPEL_DIR}/code") + +# Target: krGameLauncher +add_library(IMPORTED_krGameLauncher SHARED IMPORTED GLOBAL) +set_property(TARGET IMPORTED_krGameLauncher PROPERTY IMPORTED_LOCATION_DEBUG "${KREPEL_DIR}/bin/krGameLauncher-debug.dll") +set_property(TARGET IMPORTED_krGameLauncher PROPERTY IMPORTED_IMPLIB_DEBUG "${KREPEL_DIR}/lib/krGameLauncher-debug.lib") +set_property(TARGET IMPORTED_krGameLauncher PROPERTY IMPORTED_LOCATION_RELEASE "${KREPEL_DIR}/bin/krGameLauncher.dll") +set_property(TARGET IMPORTED_krGameLauncher PROPERTY IMPORTED_IMPLIB_RELEASE "${KREPEL_DIR}/lib/krGameLauncher.lib") +set_property(TARGET IMPORTED_krGameLauncher PROPERTY IMPORTED_LOCATION_MINSIZEREL "${KREPEL_DIR}/bin/krGameLauncher-minsize.dll") +set_property(TARGET IMPORTED_krGameLauncher PROPERTY IMPORTED_IMPLIB_MINSIZEREL "${KREPEL_DIR}/lib/krGameLauncher-minsize.lib") +set_property(TARGET IMPORTED_krGameLauncher PROPERTY IMPORTED_LOCATION_RELWITHDEBINFO "${KREPEL_DIR}/bin/krGameLauncher-reldeb.dll") +set_property(TARGET IMPORTED_krGameLauncher PROPERTY IMPORTED_IMPLIB_RELWITHDEBINFO "${KREPEL_DIR}/lib/krGameLauncher-reldeb.lib") +add_library(krGameLauncher INTERFACE) +target_link_libraries(krGameLauncher INTERFACE IMPORTED_krGameLauncher krEngine) +target_include_directories(krGameLauncher INTERFACE "${KREPEL_DIR}/code") diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index ffd71cd..55c776a 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -1 +1,2 @@ add_subdirectory("krEngine") +add_subdirectory("krGameLauncher") diff --git a/code/krEngine/CMakeLists.txt b/code/krEngine/CMakeLists.txt index 01cc666..6a50565 100755 --- a/code/krEngine/CMakeLists.txt +++ b/code/krEngine/CMakeLists.txt @@ -7,7 +7,7 @@ file(GLOB_RECURSE SOURCES *.h *.inl *.cpp) # Target Setup # ============ -add_library(krEngine SHARED ${SOURCES}) +add_library(krEngine SHARED ${SOURCES} "${KREPEL_DIR}/ezEngine.natvis") set_target_properties(krEngine PROPERTIES FOLDER engine DEFINE_SYMBOL KR_ENGINE_DLL_EXPORT) diff --git a/code/krEngine/common/implementation/utils.cpp b/code/krEngine/common/implementation/utils.cpp new file mode 100644 index 0000000..30ea0cb --- /dev/null +++ b/code/krEngine/common/implementation/utils.cpp @@ -0,0 +1,7 @@ +#include + +#if EZ_ENABLED(EZ_PLATFORM_WINDOWS) + #include "utils_Windows.inl" +#else + #error common/utils not implemented for this platform. +#endif diff --git a/code/krEngine/common/implementation/utils_Windows.inl b/code/krEngine/common/implementation/utils_Windows.inl new file mode 100644 index 0000000..5779a9e --- /dev/null +++ b/code/krEngine/common/implementation/utils_Windows.inl @@ -0,0 +1,21 @@ + +static ezStringBuilder cwdHelper() +{ + auto bufferSize = GetCurrentDirectory(0, nullptr); + auto buffer = new TCHAR[bufferSize]; + KR_ON_SCOPE_EXIT{ delete[] buffer; }; + if(GetCurrentDirectory(bufferSize, buffer) != bufferSize) + { + ezLog::Error("Error calling GetCurrentDirectory."); + return ezStringBuilder(); + } + ezStringBuilder path(buffer); + path.MakeCleanPath(); + return path; +} + +ezStringView kr::cwd() +{ + static ezStringBuilder path = cwdHelper(); + return path; +} diff --git a/code/krEngine/common/utils.h b/code/krEngine/common/utils.h index ded03ec..54ed76a 100755 --- a/code/krEngine/common/utils.h +++ b/code/krEngine/common/utils.h @@ -28,3 +28,10 @@ namespace krInternal /// \note Don't forget the trailing semicolon ';'! /// \example KR_ON_SCOPE_EXIT{ printf("Leaving scope...\n"); }; #define KR_ON_SCOPE_EXIT ::krInternal::OnScopeExit EZ_CONCAT(_onScopeExit_, EZ_SOURCE_LINE) = [&] + +namespace kr +{ + /// \brief Return the current working directory. + /// \return The path as a string view. Guaranteed to be null-terminated. + KR_ENGINE_API ezStringView cwd(); +} diff --git a/code/krEngine/game.h b/code/krEngine/game.h new file mode 100644 index 0000000..a3d9d3f --- /dev/null +++ b/code/krEngine/game.h @@ -0,0 +1,4 @@ +#pragma once + +//#include +#include diff --git a/code/krEngine/game/gameLoop.h b/code/krEngine/game/gameLoop.h new file mode 100644 index 0000000..5c4c33a --- /dev/null +++ b/code/krEngine/game/gameLoop.h @@ -0,0 +1,78 @@ +#pragma once + +namespace kr +{ + struct LoopListener; + + class KR_ENGINE_API GameLoop + { + public: // *** Built-In Channels + // These are created lazily. + + static GameLoop* preMainChannel(); + static GameLoop* mainChannel(); + static GameLoop* postMainChannel(); + + static GameLoop* preRenderingChannel(); + static GameLoop* renderingChannel(); + static GameLoop* postRenderingChannel(); + + public: // *** Construction + GameLoop() = default; + GameLoop(ezStringView name) : m_name{ name } {} + + public: // *** Runtime + + /// \brief Add a named callback function to this game loop. + /// + /// Will not overwrite an existing callback with the name \p callbackName. + /// + /// \return Returns \c EZ_SUCCESS if no other callback with the same name existed yet, else \c EZ_FAILURE. + /// \see overwriteCallback() + ezResult addCallback(ezStringView callbackName, ezDelegate callback); + + /// \brief Overwrite or add a named callback function to this game loop. + /// + /// Will add the callback if it didn't exist yet. + /// + /// \see addCallback() + void updateCallback(ezStringView callbackName, ezDelegate callback); + + /// \brief Remove a named callback from this game loop. + /// + /// If the callback existed, will return EZ_SUCCESS. + /// Otherwise EZ_FAILURE is returned and nothing else is done. + ezResult removeCallback(ezStringView callbackName); + + void tick(ezLogInterface* pLogInterface = nullptr); + + void setName(ezStringView name) { m_name = name; } + ezStringView name() const { return m_name; } + + private: // *** Internal + struct Callback + { + ezString name; + ezDelegate func; + }; + + Callback* getCallback(ezStringView name); + + private: // *** Data + ezString m_name{ "" }; + ezDynamicArray m_callbacks; + }; + + class KR_ENGINE_API GlobalGameLoopRegistry + { + GlobalGameLoopRegistry() = delete; + public: + + static ezResult add(GameLoop* pLoop); + static ezResult remove(GameLoop* pLoop); + + // TODO Dependency management. + + static void tick(); + }; +} diff --git a/code/krEngine/game/implementation/gameLoop.cpp b/code/krEngine/game/implementation/gameLoop.cpp new file mode 100644 index 0000000..176fc65 --- /dev/null +++ b/code/krEngine/game/implementation/gameLoop.cpp @@ -0,0 +1,113 @@ +#include + +// Built-In Channels +kr::GameLoop* kr::GameLoop::preMainChannel() +{ + static GameLoop loop("preMain"); + return &loop; +} + +kr::GameLoop* kr::GameLoop::mainChannel() +{ + static GameLoop loop("main"); + return &loop; +} + +kr::GameLoop* kr::GameLoop::postMainChannel() +{ + static GameLoop loop("postMain"); + return &loop; +} + +kr::GameLoop* kr::GameLoop::preRenderingChannel() +{ + static GameLoop loop("preRendering"); + return &loop; +} + +kr::GameLoop* kr::GameLoop::renderingChannel() +{ + static GameLoop loop("rendering"); + return &loop; +} + +kr::GameLoop* kr::GameLoop::postRenderingChannel() +{ + static GameLoop loop("postRendering"); + return &loop; +} + +ezResult kr::GameLoop::addCallback(ezStringView callbackName, ezDelegate callback) +{ + EZ_LOG_BLOCK("kr::GameLoop::addCallback", m_name); + + auto pCallback{ getCallback(callbackName) }; + if(pCallback != nullptr) + { + ezLog::Warning("Callback named '%s' already exists.", ezStringBuilder(callbackName).GetData()); + return EZ_FAILURE; + } + + pCallback = &m_callbacks.ExpandAndGetRef(); + pCallback->name = callbackName; + pCallback->func = callback; + + return EZ_SUCCESS; +} + +void kr::GameLoop::updateCallback(ezStringView callbackName, ezDelegate callback) +{ + EZ_LOG_BLOCK("kr::GameLoop::updateCallback", m_name); + + auto pCallback{ getCallback(callbackName) }; + if(pCallback == nullptr) + { + pCallback = &m_callbacks.ExpandAndGetRef(); + } + + pCallback->name = callbackName; + pCallback->func = callback; +} + +ezResult kr::GameLoop::removeCallback(ezStringView callbackName) +{ + EZ_LOG_BLOCK("kr::GameLoop::removeCallback", m_name); + + auto count{ m_callbacks.GetCount() }; + for(ezUInt32 i = 0; i < count; i++) + { + if(m_callbacks[i].name == callbackName) + { + m_callbacks.RemoveAt(i); + return EZ_SUCCESS; + } + } + + ezLog::Error("No callback named '%s'.", ezStringBuilder(callbackName).GetData()); + return EZ_FAILURE; +} + +void kr::GameLoop::tick(ezLogInterface* pLogInterface) +{ + EZ_LOG_BLOCK(pLogInterface, "Ticking GameLoop", m_name); + for(auto& callback : m_callbacks) + { + ezStringBuilder callbackName{ callback.name }; + EZ_LOG_BLOCK(pLogInterface, "Ticking Callback", callbackName); + + callback.func(); + } +} + +kr::GameLoop::Callback* kr::GameLoop::getCallback(ezStringView name) +{ + for(auto& cb : m_callbacks) + { + if(cb.name == name) + { + return &cb; + } + } + + return nullptr; +} diff --git a/code/krGameLauncher/CMakeLists.txt b/code/krGameLauncher/CMakeLists.txt new file mode 100644 index 0000000..4b88fa3 --- /dev/null +++ b/code/krGameLauncher/CMakeLists.txt @@ -0,0 +1,25 @@ +include(kr_set_pch) +include(kr_mirror_source_tree) + +# Source Files +# ============ +file(GLOB_RECURSE SOURCES *.h *.inl *.cpp) + +# Target Setup +# ============ +add_executable(krGameLauncher ${SOURCES}) +set_target_properties(krGameLauncher PROPERTIES + FOLDER engine) +target_include_directories(krGameLauncher PUBLIC ..) +kr_set_pch(krGameLauncher "pch.h") +kr_mirror_source_tree("${CMAKE_CURRENT_LIST_DIR}" ${SOURCES}) + +# Dependencies +# ============ + +target_link_libraries(krGameLauncher krEngine) + +# Install Target +# ============== +include(kr_install_target) +kr_install_target(krGameLauncher) diff --git a/code/krGameLauncher/main.cpp b/code/krGameLauncher/main.cpp new file mode 100644 index 0000000..f4036aa --- /dev/null +++ b/code/krGameLauncher/main.cpp @@ -0,0 +1,108 @@ + +#include +#include +#include +#include +#include + +namespace +{ + struct JsonFileReader + { + ezFileReader file; + ezJSONReader reader; + }; +} + +static ezStringBuilder extractValue(const ezVariantDictionary& data, const char* key) +{ + ezVariant value; + if(!data.TryGetValue(key, value)) + { + ezLog::Error("Unable to find key '%s'.", key); + return ezStringBuilder(); + } + + if(value.GetType() != ezVariantType::String) + { + ezLog::Error("Expected a string value for the key '%s'.", key); + return ezStringBuilder(); + } + + return ezStringBuilder(value.Get()); +} + +static ezStringBuilder extractGameName(const ezCommandLineUtils& cmd, const JsonFileReader& json) +{ + const char* const key = "game"; + ezStringBuilder parentPath; + ezStringBuilder gamePath = cmd.GetStringOption(key); + + if(gamePath.IsEmpty()) + { + // Game path was not specified via command line switch. + parentPath = json.file.GetFilePathAbsolute(); + parentPath.PathParentDirectory(); + gamePath = extractValue(json.reader.GetTopLevelObject(), key); + } + else + { + // Game path specified via command line. + parentPath = kr::cwd(); + } + + parentPath.AppendPath(gamePath); + return parentPath; +} + +static ezResult loadGame(const ezStringBuilder& path) +{ + EZ_LOG_BLOCK("Loading Game", path); + + return ezPlugin::LoadPlugin(path); +} + +int main(int argc, char* argv[]) +{ + ezStringBuilder hello("Hello World"); + + ezGlobalLog::AddLogWriter(ezLogWriter::Console::LogMessageHandler); + KR_ON_SCOPE_EXIT{ ezGlobalLog::RemoveLogWriter(ezLogWriter::Console::LogMessageHandler); }; + + ezGlobalLog::AddLogWriter(ezLogWriter::VisualStudio::LogMessageHandler); + KR_ON_SCOPE_EXIT{ ezGlobalLog::RemoveLogWriter(ezLogWriter::VisualStudio::LogMessageHandler); }; + + ezStartup::StartupCore(); + KR_ON_SCOPE_EXIT{ ezStartup::ShutdownCore(); }; + + ezFileSystem::RegisterDataDirectoryFactory(ezDataDirectory::FolderType::Factory); + ezFileSystem::AddDataDirectory(kr::cwd().GetData(), ezFileSystem::DataDirUsage::AllowWrites, "cwd", "cwd"); + KR_ON_SCOPE_EXIT{ ezFileSystem::RemoveDataDirectoryGroup("cwd"); }; + + ezCommandLineUtils cmd; + cmd.SetCommandLine(argc, const_cast(argv)); + + ezStringBuilder configFileName = cmd.GetStringOption("--config"); + + if(configFileName.IsEmpty()) + { + configFileName = "config.json"; + } + configFileName.Prepend(""); + + JsonFileReader json; + EZ_VERIFY(json.file.Open(configFileName).Succeeded(), "Failed to open config file."); + json.reader.Parse(json.file); + + auto gameName = extractGameName(cmd, json); + if(gameName.IsEmpty()) + { + ezLog::Error("No game path specified."); + return 1; + } + + // Finally load the game. + loadGame(gameName); + + return 0; +} diff --git a/code/krGameLauncher/pch.h b/code/krGameLauncher/pch.h new file mode 100644 index 0000000..327ad9e --- /dev/null +++ b/code/krGameLauncher/pch.h @@ -0,0 +1,3 @@ +#pragma once + +#include diff --git a/ezEngine.natvis b/ezEngine.natvis new file mode 100644 index 0000000..63e350d --- /dev/null +++ b/ezEngine.natvis @@ -0,0 +1,223 @@ + + + + + + + {m_Data.m_pElements,s8} + m_Data.m_pElements,s8 + + m_Data.m_pElements,s8 + m_uiCharacterCount + m_Data.m_uiCount + + + + + {m_pStart,[m_pEnd - m_pStart]s8} + <Invalid String> + m_pStart,[m_pEnd - m_pStart]s8 + + m_pStart,[m_pEnd - m_pStart]s8 + m_pEnd - m_pStart + m_pStart != 0 + m_pEnd[0] == 0 + + + + + + <Invalid Iterator> + {m_pElement,[1]s8} + {m_pElement,[2]s8} + {m_pElement,[3]s8} + {m_pElement,[4]s8} + + + + {m_Data.m_pElement->m_Key} + + m_Data.m_pElement->m_Value.m_iRefCount + m_Data.m_pElement->m_Value.m_uiHash,x + + + + + + {{ count={m_uiCount} }} + + m_uiCount + m_uiCapacity + + m_uiCount + m_pElements + + + + + + {{ count={m_uiCount} }} + + m_uiCount + m_uiCapacity + + m_uiCapacity + m_pEntries + + + + + + {{ count={m_uiCount} }} + + m_uiCount + + m_uiCount + m_First.m_pNext + m_pNext + m_Data + + + + + + {{ count={m_uiCount} }} + + m_uiCount + + m_uiCount + m_pChunks[(m_uiFirstElement + $i) / m_uiChunkSize][(m_uiFirstElement + $i) % m_uiChunkSize] + + + + + + + {{ count={m_uiCount} }} + + m_uiCount + + m_uiCount + m_pRoot + m_pLink[0] + m_pLink[1] + *this + + + + + + {{ count={m_uiCount} }} + + m_uiCount + m_uiFirstElement + + m_uiCount + m_pElements + + + + + + {{ count={m_uiCount} }} + + m_uiCount + + m_uiCount + m_ptr + + + + + + + + (Type::Enum) m_Type + *reinterpret_cast<bool*>(&m_Data) + *reinterpret_cast<ezInt8*>(&m_Data) + *reinterpret_cast<ezUInt8*>(&m_Data) + *reinterpret_cast<ezInt16*>(&m_Data) + *reinterpret_cast<ezUInt16*>(&m_Data) + *reinterpret_cast<ezInt32*>(&m_Data) + *reinterpret_cast<ezUInt32*>(&m_Data) + *reinterpret_cast<ezInt64*>(&m_Data) + *reinterpret_cast<ezUInt64*>(&m_Data) + *reinterpret_cast<float*>(&m_Data) + *reinterpret_cast<double*>(&m_Data) + *reinterpret_cast<ezColor*>(&m_Data) + *reinterpret_cast<ezVec2Template<float>*>(&m_Data) + *reinterpret_cast<ezVec3Template<float>*>(&m_Data) + *reinterpret_cast<ezVec4Template<float>*>(&m_Data) + *reinterpret_cast<ezQuatTemplate<float>*>(&m_Data) + *reinterpret_cast<ezTime*>(&m_Data) + *reinterpret_cast<ezUuid*>(&m_Data) + *reinterpret_cast<void**>(&m_Data) + *reinterpret_cast<ezReflectedClass**>(&m_Data) + *reinterpret_cast<ezMat3Template<float>*>(m_Data.shared->m_Ptr) + *reinterpret_cast<ezMat4Template<float>*>(m_Data.shared->m_Ptr) + *reinterpret_cast<ezHybridStringBase<32>*>(m_Data.shared->m_Ptr) + *reinterpret_cast<ezDynamicArray<ezVariant,ezDefaultAllocatorWrapper>*>(m_Data.shared->m_Ptr) + *reinterpret_cast<ezHashTable<ezHybridString<32,ezDefaultAllocatorWrapper>,ezVariant,ezHashHelper<ezHybridString<32,ezDefaultAllocatorWrapper> >,ezDefaultAllocatorWrapper>*>(m_Data.shared->m_Ptr) + m_bIsShared != 0 + + + + + {($T1::Enum)m_value} + + + + {($T1::Enum)m_Value} + + + + {{ x={x}, y={y} }} + + + + {{ x={x}, y={y}, z={z} }} + + + + {{ x={x}, y={y}, z={z}, w={w} }} + + + + {{ x={v.x}, y={v.y}, z={v.z}, w={w} }} + + + + {{ nx={m_vNormal.x}, ny={m_vNormal.y}, nz={m_vNormal.z}, negDist={m_fNegDistance} }} + + + + {{ r={r}, g={g}, b={b}, a={a} }} + + + + {{seconds = {m_fTime} }} + + m_fTime + m_fTime * 1000.0 + m_fTime * 1000000.0 + m_fTime * 1000000000.0 + + + + + + &m_fElementsCM[0],3 + &m_fElementsCM[3],3 + &m_fElementsCM[6],3 + + + + + + &m_fElementsCM[0],4 + &m_fElementsCM[4],4 + &m_fElementsCM[8],4 + &m_fElementsCM[12],4 + + + diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 24e2d24..548e7ec 100755 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1 +1,2 @@ add_subdirectory("krEngineTests") +add_subdirectory("krGameTest1") diff --git a/tests/krEngineTests/CMakeLists.txt b/tests/krEngineTests/CMakeLists.txt index a746807..9a46ce5 100755 --- a/tests/krEngineTests/CMakeLists.txt +++ b/tests/krEngineTests/CMakeLists.txt @@ -15,18 +15,7 @@ target_include_directories(krEngineTests PUBLIC ..) kr_set_pch(krEngineTests "pch.h") kr_mirror_source_tree("${CMAKE_CURRENT_LIST_DIR}" ${SOURCES}) -# Dependencies -# ============ - -find_package(OpenGL REQUIRED) -if(OPENGL_INCLUDE_DIR) - # Needed on non-windows platforms (I think). - target_include_directories(krEngineTests PUBLIC ${OPENGL_INCLUDE_DIR}) -endif() - -target_link_libraries(krEngineTests - krEngine - ${OPENGL_LIBRARIES}) +target_link_libraries(krEngineTests krEngine) # Special Compile Options # ======================= diff --git a/tests/krEngineTests/test_game.cpp b/tests/krEngineTests/test_game.cpp new file mode 100644 index 0000000..aa4baf5 --- /dev/null +++ b/tests/krEngineTests/test_game.cpp @@ -0,0 +1,69 @@ +#include +#include + +#include + +TEST_CASE("Game Loop", "[game]") +{ + using namespace kr; + + SECTION("Ticking an empty main loop") + { + GameLoop loop; + loop.tick(); + } + + SECTION("Single channel usage") + { + GameLoop loop; + int check = 0; + + REQUIRE(loop.addCallback("foo", [&]() { ++check; }).Succeeded()); + REQUIRE(check == 0); + + loop.tick(); + REQUIRE(check == 1); + + loop.tick(); + loop.tick(); + REQUIRE(check == 3); + + REQUIRE(loop.removeCallback("foo").Succeeded()); + REQUIRE(check == 3); + loop.tick(); + REQUIRE(check == 3); + } + + SECTION("Multi-channel usage") + { + GameLoop loop; + int checkFoo = 0; + int checkBar = 0; + + auto cbBoth = [&]() { ++checkFoo; ++checkBar; }; + auto cbFoo = [&]() { ++checkFoo; }; + + REQUIRE(loop.addCallback("fooAndBar", cbBoth).Succeeded()); + REQUIRE(loop.addCallback("foo", cbFoo).Succeeded()); + + loop.tick(); + REQUIRE(checkFoo == 2); + REQUIRE(checkBar == 1); + + loop.tick(); + loop.tick(); + REQUIRE(checkFoo == 6); + REQUIRE(checkBar == 3); + + REQUIRE(loop.removeCallback("fooAndBar").Succeeded()); + loop.tick(); + REQUIRE(checkFoo == 7); + REQUIRE(checkBar == 3); + + REQUIRE(loop.addCallback("fooAndBar", cbBoth).Succeeded()); + REQUIRE(loop.addCallback("fooAndBar", cbBoth).Failed()); + loop.tick(); + REQUIRE(checkFoo == 9); + REQUIRE(checkBar == 4); + } +} diff --git a/tests/krGameTest1/CMakeLists.txt b/tests/krGameTest1/CMakeLists.txt new file mode 100644 index 0000000..366e0db --- /dev/null +++ b/tests/krGameTest1/CMakeLists.txt @@ -0,0 +1,19 @@ +include(kr_set_pch) +include(kr_mirror_source_tree) + +# Source Files +# ============ +file(GLOB_RECURSE SOURCES *.h *.inl *.cpp) + +# Target Setup +# ============ +add_library(krGameTest1 SHARED ${SOURCES}) +set_target_properties(krGameTest1 PROPERTIES + FOLDER tests) +target_include_directories(krGameTest1 PUBLIC ../../code) +target_include_directories(krGameTest1 PUBLIC ..) +kr_set_pch(krGameTest1 "pch.h") +kr_mirror_source_tree("${CMAKE_CURRENT_LIST_DIR}" ${SOURCES}) + +target_link_libraries(krGameTest1 krEngine) +add_dependencies(krGameTest1 krGameLauncher) diff --git a/tests/krGameTest1/game.cpp b/tests/krGameTest1/game.cpp new file mode 100644 index 0000000..2a4587b --- /dev/null +++ b/tests/krGameTest1/game.cpp @@ -0,0 +1,6 @@ + +class GameTest1 : public kr::GameApplication +{ +}; + +KR_DEFINE_GAME_MODULE(); diff --git a/tests/krGameTest1/pch.h b/tests/krGameTest1/pch.h new file mode 100644 index 0000000..d44b223 --- /dev/null +++ b/tests/krGameTest1/pch.h @@ -0,0 +1,3 @@ + +#include +#include From c042611b50daf703a7703685cb40581a1afdab7c Mon Sep 17 00:00:00 2001 From: Manuel Maier Date: Tue, 3 Nov 2015 11:59:42 +0000 Subject: [PATCH 02/23] Implement basics of GlobalGameLoopRegistry. No dependency management yet. --- code/krEngine/game/gameLoop.h | 43 ++--- .../krEngine/game/implementation/gameLoop.cpp | 154 +++++++++++++----- tests/krEngineTests/test_game.cpp | 25 +++ 3 files changed, 158 insertions(+), 64 deletions(-) diff --git a/code/krEngine/game/gameLoop.h b/code/krEngine/game/gameLoop.h index 5c4c33a..ceb709d 100644 --- a/code/krEngine/game/gameLoop.h +++ b/code/krEngine/game/gameLoop.h @@ -6,20 +6,8 @@ namespace kr class KR_ENGINE_API GameLoop { - public: // *** Built-In Channels - // These are created lazily. - - static GameLoop* preMainChannel(); - static GameLoop* mainChannel(); - static GameLoop* postMainChannel(); - - static GameLoop* preRenderingChannel(); - static GameLoop* renderingChannel(); - static GameLoop* postRenderingChannel(); - public: // *** Construction GameLoop() = default; - GameLoop(ezStringView name) : m_name{ name } {} public: // *** Runtime @@ -46,9 +34,6 @@ namespace kr void tick(ezLogInterface* pLogInterface = nullptr); - void setName(ezStringView name) { m_name = name; } - ezStringView name() const { return m_name; } - private: // *** Internal struct Callback { @@ -59,20 +44,38 @@ namespace kr Callback* getCallback(ezStringView name); private: // *** Data - ezString m_name{ "" }; ezDynamicArray m_callbacks; }; + /// \brief Static class to register game loops with a name. + /// + /// \todo Dependency management. Something like \c depend(GameLoop* pLoop, GameLoop* pDependency);. Fail for cyclic deps. class KR_ENGINE_API GlobalGameLoopRegistry { GlobalGameLoopRegistry() = delete; public: - static ezResult add(GameLoop* pLoop); - static ezResult remove(GameLoop* pLoop); + /// \brief Add a named gameloop to the global gameloop registry. + /// \return \c EZ_SUCCESS if the names gameloop did not exist yet, \c EZ_FAILURE otherwise. + static ezResult add(ezStringView name, GameLoop* pLoop, ezLogInterface* pLogInterface = nullptr); + + /// \brief Get a registered gameloop instance by name. + /// \return \c nullptr if the named gameloop does not exist. + static GameLoop* get(ezStringView name, ezLogInterface* pLogInterface = nullptr); + + /// \brief Remove a registered gameloop by pointer. + /// + /// You can also remove a registered gameloop by name with the overloaded version of remove(). + /// + /// \return \c EZ_SUCCESS if the gameloop could successfully be removed, \c EZ_FAILURE if \p pLoop is null or not registered. + /// + /// \see remove() + static ezResult remove(GameLoop* pLoop, ezLogInterface* pLogInterface = nullptr); - // TODO Dependency management. + /// \brief Remove a registered gameloop by name. + /// \see remove() + static ezResult remove(ezStringView name, ezLogInterface* pLogInterface = nullptr) { return remove(get(name, pLogInterface), pLogInterface); } - static void tick(); + static void tick(ezLogInterface* pLogInterface = nullptr); }; } diff --git a/code/krEngine/game/implementation/gameLoop.cpp b/code/krEngine/game/implementation/gameLoop.cpp index 176fc65..866bf5c 100644 --- a/code/krEngine/game/implementation/gameLoop.cpp +++ b/code/krEngine/game/implementation/gameLoop.cpp @@ -1,53 +1,18 @@ #include -// Built-In Channels -kr::GameLoop* kr::GameLoop::preMainChannel() -{ - static GameLoop loop("preMain"); - return &loop; -} - -kr::GameLoop* kr::GameLoop::mainChannel() -{ - static GameLoop loop("main"); - return &loop; -} - -kr::GameLoop* kr::GameLoop::postMainChannel() -{ - static GameLoop loop("postMain"); - return &loop; -} - -kr::GameLoop* kr::GameLoop::preRenderingChannel() -{ - static GameLoop loop("preRendering"); - return &loop; -} - -kr::GameLoop* kr::GameLoop::renderingChannel() -{ - static GameLoop loop("rendering"); - return &loop; -} - -kr::GameLoop* kr::GameLoop::postRenderingChannel() -{ - static GameLoop loop("postRendering"); - return &loop; -} - ezResult kr::GameLoop::addCallback(ezStringView callbackName, ezDelegate callback) { - EZ_LOG_BLOCK("kr::GameLoop::addCallback", m_name); + EZ_LOG_BLOCK("kr::GameLoop::addCallback", ezStringBuilder(callbackName).GetData()); auto pCallback{ getCallback(callbackName) }; if(pCallback != nullptr) { - ezLog::Warning("Callback named '%s' already exists.", ezStringBuilder(callbackName).GetData()); + ezLog::Warning("Callback with the given name already exists."); return EZ_FAILURE; } + ezLog::VerboseDebugMessage("Adding callback."); + pCallback = &m_callbacks.ExpandAndGetRef(); pCallback->name = callbackName; pCallback->func = callback; @@ -57,11 +22,16 @@ ezResult kr::GameLoop::addCallback(ezStringView callbackName, ezDelegate void kr::GameLoop::updateCallback(ezStringView callbackName, ezDelegate callback) { - EZ_LOG_BLOCK("kr::GameLoop::updateCallback", m_name); + EZ_LOG_BLOCK("kr::GameLoop::updateCallback", ezStringBuilder(callbackName).GetData()); auto pCallback{ getCallback(callbackName) }; - if(pCallback == nullptr) + if(pCallback) { + ezLog::VerboseDebugMessage("Overwriting existing instance."); + } + else + { + ezLog::VerboseDebugMessage("Creating new instance."); pCallback = &m_callbacks.ExpandAndGetRef(); } @@ -71,7 +41,7 @@ void kr::GameLoop::updateCallback(ezStringView callbackName, ezDelegate ezResult kr::GameLoop::removeCallback(ezStringView callbackName) { - EZ_LOG_BLOCK("kr::GameLoop::removeCallback", m_name); + EZ_LOG_BLOCK("kr::GameLoop::removeCallback", ezStringBuilder(callbackName).GetData()); auto count{ m_callbacks.GetCount() }; for(ezUInt32 i = 0; i < count; i++) @@ -83,13 +53,12 @@ ezResult kr::GameLoop::removeCallback(ezStringView callbackName) } } - ezLog::Error("No callback named '%s'.", ezStringBuilder(callbackName).GetData()); + ezLog::Info("Unable to find the callback with the given name."); return EZ_FAILURE; } void kr::GameLoop::tick(ezLogInterface* pLogInterface) { - EZ_LOG_BLOCK(pLogInterface, "Ticking GameLoop", m_name); for(auto& callback : m_callbacks) { ezStringBuilder callbackName{ callback.name }; @@ -111,3 +80,100 @@ kr::GameLoop::Callback* kr::GameLoop::getCallback(ezStringView name) return nullptr; } + +namespace +{ + struct NamedGameLoop + { + ezString name; + kr::GameLoop* pInstance = nullptr; + + NamedGameLoop() = default; + NamedGameLoop(ezStringView name, kr::GameLoop* pInstance) : name{ name }, pInstance{ pInstance } {} + ~NamedGameLoop() { pInstance = nullptr; } + }; +} + +static ezDynamicArray& globalGameLoops() +{ + static ezDynamicArray value; + return value; +} + +ezResult kr::GlobalGameLoopRegistry::add(ezStringView loopName, GameLoop* pLoop, ezLogInterface* pLogInterface) +{ + EZ_LOG_BLOCK(pLogInterface, "Add Global Game Loop", ezStringBuilder(loopName).GetData()); + + if(pLoop == nullptr) + { + ezLog::Warning(pLogInterface, "Given game loop instance is null."); + return EZ_FAILURE; + } + + auto& all = globalGameLoops(); + for(auto& namedLoop : all) + { + if(namedLoop.pInstance == pLoop) + { + ezLog::Info(pLogInterface, "Global game loop with the given name already exists."); + ezLog::Dev(pLogInterface, "If you want to overwrite it, call remove() first."); + return EZ_FAILURE; + } + } + + all.PushBack(move(NamedGameLoop{ loopName, pLoop })); + ezLog::Success(pLogInterface, "Game loop added successfully."); + return EZ_SUCCESS; +} + +kr::GameLoop* kr::GlobalGameLoopRegistry::get(ezStringView loopName, ezLogInterface* pLogInterface) +{ + EZ_LOG_BLOCK(pLogInterface, "Get Global Game Loop"); + + auto& all = globalGameLoops(); + for(auto& namedLoop : all) + { + if(namedLoop.name == loopName) + { + ezLog::VerboseDebugMessage(pLogInterface, "Found global game loop named '%s'.", loopName); + return namedLoop.pInstance; + } + } + + ezLog::Warning(pLogInterface, "Unable to find global game loop named '%s'.", ezStringBuilder(loopName).GetData()); + return nullptr; +} + +ezResult kr::GlobalGameLoopRegistry::remove(GameLoop* pLoop, ezLogInterface* pLogInterface) +{ + EZ_LOG_BLOCK(pLogInterface, "Removing Global Game Loop"); + + if(pLoop == nullptr) + { + ezLog::Warning(pLogInterface, "Given game loop instance is null!"); + return EZ_FAILURE; + } + + auto& all = globalGameLoops(); + for(ezUInt32 i = 0; i < all.GetCount(); ++i) + { + if(all[i].pInstance == pLoop) + { + ezLog::Success(pLogInterface, "Removing game loop named '%s'.", all[i].name); + all.RemoveAt(i); + return EZ_SUCCESS; + } + } + + ezLog::Warning(pLogInterface, "Given game loop instance is not registered. Unable to remove."); + return EZ_FAILURE; +} + +void kr::GlobalGameLoopRegistry::tick(ezLogInterface* pLogInterface) +{ + for(auto& namedLoop : globalGameLoops()) + { + EZ_LOG_BLOCK(pLogInterface, "Ticking Global Game Loop", namedLoop.name); + namedLoop.pInstance->tick(pLogInterface); + } +} diff --git a/tests/krEngineTests/test_game.cpp b/tests/krEngineTests/test_game.cpp index aa4baf5..1e791b3 100644 --- a/tests/krEngineTests/test_game.cpp +++ b/tests/krEngineTests/test_game.cpp @@ -67,3 +67,28 @@ TEST_CASE("Game Loop", "[game]") REQUIRE(checkBar == 4); } } + + +TEST_CASE("Global Game Loop Registry", "[game]") +{ + using namespace kr; + + GameLoop loop1; + REQUIRE(GlobalGameLoopRegistry::add("loop1", &loop1).Succeeded()); + + GameLoop loop2; + REQUIRE(GlobalGameLoopRegistry::add("loop2", &loop2).Succeeded()); + + int check1 = 0; + int check2 = 0; + + REQUIRE(loop1.addCallback("foo", [&]() { ++check1; }).Succeeded()); + REQUIRE(loop2.addCallback("foo", [&]() { ++check2; }).Succeeded()); + + GlobalGameLoopRegistry::tick(); + REQUIRE(check1 == 1); + REQUIRE(check2 == 1); + + REQUIRE(GlobalGameLoopRegistry::remove("loop2").Succeeded()); + REQUIRE(GlobalGameLoopRegistry::remove("loop1").Succeeded()); +} From a35cebb1ff27d188061b38c6c4e4e3e46348aad5 Mon Sep 17 00:00:00 2001 From: Manuel Maier Date: Sat, 7 Nov 2015 13:09:13 +0000 Subject: [PATCH 03/23] Define main module structure Default main module is not fully implemented yet though. --- code/krEngine/game.h | 3 +- code/krEngine/game/defaultMainModule.h | 43 ++++++ .../game/implementation/defaultMainModule.cpp | 56 ++++++++ .../game/implementation/mainModule.cpp | 13 ++ code/krEngine/game/mainModule.h | 50 +++++++ code/krGameLauncher/main.cpp | 135 ++++++++++++------ tests/krGameTest1/game.cpp | 22 ++- 7 files changed, 278 insertions(+), 44 deletions(-) create mode 100644 code/krEngine/game/defaultMainModule.h create mode 100644 code/krEngine/game/implementation/defaultMainModule.cpp create mode 100644 code/krEngine/game/implementation/mainModule.cpp create mode 100644 code/krEngine/game/mainModule.h diff --git a/code/krEngine/game.h b/code/krEngine/game.h index a3d9d3f..b482a62 100644 --- a/code/krEngine/game.h +++ b/code/krEngine/game.h @@ -1,4 +1,5 @@ #pragma once -//#include #include +#include +#include diff --git a/code/krEngine/game/defaultMainModule.h b/code/krEngine/game/defaultMainModule.h new file mode 100644 index 0000000..c85c058 --- /dev/null +++ b/code/krEngine/game/defaultMainModule.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +#include + +namespace kr +{ + class KR_ENGINE_API DefaultWindow : public ezWindow + { + public: // *** Inherited from ezWindow + virtual void OnClickCloseMessage(); + + public: // *** Accessors + + void SetCreationDescription(ezWindowCreationDesc& desc); + bool userRequestsClose() const { return m_userRequestsClose; } + + protected: // *** Data + bool m_userRequestsClose; + }; + + class KR_ENGINE_API DefaultMainModule : public MainModule + { + public: // *** Static API + DefaultMainModule* instance(); + + public: // *** Inherited via MainModule + virtual void startupCore() override; + virtual void startupEngine() override; + virtual void shutdownEngine() override; + virtual void shutdownCore() override; + virtual void tick() override; + virtual bool keepTicking() const override { return m_keepTicking; } + + public: // *** Accessors + DefaultWindow* window() { return &m_window; } + + protected: // *** Data + bool m_keepTicking = true; + DefaultWindow m_window; + }; +} diff --git a/code/krEngine/game/implementation/defaultMainModule.cpp b/code/krEngine/game/implementation/defaultMainModule.cpp new file mode 100644 index 0000000..ec30910 --- /dev/null +++ b/code/krEngine/game/implementation/defaultMainModule.cpp @@ -0,0 +1,56 @@ +#include + +#include +#include + + +kr::DefaultMainModule* kr::DefaultMainModule::instance() +{ + return static_cast(kr::MainModule::instance()); +} + +void kr::DefaultMainModule::startupCore() +{ + ezStartup::StartupCore(); +} + +void kr::DefaultMainModule::startupEngine() +{ + EZ_ASSERT_ALWAYS(m_window.Initialize().Succeeded(), "Failed to open window"); + + ezStartup::StartupEngine(); +} + +void kr::DefaultMainModule::shutdownEngine() +{ + ezStartup::ShutdownEngine(); + + if (m_window.Destroy().Failed()) + { + ezLog::SeriousWarning("Failed to destroy window."); + } +} + +void kr::DefaultMainModule::shutdownCore() +{ + ezStartup::ShutdownCore(); +} + +void kr::DefaultMainModule::tick() +{ + // TODO +} + +void kr::DefaultWindow::OnClickCloseMessage() +{ + m_userRequestsClose = true; +} + +void kr::DefaultWindow::SetCreationDescription(ezWindowCreationDesc& desc) +{ + if (IsInitialized()) + { + ezLog::Warning("Setting window creation description after the window has been created does not take effect until the window is re-initialized!"); + } + m_CreationDescription = desc; +} diff --git a/code/krEngine/game/implementation/mainModule.cpp b/code/krEngine/game/implementation/mainModule.cpp new file mode 100644 index 0000000..24aa9a0 --- /dev/null +++ b/code/krEngine/game/implementation/mainModule.cpp @@ -0,0 +1,13 @@ +#include + +static kr::MainModule* g_pMainModule{ nullptr }; + +void kr::MainModule::installInstance(MainModule* pModule) +{ + g_pMainModule = pModule; +} + +kr::MainModule* kr::MainModule::instance() +{ + return g_pMainModule; +} diff --git a/code/krEngine/game/mainModule.h b/code/krEngine/game/mainModule.h new file mode 100644 index 0000000..3ed7b1d --- /dev/null +++ b/code/krEngine/game/mainModule.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include + +namespace kr +{ + class KR_ENGINE_API MainModule + { + public: // *** Static API + static void installInstance(MainModule* pModule); + static MainModule* instance(); + + public: // *** Construction + MainModule() {} + virtual ~MainModule() {} + + public: // *** Events + virtual void startupCore() = 0; + virtual void startupEngine() = 0; + virtual void shutdownEngine() = 0; + virtual void shutdownCore() = 0; + + virtual void tick() = 0; + + public: // *** Accessors + virtual bool keepTicking() const = 0; + }; + + template + void OnMainModuleLoad(bool isReloading) + { + static bool mainModuleInitialized = false; + EZ_VERIFY(!mainModuleInitialized, "More than one main module specified! This is not allowed."); + ModuleType* pModule = EZ_DEFAULT_NEW(ModuleType); + kr::MainModule::installInstance(pModule); + } + + template + void OnMainModuleUnload(bool isReloading) + { + auto pModule = static_cast(kr::MainModule::instance()); + kr::MainModule::installInstance(nullptr); + EZ_DEFAULT_DELETE(pModule); + } +} + +#define KR_MAIN_GAME_MODULE(ModuleType) \ + static ezPlugin g_mainGameModulePlugin(false, &kr::OnMainModuleLoad, &kr::OnMainModuleUnload) diff --git a/code/krGameLauncher/main.cpp b/code/krGameLauncher/main.cpp index f4036aa..6e4eca0 100644 --- a/code/krGameLauncher/main.cpp +++ b/code/krGameLauncher/main.cpp @@ -1,7 +1,8 @@ +#include #include #include -#include +#include #include #include @@ -9,15 +10,18 @@ namespace { struct JsonFileReader { - ezFileReader file; + ezOSFile file; ezJSONReader reader; }; } -static ezStringBuilder extractValue(const ezVariantDictionary& data, const char* key) +// pData may NOT be a nullptr. +static ezStringBuilder extractValue(const ezVariantDictionary* pData, const char* key) { + EZ_ASSERT_DEBUG(pData != nullptr, "Unexpected nullptr."); + ezVariant value; - if(!data.TryGetValue(key, value)) + if(!pData->TryGetValue(key, value)) { ezLog::Error("Unable to find key '%s'.", key); return ezStringBuilder(); @@ -32,77 +36,128 @@ static ezStringBuilder extractValue(const ezVariantDictionary& data, const char* return ezStringBuilder(value.Get()); } -static ezStringBuilder extractGameName(const ezCommandLineUtils& cmd, const JsonFileReader& json) +// pData may be a nullptr. +static ezStringBuilder extractValue(const ezCommandLineUtils& cmd, const ezVariantDictionary* pData, const char* key) { - const char* const key = "game"; - ezStringBuilder parentPath; ezStringBuilder gamePath = cmd.GetStringOption(key); - if(gamePath.IsEmpty()) + if(gamePath.IsEmpty() && pData != nullptr) { // Game path was not specified via command line switch. - parentPath = json.file.GetFilePathAbsolute(); - parentPath.PathParentDirectory(); - gamePath = extractValue(json.reader.GetTopLevelObject(), key); - } - else - { - // Game path specified via command line. - parentPath = kr::cwd(); + gamePath = extractValue(pData, key); } - parentPath.AppendPath(gamePath); - return parentPath; + return gamePath; } -static ezResult loadGame(const ezStringBuilder& path) +static ezResult loadGame(const ezStringBuilder& name) { - EZ_LOG_BLOCK("Loading Game", path); + EZ_LOG_BLOCK("Loading Game", name); - return ezPlugin::LoadPlugin(path); + return ezPlugin::LoadPlugin(name); } -int main(int argc, char* argv[]) +static ezResult unloadGame(const ezStringBuilder& name) { - ezStringBuilder hello("Hello World"); + EZ_LOG_BLOCK("Unloading Game", name); + + return ezPlugin::UnloadPlugin(name); +} + +static int runGame(const ezStringBuilder& name) +{ + EZ_LOG_BLOCK("Running Game", name); + + auto pModule{ kr::MainModule::instance() }; + + if (pModule == nullptr) + { + ezLog::Error("No main module instance found."); + return 1; + } + + pModule->startupCore(); + pModule->startupEngine(); + + while(pModule->keepTicking()) + { + pModule->tick(); + } + + pModule->shutdownEngine(); + pModule->shutdownCore(); + + return 0; +} +int main(int argc, char* argv[]) +{ ezGlobalLog::AddLogWriter(ezLogWriter::Console::LogMessageHandler); KR_ON_SCOPE_EXIT{ ezGlobalLog::RemoveLogWriter(ezLogWriter::Console::LogMessageHandler); }; - ezGlobalLog::AddLogWriter(ezLogWriter::VisualStudio::LogMessageHandler); KR_ON_SCOPE_EXIT{ ezGlobalLog::RemoveLogWriter(ezLogWriter::VisualStudio::LogMessageHandler); }; - ezStartup::StartupCore(); - KR_ON_SCOPE_EXIT{ ezStartup::ShutdownCore(); }; - - ezFileSystem::RegisterDataDirectoryFactory(ezDataDirectory::FolderType::Factory); - ezFileSystem::AddDataDirectory(kr::cwd().GetData(), ezFileSystem::DataDirUsage::AllowWrites, "cwd", "cwd"); - KR_ON_SCOPE_EXIT{ ezFileSystem::RemoveDataDirectoryGroup("cwd"); }; - ezCommandLineUtils cmd; cmd.SetCommandLine(argc, const_cast(argv)); - ezStringBuilder configFileName = cmd.GetStringOption("--config"); + ezStringBuilder configFileName{ cmd.GetStringOption("--config") }; if(configFileName.IsEmpty()) { configFileName = "config.json"; } - configFileName.Prepend(""); - JsonFileReader json; - EZ_VERIFY(json.file.Open(configFileName).Succeeded(), "Failed to open config file."); - json.reader.Parse(json.file); + const ezVariantDictionary* pData{ nullptr }; + ezExtendedJSONReader jsonReader; + ezOSFile jsonFile; - auto gameName = extractGameName(cmd, json); + if (jsonFile.Open(configFileName, ezFileMode::Read).Failed()) + { + ezLog::Info("Unable to open JSON config file."); + } + else + { + auto fileData{ EZ_DEFAULT_NEW_ARRAY(ezUInt8, static_cast(jsonFile.GetFileSize())) }; + jsonFile.Read(fileData.GetPtr(), fileData.GetCount()); + + { + ezMemoryStreamStorage mem{ fileData.GetCount() }; + ezMemoryStreamWriter{ &mem }.WriteBytes(fileData.GetPtr(), fileData.GetCount()); + if(jsonReader.Parse(ezMemoryStreamReader{ &mem }).Failed()) + { + ezLog::SeriousWarning("Discarding config file due to error when parsing. Check for a valid JSON format."); + } + else + { + pData = &jsonReader.GetTopLevelObject(); + } + } + } + + auto gameName{ extractValue(cmd, pData, "--game") }; if(gameName.IsEmpty()) { - ezLog::Error("No game path specified."); + ezLog::Error("No game module specified. use either --game on the command line or \"game\" = \"\" in the config file."); + return -1; + } + + if(loadGame(gameName).Failed()) + { + ezLog::Error("Failed to load game."); return 1; } - // Finally load the game. - loadGame(gameName); + auto result{ runGame(gameName) }; + if(result != 0) + { + return result; + } + + if(unloadGame(gameName).Failed()) + { + ezLog::Error("Failed to unload game."); + return 1; + } return 0; } diff --git a/tests/krGameTest1/game.cpp b/tests/krGameTest1/game.cpp index 2a4587b..96610f1 100644 --- a/tests/krGameTest1/game.cpp +++ b/tests/krGameTest1/game.cpp @@ -1,6 +1,22 @@ +#include -class GameTest1 : public kr::GameApplication +class krGameTest1Subsystem : public ezSubSystem { -}; + BEGIN_SUBSYSTEM_DEPENDENCIES + "krEngine" + END_SUBSYSTEM_DEPENDENCIES -KR_DEFINE_GAME_MODULE(); +public: + virtual const char* GetGroupName() const override { return "krGameTest1"; } + virtual const char* GetSubSystemName() const override { return "main"; } + + virtual void OnCoreStartup() + { + auto pMod{ static_cast(kr::MainModule::instance()) }; + ezWindowCreationDesc desc{ pMod->window()->GetCreationDescription() }; + desc.m_Title = "Game Test 1"; + pMod->window()->SetCreationDescription(desc); + } +} static s_GameTest1Subsystem; + +KR_MAIN_GAME_MODULE(kr::DefaultMainModule); From 170fc40c5c33135885c325e2776acd3099e26e5b Mon Sep 17 00:00:00 2001 From: Manuel Maier Date: Sun, 8 Nov 2015 00:43:39 +0000 Subject: [PATCH 04/23] Use ezSubSystem for main module This allows for greater flexibility since the main module is now a sub system and can be integrated in the existing dependency management chain. --- code/krEngine/game/defaultMainModule.h | 24 ++++++---- code/krEngine/game/gameLoop.h | 14 ++++-- .../game/implementation/defaultMainModule.cpp | 43 +++++++++++------ .../krEngine/game/implementation/gameLoop.cpp | 30 +++++++++--- .../game/implementation/mainModule.cpp | 33 +++++++++++-- code/krEngine/game/mainModule.h | 46 ++++--------------- code/krGameLauncher/main.cpp | 26 +++++------ tests/krGameTest1/game.cpp | 23 +++------- 8 files changed, 137 insertions(+), 102 deletions(-) diff --git a/code/krEngine/game/defaultMainModule.h b/code/krEngine/game/defaultMainModule.h index c85c058..c39cd70 100644 --- a/code/krEngine/game/defaultMainModule.h +++ b/code/krEngine/game/defaultMainModule.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include @@ -22,22 +23,25 @@ namespace kr class KR_ENGINE_API DefaultMainModule : public MainModule { - public: // *** Static API - DefaultMainModule* instance(); + public: // *** Construction + DefaultMainModule(); + virtual ~DefaultMainModule(); - public: // *** Inherited via MainModule - virtual void startupCore() override; - virtual void startupEngine() override; - virtual void shutdownEngine() override; - virtual void shutdownCore() override; - virtual void tick() override; - virtual bool keepTicking() const override { return m_keepTicking; } + public: // *** Inherited via kr::MainModule + virtual void OnCoreStartup() override; + virtual void OnEngineStartup() override; + virtual void OnEngineShutdown() override; + virtual void OnCoreShutdown() override; + + public: // *** Runtime + void tick(); public: // *** Accessors DefaultWindow* window() { return &m_window; } protected: // *** Data - bool m_keepTicking = true; + ezPlugin m_plugin{ false }; DefaultWindow m_window; + GameLoop m_moduleLoop; }; } diff --git a/code/krEngine/game/gameLoop.h b/code/krEngine/game/gameLoop.h index ceb709d..19a02ce 100644 --- a/code/krEngine/game/gameLoop.h +++ b/code/krEngine/game/gameLoop.h @@ -2,8 +2,6 @@ namespace kr { - struct LoopListener; - class KR_ENGINE_API GameLoop { public: // *** Construction @@ -74,8 +72,18 @@ namespace kr /// \brief Remove a registered gameloop by name. /// \see remove() - static ezResult remove(ezStringView name, ezLogInterface* pLogInterface = nullptr) { return remove(get(name, pLogInterface), pLogInterface); } + static ezResult remove(ezStringView name, ezLogInterface* pLogInterface = nullptr); static void tick(ezLogInterface* pLogInterface = nullptr); + + /// \brief Whether the global game loop should be ticked or not. + /// \note This value is just a hint, so calls to tick() can still be made. + /// \see setKeepTicking() + static bool keepTicking(); + + /// \brief Set whether the global game loop should be ticked or not. + /// \note Calls to tick() can still be made. + /// \see keepTicking() + static void setKeepTicking(bool value); }; } diff --git a/code/krEngine/game/implementation/defaultMainModule.cpp b/code/krEngine/game/implementation/defaultMainModule.cpp index ec30910..4fb05b7 100644 --- a/code/krEngine/game/implementation/defaultMainModule.cpp +++ b/code/krEngine/game/implementation/defaultMainModule.cpp @@ -1,44 +1,59 @@ #include +#include #include #include -kr::DefaultMainModule* kr::DefaultMainModule::instance() +kr::DefaultMainModule::DefaultMainModule() { - return static_cast(kr::MainModule::instance()); } -void kr::DefaultMainModule::startupCore() +kr::DefaultMainModule::~DefaultMainModule() { - ezStartup::StartupCore(); } -void kr::DefaultMainModule::startupEngine() +void kr::DefaultMainModule::OnCoreStartup() { - EZ_ASSERT_ALWAYS(m_window.Initialize().Succeeded(), "Failed to open window"); +} + +void kr::DefaultMainModule::OnEngineStartup() +{ + EZ_VERIFY(m_window.Initialize().Succeeded(), "Failed to open window"); - ezStartup::StartupEngine(); + // We own this game loop, so the following call should never fail. + EZ_VERIFY(m_moduleLoop.addCallback("main", { &DefaultMainModule::tick, this }).Succeeded(), "Failed to register module tick function."); + + // The following call may fail if the user supplied their own "module" game loop. + if (GlobalGameLoopRegistry::add("module", &m_moduleLoop, ezGlobalLog::GetInstance()).Failed()) + { + ezLog::Error("Failed to register the main module's game loop. Things will probably not work as intended."); + } } -void kr::DefaultMainModule::shutdownEngine() +void kr::DefaultMainModule::OnEngineShutdown() { - ezStartup::ShutdownEngine(); + GlobalGameLoopRegistry::remove("module", ezGlobalLog::GetInstance()); - if (m_window.Destroy().Failed()) + if(m_window.Destroy().Failed()) { ezLog::SeriousWarning("Failed to destroy window."); } } -void kr::DefaultMainModule::shutdownCore() +void kr::DefaultMainModule::OnCoreShutdown() { - ezStartup::ShutdownCore(); } void kr::DefaultMainModule::tick() { - // TODO + m_window.ProcessWindowMessages(); + + if(m_window.userRequestsClose()) + { + GlobalGameLoopRegistry::setKeepTicking(false); + return; + } } void kr::DefaultWindow::OnClickCloseMessage() @@ -48,7 +63,7 @@ void kr::DefaultWindow::OnClickCloseMessage() void kr::DefaultWindow::SetCreationDescription(ezWindowCreationDesc& desc) { - if (IsInitialized()) + if(IsInitialized()) { ezLog::Warning("Setting window creation description after the window has been created does not take effect until the window is re-initialized!"); } diff --git a/code/krEngine/game/implementation/gameLoop.cpp b/code/krEngine/game/implementation/gameLoop.cpp index 866bf5c..1df311b 100644 --- a/code/krEngine/game/implementation/gameLoop.cpp +++ b/code/krEngine/game/implementation/gameLoop.cpp @@ -1,5 +1,6 @@ #include + ezResult kr::GameLoop::addCallback(ezStringView callbackName, ezDelegate callback) { EZ_LOG_BLOCK("kr::GameLoop::addCallback", ezStringBuilder(callbackName).GetData()); @@ -86,7 +87,7 @@ namespace struct NamedGameLoop { ezString name; - kr::GameLoop* pInstance = nullptr; + kr::GameLoop* pInstance{ nullptr }; NamedGameLoop() = default; NamedGameLoop(ezStringView name, kr::GameLoop* pInstance) : name{ name }, pInstance{ pInstance } {} @@ -128,19 +129,19 @@ ezResult kr::GlobalGameLoopRegistry::add(ezStringView loopName, GameLoop* pLoop, kr::GameLoop* kr::GlobalGameLoopRegistry::get(ezStringView loopName, ezLogInterface* pLogInterface) { - EZ_LOG_BLOCK(pLogInterface, "Get Global Game Loop"); + EZ_LOG_BLOCK(pLogInterface, "Get Global Game Loop", ezStringBuilder{ loopName }); auto& all = globalGameLoops(); for(auto& namedLoop : all) { if(namedLoop.name == loopName) { - ezLog::VerboseDebugMessage(pLogInterface, "Found global game loop named '%s'.", loopName); + ezLog::VerboseDebugMessage(pLogInterface, "Found global game loop with the given name."); return namedLoop.pInstance; } } - ezLog::Warning(pLogInterface, "Unable to find global game loop named '%s'.", ezStringBuilder(loopName).GetData()); + ezLog::Warning(pLogInterface, "Unable to find global game loop with the given name."); return nullptr; } @@ -154,12 +155,12 @@ ezResult kr::GlobalGameLoopRegistry::remove(GameLoop* pLoop, ezLogInterface* pLo return EZ_FAILURE; } - auto& all = globalGameLoops(); + auto& all{ globalGameLoops() }; for(ezUInt32 i = 0; i < all.GetCount(); ++i) { if(all[i].pInstance == pLoop) { - ezLog::Success(pLogInterface, "Removing game loop named '%s'.", all[i].name); + ezLog::Success(pLogInterface, "Removed game loop named '%s'.", all[i].name.GetData()); all.RemoveAt(i); return EZ_SUCCESS; } @@ -169,6 +170,11 @@ ezResult kr::GlobalGameLoopRegistry::remove(GameLoop* pLoop, ezLogInterface* pLo return EZ_FAILURE; } +ezResult kr::GlobalGameLoopRegistry::remove(ezStringView name, ezLogInterface* pLogInterface /*= nullptr*/) +{ + return remove(get(name, pLogInterface), pLogInterface); +} + void kr::GlobalGameLoopRegistry::tick(ezLogInterface* pLogInterface) { for(auto& namedLoop : globalGameLoops()) @@ -177,3 +183,15 @@ void kr::GlobalGameLoopRegistry::tick(ezLogInterface* pLogInterface) namedLoop.pInstance->tick(pLogInterface); } } + +static bool g_keepGlobalGameLoopTicking{ true }; + +bool kr::GlobalGameLoopRegistry::keepTicking() +{ + return g_keepGlobalGameLoopTicking; +} + +void kr::GlobalGameLoopRegistry::setKeepTicking(bool value) +{ + g_keepGlobalGameLoopTicking = value; +} diff --git a/code/krEngine/game/implementation/mainModule.cpp b/code/krEngine/game/implementation/mainModule.cpp index 24aa9a0..26ceabc 100644 --- a/code/krEngine/game/implementation/mainModule.cpp +++ b/code/krEngine/game/implementation/mainModule.cpp @@ -2,12 +2,39 @@ static kr::MainModule* g_pMainModule{ nullptr }; -void kr::MainModule::installInstance(MainModule* pModule) +void kr::MainModule::setGlobalInstance(MainModule* pModule) { g_pMainModule = pModule; } -kr::MainModule* kr::MainModule::instance() +kr::MainModule* kr::MainModule::globalInstance() { return g_pMainModule; -} +} + +kr::MainModule::MainModule() +{ +} + +kr::MainModule::~MainModule() +{ +} + +const char* kr::MainModule::GetSubSystemName() const +{ + return "main"; +} + +const char* kr::MainModule::GetGroupName() const +{ + return "Game"; +} + +const char* kr::MainModule::GetDependency(ezInt32 depIndex) +{ + // Note: ez expects a nullptr when all dependencies are given. + // If we never return nullptr, we get an infinite loop when + // starting up the ezEngine. + const char* deps[] = { "krEngine", nullptr }; + return deps[depIndex]; +} diff --git a/code/krEngine/game/mainModule.h b/code/krEngine/game/mainModule.h index 3ed7b1d..d38e63b 100644 --- a/code/krEngine/game/mainModule.h +++ b/code/krEngine/game/mainModule.h @@ -6,45 +6,19 @@ namespace kr { - class KR_ENGINE_API MainModule + class KR_ENGINE_API MainModule : public ezSubSystem { - public: // *** Static API - static void installInstance(MainModule* pModule); - static MainModule* instance(); + public: // *** Static + static void setGlobalInstance(MainModule* pModule); + static MainModule* globalInstance(); public: // *** Construction - MainModule() {} - virtual ~MainModule() {} + MainModule(); + virtual ~MainModule(); - public: // *** Events - virtual void startupCore() = 0; - virtual void startupEngine() = 0; - virtual void shutdownEngine() = 0; - virtual void shutdownCore() = 0; - - virtual void tick() = 0; - - public: // *** Accessors - virtual bool keepTicking() const = 0; + public: // *** Inherited from ezSubSystem + virtual const char* GetSubSystemName() const override; + virtual const char* GetGroupName() const override; + virtual const char* GetDependency(ezInt32 depIndex) override; }; - - template - void OnMainModuleLoad(bool isReloading) - { - static bool mainModuleInitialized = false; - EZ_VERIFY(!mainModuleInitialized, "More than one main module specified! This is not allowed."); - ModuleType* pModule = EZ_DEFAULT_NEW(ModuleType); - kr::MainModule::installInstance(pModule); - } - - template - void OnMainModuleUnload(bool isReloading) - { - auto pModule = static_cast(kr::MainModule::instance()); - kr::MainModule::installInstance(nullptr); - EZ_DEFAULT_DELETE(pModule); - } } - -#define KR_MAIN_GAME_MODULE(ModuleType) \ - static ezPlugin g_mainGameModulePlugin(false, &kr::OnMainModuleLoad, &kr::OnMainModuleUnload) diff --git a/code/krGameLauncher/main.cpp b/code/krGameLauncher/main.cpp index 6e4eca0..3c2d3a3 100644 --- a/code/krGameLauncher/main.cpp +++ b/code/krGameLauncher/main.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -68,24 +69,14 @@ static int runGame(const ezStringBuilder& name) { EZ_LOG_BLOCK("Running Game", name); - auto pModule{ kr::MainModule::instance() }; + ezStartup::StartupEngine(); - if (pModule == nullptr) + while(kr::GlobalGameLoopRegistry::keepTicking()) { - ezLog::Error("No main module instance found."); - return 1; - } - - pModule->startupCore(); - pModule->startupEngine(); - - while(pModule->keepTicking()) - { - pModule->tick(); + kr::GlobalGameLoopRegistry::tick(); } - pModule->shutdownEngine(); - pModule->shutdownCore(); + ezStartup::ShutdownEngine(); return 0; } @@ -97,6 +88,8 @@ int main(int argc, char* argv[]) ezGlobalLog::AddLogWriter(ezLogWriter::VisualStudio::LogMessageHandler); KR_ON_SCOPE_EXIT{ ezGlobalLog::RemoveLogWriter(ezLogWriter::VisualStudio::LogMessageHandler); }; + ezStartup::StartupCore(); + ezCommandLineUtils cmd; cmd.SetCommandLine(argc, const_cast(argv)); @@ -147,12 +140,17 @@ int main(int argc, char* argv[]) return 1; } + // Now that the game module is loaded, re-init to Core again so the game module also gets the event. + ezStartup::ReinitToCurrentState(); + auto result{ runGame(gameName) }; if(result != 0) { return result; } + ezStartup::ShutdownCore(); + if(unloadGame(gameName).Failed()) { ezLog::Error("Failed to unload game."); diff --git a/tests/krGameTest1/game.cpp b/tests/krGameTest1/game.cpp index 96610f1..5d9bad7 100644 --- a/tests/krGameTest1/game.cpp +++ b/tests/krGameTest1/game.cpp @@ -1,22 +1,13 @@ #include -class krGameTest1Subsystem : public ezSubSystem +class krGameTest1 : public kr::DefaultMainModule { - BEGIN_SUBSYSTEM_DEPENDENCIES - "krEngine" - END_SUBSYSTEM_DEPENDENCIES - -public: - virtual const char* GetGroupName() const override { return "krGameTest1"; } - virtual const char* GetSubSystemName() const override { return "main"; } - - virtual void OnCoreStartup() + virtual void OnCoreStartup() override { - auto pMod{ static_cast(kr::MainModule::instance()) }; - ezWindowCreationDesc desc{ pMod->window()->GetCreationDescription() }; + ezWindowCreationDesc desc{ window()->GetCreationDescription() }; desc.m_Title = "Game Test 1"; - pMod->window()->SetCreationDescription(desc); - } -} static s_GameTest1Subsystem; + window()->SetCreationDescription(desc); -KR_MAIN_GAME_MODULE(kr::DefaultMainModule); + kr::DefaultMainModule::OnCoreStartup(); + } +} static g_mainModule; // <-- A static instance is mandatory! From 8b474e68fa328c30f1db076f78ea3ac8cfde34fd Mon Sep 17 00:00:00 2001 From: Manuel Maier Date: Sun, 8 Nov 2015 01:37:01 +0000 Subject: [PATCH 05/23] HTML Log Fix kr::cwd off-by-one error. Mount current working dir as category ".". Write html log on core startup. Might be too late then? --- .../common/implementation/utils_Windows.inl | 3 +- code/krEngine/game/defaultMainModule.h | 4 +- .../game/implementation/defaultMainModule.cpp | 42 ++++++++++++------- tests/krGameTest1/game.cpp | 11 ++--- 4 files changed, 35 insertions(+), 25 deletions(-) diff --git a/code/krEngine/common/implementation/utils_Windows.inl b/code/krEngine/common/implementation/utils_Windows.inl index 5779a9e..b9c9a72 100644 --- a/code/krEngine/common/implementation/utils_Windows.inl +++ b/code/krEngine/common/implementation/utils_Windows.inl @@ -4,7 +4,8 @@ static ezStringBuilder cwdHelper() auto bufferSize = GetCurrentDirectory(0, nullptr); auto buffer = new TCHAR[bufferSize]; KR_ON_SCOPE_EXIT{ delete[] buffer; }; - if(GetCurrentDirectory(bufferSize, buffer) != bufferSize) + auto result = GetCurrentDirectory(bufferSize, buffer); + if(result != bufferSize - 1) { ezLog::Error("Error calling GetCurrentDirectory."); return ezStringBuilder(); diff --git a/code/krEngine/game/defaultMainModule.h b/code/krEngine/game/defaultMainModule.h index c39cd70..ee2899f 100644 --- a/code/krEngine/game/defaultMainModule.h +++ b/code/krEngine/game/defaultMainModule.h @@ -4,6 +4,7 @@ #include #include +#include namespace kr { @@ -14,7 +15,6 @@ namespace kr public: // *** Accessors - void SetCreationDescription(ezWindowCreationDesc& desc); bool userRequestsClose() const { return m_userRequestsClose; } protected: // *** Data @@ -41,7 +41,9 @@ namespace kr protected: // *** Data ezPlugin m_plugin{ false }; + ezWindowCreationDesc m_windowDesc; DefaultWindow m_window; GameLoop m_moduleLoop; + ezLogWriter::HTML m_htmlLog; }; } diff --git a/code/krEngine/game/implementation/defaultMainModule.cpp b/code/krEngine/game/implementation/defaultMainModule.cpp index 4fb05b7..98c6811 100644 --- a/code/krEngine/game/implementation/defaultMainModule.cpp +++ b/code/krEngine/game/implementation/defaultMainModule.cpp @@ -1,12 +1,21 @@ #include #include +#include #include #include +#include +#include +void kr::DefaultWindow::OnClickCloseMessage() +{ + m_userRequestsClose = true; +} + kr::DefaultMainModule::DefaultMainModule() { + ezFileSystem::RegisterDataDirectoryFactory(ezDataDirectory::FolderType::Factory); } kr::DefaultMainModule::~DefaultMainModule() @@ -15,17 +24,29 @@ kr::DefaultMainModule::~DefaultMainModule() void kr::DefaultMainModule::OnCoreStartup() { + if(ezFileSystem::AddDataDirectory(kr::cwd().GetData(), ezFileSystem::AllowWrites, ".", ".").Failed()) + { + ezLog::Error("Failed to mount current working directory as data directory."); + } + + if (m_windowDesc.m_Title.IsEmpty()) + { + m_windowDesc.m_Title = m_plugin.GetPluginName(); + } + + m_htmlLog.BeginLog(ezStringBuilder{ "<.>", m_plugin.GetPluginName(), "Log.html" }, m_windowDesc.m_Title); + ezGlobalLog::AddLogWriter({ &ezLogWriter::HTML::LogMessageHandler, &m_htmlLog }); } void kr::DefaultMainModule::OnEngineStartup() { - EZ_VERIFY(m_window.Initialize().Succeeded(), "Failed to open window"); + EZ_VERIFY(m_window.Initialize(m_windowDesc).Succeeded(), "Failed to open window"); // We own this game loop, so the following call should never fail. EZ_VERIFY(m_moduleLoop.addCallback("main", { &DefaultMainModule::tick, this }).Succeeded(), "Failed to register module tick function."); // The following call may fail if the user supplied their own "module" game loop. - if (GlobalGameLoopRegistry::add("module", &m_moduleLoop, ezGlobalLog::GetInstance()).Failed()) + if(GlobalGameLoopRegistry::add("module", &m_moduleLoop, ezGlobalLog::GetInstance()).Failed()) { ezLog::Error("Failed to register the main module's game loop. Things will probably not work as intended."); } @@ -43,6 +64,9 @@ void kr::DefaultMainModule::OnEngineShutdown() void kr::DefaultMainModule::OnCoreShutdown() { + ezGlobalLog::RemoveLogWriter({ &ezLogWriter::HTML::LogMessageHandler, &m_htmlLog }); + m_htmlLog.EndLog(); + ezFileSystem::RemoveDataDirectoryGroup("."); } void kr::DefaultMainModule::tick() @@ -55,17 +79,3 @@ void kr::DefaultMainModule::tick() return; } } - -void kr::DefaultWindow::OnClickCloseMessage() -{ - m_userRequestsClose = true; -} - -void kr::DefaultWindow::SetCreationDescription(ezWindowCreationDesc& desc) -{ - if(IsInitialized()) - { - ezLog::Warning("Setting window creation description after the window has been created does not take effect until the window is re-initialized!"); - } - m_CreationDescription = desc; -} diff --git a/tests/krGameTest1/game.cpp b/tests/krGameTest1/game.cpp index 5d9bad7..4856d8d 100644 --- a/tests/krGameTest1/game.cpp +++ b/tests/krGameTest1/game.cpp @@ -1,13 +1,10 @@ #include -class krGameTest1 : public kr::DefaultMainModule +class krGameTest1Module : public kr::DefaultMainModule { - virtual void OnCoreStartup() override +public: + krGameTest1Module() { - ezWindowCreationDesc desc{ window()->GetCreationDescription() }; - desc.m_Title = "Game Test 1"; - window()->SetCreationDescription(desc); - - kr::DefaultMainModule::OnCoreStartup(); + m_windowDesc.m_Title = "krGameTest1"; } } static g_mainModule; // <-- A static instance is mandatory! From 1c87be57dad93786e6eb3df59851febf48fc926a Mon Sep 17 00:00:00 2001 From: Manuel Maier Date: Sun, 8 Nov 2015 21:12:57 +0000 Subject: [PATCH 06/23] Fix release builds Setting of linker options was done incorrectly and CMake proves to be an inconsistent son of a bee once more... --- build/CMake/platforms/Windows.cmake | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/build/CMake/platforms/Windows.cmake b/build/CMake/platforms/Windows.cmake index 88be907..9b02593 100644 --- a/build/CMake/platforms/Windows.cmake +++ b/build/CMake/platforms/Windows.cmake @@ -16,14 +16,26 @@ if(MSVC) # Enable floating point exceptions. #"/fp:except" + ) - # Enable debugging local variables in optimized code. - $<$:"/Zo"> + # Enable debugging local variables in optimized code. + set(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO} /Zo") + set(CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO} /Zo") + set(CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO} /Zo") + + + set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /INCREMENTAL:NO") + set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /INCREMENTAL:NO") + set(CMAKE_STATIC_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /INCREMENTAL:NO") + + set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /OPT:REF") + set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /OPT:REF") + set(CMAKE_STATIC_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /OPT:REF") + + set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /OPT:ICF") + set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /OPT:ICF") + set(CMAKE_STATIC_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /OPT:ICF") - $<$:"/INCREMENTAL:NO"> - $<$:"/OPT:REF"> - $<$:"/OPT:ICF"> - ) else() message(WARNING "Unknown compiler on Windows.") endif() From 2023f504770130b3b37c64c9f9755db4c8d7d549 Mon Sep 17 00:00:00 2001 From: Manuel Maier Date: Sun, 8 Nov 2015 22:30:53 +0000 Subject: [PATCH 07/23] Easier custom data directories Provide helper function to get the default root path as an absolute native path. --- code/krEngine/common/implementation/utils.cpp | 13 +++++++++++++ code/krEngine/common/utils.h | 3 +++ .../game/implementation/defaultMainModule.cpp | 15 +++++++++++---- tests/krGameTest1/game.cpp | 3 +++ 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/code/krEngine/common/implementation/utils.cpp b/code/krEngine/common/implementation/utils.cpp index 30ea0cb..9b525bb 100644 --- a/code/krEngine/common/implementation/utils.cpp +++ b/code/krEngine/common/implementation/utils.cpp @@ -5,3 +5,16 @@ #else #error common/utils not implemented for this platform. #endif + +static ezStringBuilder defaultRootHelper() +{ + ezStringBuilder root{ ezOSFile::GetApplicationDirectory() }; + root.PathParentDirectory(); + return root; +} + +const ezStringBuilder& kr::defaultRoot() +{ + static auto root{ defaultRootHelper() }; + return root; +} diff --git a/code/krEngine/common/utils.h b/code/krEngine/common/utils.h index 54ed76a..54d6a51 100755 --- a/code/krEngine/common/utils.h +++ b/code/krEngine/common/utils.h @@ -34,4 +34,7 @@ namespace kr /// \brief Return the current working directory. /// \return The path as a string view. Guaranteed to be null-terminated. KR_ENGINE_API ezStringView cwd(); + + /// \brief The absolute native path to the default data directory. + KR_ENGINE_API const ezStringBuilder& defaultRoot(); } diff --git a/code/krEngine/game/implementation/defaultMainModule.cpp b/code/krEngine/game/implementation/defaultMainModule.cpp index 98c6811..109f62f 100644 --- a/code/krEngine/game/implementation/defaultMainModule.cpp +++ b/code/krEngine/game/implementation/defaultMainModule.cpp @@ -24,9 +24,16 @@ kr::DefaultMainModule::~DefaultMainModule() void kr::DefaultMainModule::OnCoreStartup() { - if(ezFileSystem::AddDataDirectory(kr::cwd().GetData(), ezFileSystem::AllowWrites, ".", ".").Failed()) + // The directory right above the executable belongs to the empty group. + if(ezFileSystem::AddDataDirectory(kr::defaultRoot(), ezFileSystem::ReadOnly).Failed()) { - ezLog::Error("Failed to mount current working directory as data directory."); + ezLog::Error("Failed to mount the default root as read-only data directory."); + } + + // The current working directory belongs to the empty group "". + if(ezFileSystem::AddDataDirectory(kr::cwd().GetData(), ezFileSystem::AllowWrites).Failed()) + { + ezLog::Error("Failed to mount current working directory as writable data directory."); } if (m_windowDesc.m_Title.IsEmpty()) @@ -34,7 +41,7 @@ void kr::DefaultMainModule::OnCoreStartup() m_windowDesc.m_Title = m_plugin.GetPluginName(); } - m_htmlLog.BeginLog(ezStringBuilder{ "<.>", m_plugin.GetPluginName(), "Log.html" }, m_windowDesc.m_Title); + m_htmlLog.BeginLog(ezStringBuilder{ m_plugin.GetPluginName(), "Log.html" }, m_windowDesc.m_Title); ezGlobalLog::AddLogWriter({ &ezLogWriter::HTML::LogMessageHandler, &m_htmlLog }); } @@ -66,7 +73,7 @@ void kr::DefaultMainModule::OnCoreShutdown() { ezGlobalLog::RemoveLogWriter({ &ezLogWriter::HTML::LogMessageHandler, &m_htmlLog }); m_htmlLog.EndLog(); - ezFileSystem::RemoveDataDirectoryGroup("."); + ezFileSystem::RemoveDataDirectoryGroup(""); } void kr::DefaultMainModule::tick() diff --git a/tests/krGameTest1/game.cpp b/tests/krGameTest1/game.cpp index 4856d8d..6a1f522 100644 --- a/tests/krGameTest1/game.cpp +++ b/tests/krGameTest1/game.cpp @@ -6,5 +6,8 @@ class krGameTest1Module : public kr::DefaultMainModule krGameTest1Module() { m_windowDesc.m_Title = "krGameTest1"; + + EZ_VERIFY(ezFileSystem::AddDataDirectory(ezStringBuilder{ kr::defaultRoot(), "testData/textures" }, ezFileSystem::ReadOnly, "", "textures").Succeeded(), + "Failed to mount textures directory."); } } static g_mainModule; // <-- A static instance is mandatory! From b0c586a1af90eeb3ccd10997a8293aa4cbeb840b Mon Sep 17 00:00:00 2001 From: Manuel Maier Date: Tue, 10 Nov 2015 06:45:30 +0000 Subject: [PATCH 08/23] Util makePath To prevent erroneous use of ezStringBuilder to create paths. --- code/krEngine/common/implementation/utils.cpp | 1 + code/krEngine/common/utils.h | 8 ++++++++ tests/krEngineTests/test_common.cpp | 13 +++---------- tests/krGameTest1/game.cpp | 2 +- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/code/krEngine/common/implementation/utils.cpp b/code/krEngine/common/implementation/utils.cpp index 9b525bb..d3aac8f 100644 --- a/code/krEngine/common/implementation/utils.cpp +++ b/code/krEngine/common/implementation/utils.cpp @@ -10,6 +10,7 @@ static ezStringBuilder defaultRootHelper() { ezStringBuilder root{ ezOSFile::GetApplicationDirectory() }; root.PathParentDirectory(); + root.MakeCleanPath(); return root; } diff --git a/code/krEngine/common/utils.h b/code/krEngine/common/utils.h index 54d6a51..a77cb4f 100755 --- a/code/krEngine/common/utils.h +++ b/code/krEngine/common/utils.h @@ -37,4 +37,12 @@ namespace kr /// \brief The absolute native path to the default data directory. KR_ENGINE_API const ezStringBuilder& defaultRoot(); + + inline ezStringBuilder makePath(const char* szPath1, const char* szPath2 = nullptr, const char* szPath3 = nullptr, const char* szPath4 = nullptr) + { + ezStringBuilder path; + path.AppendPath(szPath1, szPath2, szPath3, szPath4); + path.MakeCleanPath(); + return path; + } } diff --git a/tests/krEngineTests/test_common.cpp b/tests/krEngineTests/test_common.cpp index d317210..4b4d7ab 100755 --- a/tests/krEngineTests/test_common.cpp +++ b/tests/krEngineTests/test_common.cpp @@ -133,17 +133,10 @@ TEST_CASE("ezContainer Extension", "[common]") */ } -TEST_CASE("Utils", "[common]") +TEST_CASE("makePath", "[common]") { using namespace kr; - int data = 42; - auto pData = &data; - - REQUIRE(pData != nullptr); - REQUIRE_FALSE(pData == nullptr); - - pData = nullptr; - REQUIRE_FALSE(pData != nullptr); - REQUIRE(pData == nullptr); + auto path = makePath("C:", "hello", "world"); + REQUIRE(path == "C:/hello/world"); // Yes, on all systems. } diff --git a/tests/krGameTest1/game.cpp b/tests/krGameTest1/game.cpp index 6a1f522..fc78331 100644 --- a/tests/krGameTest1/game.cpp +++ b/tests/krGameTest1/game.cpp @@ -7,7 +7,7 @@ class krGameTest1Module : public kr::DefaultMainModule { m_windowDesc.m_Title = "krGameTest1"; - EZ_VERIFY(ezFileSystem::AddDataDirectory(ezStringBuilder{ kr::defaultRoot(), "testData/textures" }, ezFileSystem::ReadOnly, "", "textures").Succeeded(), + EZ_VERIFY(ezFileSystem::AddDataDirectory(kr::makePath(kr::defaultRoot(), "testData", "textures" ), ezFileSystem::ReadOnly, "", "textures").Succeeded(), "Failed to mount textures directory."); } } static g_mainModule; // <-- A static instance is mandatory! From 1f471de15951cfc4ca7bd6f5a8642ea9f3eef38c Mon Sep 17 00:00:00 2001 From: Manuel Maier Date: Tue, 10 Nov 2015 17:01:15 +0000 Subject: [PATCH 09/23] Include the launcher exe on krepel user-side --- build/CMake/kr_client_copy_dlls.cmake | 1 + build/CMake/pullRuntimeBinaries.cmake.in | 1 + 2 files changed, 2 insertions(+) diff --git a/build/CMake/kr_client_copy_dlls.cmake b/build/CMake/kr_client_copy_dlls.cmake index fbfb1e0..36f9b33 100644 --- a/build/CMake/kr_client_copy_dlls.cmake +++ b/build/CMake/kr_client_copy_dlls.cmake @@ -11,6 +11,7 @@ function(kr_client_copy_dlls CLIENT_DIR) file(INSTALL "${KREPEL_DIR}/bin/" DESTINATION "${CLIENT_DIR}" FILES_MATCHING + PATTERN "kr*Launcher.exe" PATTERN "*.dll" PATTERN "*.pdb") set(KREPEL_CLIENT_COPY_DLLS OFF CACHE BOOL "${desc}" FORCE) diff --git a/build/CMake/pullRuntimeBinaries.cmake.in b/build/CMake/pullRuntimeBinaries.cmake.in index 529515a..f336dc5 100644 --- a/build/CMake/pullRuntimeBinaries.cmake.in +++ b/build/CMake/pullRuntimeBinaries.cmake.in @@ -1,5 +1,6 @@ set(CMAKE_INSTALL_MESSAGE "LAZY") file(INSTALL @PULL_PATH@ DESTINATION "@PULL_DESTINATION@" + PATTERN "krGameLauncher.exe" PATTERN "*.dll" PATTERN "*.pdb" ) From b259f99fddd1f64f5a643dfe558b0503a8bd8edf Mon Sep 17 00:00:00 2001 From: Manuel Maier Date: Tue, 10 Nov 2015 17:01:35 +0000 Subject: [PATCH 10/23] Add kr_target_make_launchable --- build/CMake/kr_target_make_launchable.cmake | 17 +++++++++++++++++ build/CMake/launchableGame.vcxproj.user.in | 8 ++++++++ 2 files changed, 25 insertions(+) create mode 100644 build/CMake/kr_target_make_launchable.cmake create mode 100644 build/CMake/launchableGame.vcxproj.user.in diff --git a/build/CMake/kr_target_make_launchable.cmake b/build/CMake/kr_target_make_launchable.cmake new file mode 100644 index 0000000..89018e1 --- /dev/null +++ b/build/CMake/kr_target_make_launchable.cmake @@ -0,0 +1,17 @@ + +function(_kr_target_make_launchable_msvc TARGET_NAME KREPEL_GAME_LAUNCHER) + configure_file("${KREPEL_DIR}/build/CMake/launchableGame.vcxproj.user.in" + "${CMAKE_BINARY_DIR}/code/${TARGET_NAME}/${TARGET_NAME}.vcxproj.user" + @ONLY) +endfunction() + +function(kr_target_make_launchable TARGET_NAME) + find_program(KREPEL_GAME_LAUNCHER + krGameLauncher + HINTS ${CMAKE_SOURCE_DIR}/bin) + if(MSVC) + _kr_target_make_launchable_msvc(${TARGET_NAME} ${KREPEL_GAME_LAUNCHER}) + else() + message(WARNING "Making a target launchable is not supported on this platform.") + endif() +endfunction() diff --git a/build/CMake/launchableGame.vcxproj.user.in b/build/CMake/launchableGame.vcxproj.user.in new file mode 100644 index 0000000..a9bb86b --- /dev/null +++ b/build/CMake/launchableGame.vcxproj.user.in @@ -0,0 +1,8 @@ + + + + @KREPEL_GAME_LAUNCHER@ + WindowsLocalDebugger + --game @TARGET_NAME@ + + From aaf5401e19e0cf8f01f73dd3afe9e1dcc411b5a2 Mon Sep 17 00:00:00 2001 From: Manuel Maier Date: Tue, 10 Nov 2015 17:53:00 +0000 Subject: [PATCH 11/23] kr::configPostfix() + usage in krGameLauncher The function returns -debug, etc. as a string, which is used to load the proper game modules in the current config mode, e.g. asteroids-debug. --- build/CMake/kr_set_pch.cmake | 2 +- code/krEngine/CMakeLists.txt | 2 ++ code/krEngine/common/implementation/utils.cpp | 5 +++ code/krEngine/common/utils.h | 3 ++ code/krGameLauncher/main.cpp | 33 +++++++++++-------- tests/krEngineTests/test_common.cpp | 11 +++++++ 6 files changed, 42 insertions(+), 14 deletions(-) diff --git a/build/CMake/kr_set_pch.cmake b/build/CMake/kr_set_pch.cmake index 1d76f9f..6cac111 100644 --- a/build/CMake/kr_set_pch.cmake +++ b/build/CMake/kr_set_pch.cmake @@ -25,7 +25,7 @@ function(kr_set_pch TARGET_NAME PCH_H) if(NOT EXISTS "${PCH_CPP}") file(WRITE "${PCH_CPP}" "// This file was automatically generated by the krepel build system.\n" - "// This file is empty on purpose." + "// This file is empty on purpose.\n" "// It is used to create the precompield header file (.pch).\n" "// Do not add anything to this file, your changes will be lost!\n" "// Do not check it into your VCS.\n") diff --git a/code/krEngine/CMakeLists.txt b/code/krEngine/CMakeLists.txt index 6a50565..8a36491 100755 --- a/code/krEngine/CMakeLists.txt +++ b/code/krEngine/CMakeLists.txt @@ -29,6 +29,8 @@ target_link_libraries(krEngine ezThirdParty ezFoundation ezCore ezCoreUtils ezSystem ${OPENGL_LIBRARIES}) +target_compile_definitions(krEngine PRIVATE _KR_CONFIG_POSTFIX="$>_POSTFIX>") + # Install Target # ============== include(kr_install_target) diff --git a/code/krEngine/common/implementation/utils.cpp b/code/krEngine/common/implementation/utils.cpp index d3aac8f..740a72b 100644 --- a/code/krEngine/common/implementation/utils.cpp +++ b/code/krEngine/common/implementation/utils.cpp @@ -19,3 +19,8 @@ const ezStringBuilder& kr::defaultRoot() static auto root{ defaultRootHelper() }; return root; } + +const char* kr::configPostfix() +{ + return _KR_CONFIG_POSTFIX; +} diff --git a/code/krEngine/common/utils.h b/code/krEngine/common/utils.h index a77cb4f..ef530f0 100755 --- a/code/krEngine/common/utils.h +++ b/code/krEngine/common/utils.h @@ -45,4 +45,7 @@ namespace kr path.MakeCleanPath(); return path; } + + /// \return One of "-debug", "-minsize", "-reldeb", or "" (i.e. the empty string). + KR_ENGINE_API const char* configPostfix(); } diff --git a/code/krGameLauncher/main.cpp b/code/krGameLauncher/main.cpp index 3c2d3a3..b561761 100644 --- a/code/krGameLauncher/main.cpp +++ b/code/krGameLauncher/main.cpp @@ -89,6 +89,7 @@ int main(int argc, char* argv[]) KR_ON_SCOPE_EXIT{ ezGlobalLog::RemoveLogWriter(ezLogWriter::VisualStudio::LogMessageHandler); }; ezStartup::StartupCore(); + KR_ON_SCOPE_EXIT{ ezStartup::ShutdownCore(); }; ezCommandLineUtils cmd; cmd.SetCommandLine(argc, const_cast(argv)); @@ -134,28 +135,34 @@ int main(int argc, char* argv[]) return -1; } - if(loadGame(gameName).Failed()) + auto gameModuleName{ gameName }; + gameModuleName.Append(kr::configPostfix()); + + auto result{ 0 }; + + if(loadGame(gameModuleName).Failed()) { ezLog::Error("Failed to load game."); - return 1; + result = 1; } - // Now that the game module is loaded, re-init to Core again so the game module also gets the event. - ezStartup::ReinitToCurrentState(); - - auto result{ runGame(gameName) }; - if(result != 0) + if (result == 0) { - return result; - } + // Now that the game module is loaded, re-init to Core again so the game module also gets the event. + ezStartup::ReinitToCurrentState(); - ezStartup::ShutdownCore(); + auto result{ runGame(gameModuleName) }; + if(result != 0) + { + return result; + } + } - if(unloadGame(gameName).Failed()) + if(unloadGame(gameModuleName).Failed()) { ezLog::Error("Failed to unload game."); - return 1; + result = 1; } - return 0; + return result; } diff --git a/tests/krEngineTests/test_common.cpp b/tests/krEngineTests/test_common.cpp index 4b4d7ab..1bcfcce 100755 --- a/tests/krEngineTests/test_common.cpp +++ b/tests/krEngineTests/test_common.cpp @@ -140,3 +140,14 @@ TEST_CASE("makePath", "[common]") auto path = makePath("C:", "hello", "world"); REQUIRE(path == "C:/hello/world"); // Yes, on all systems. } + +TEST_CASE("configPostfix", "[common]") +{ + ezStringView postfix{ kr::configPostfix() }; + +#if EZ_ENABLED(EZ_COMPILE_FOR_DEBUG) + REQUIRE(postfix == "-debug"); +#else + REQUIRE(postfix.IsEmpty()); +#endif +} From 3db9490e98390b34dd57a20b6f3879a68c644040 Mon Sep 17 00:00:00 2001 From: Manuel Maier Date: Sat, 14 Nov 2015 20:02:24 +0100 Subject: [PATCH 12/23] Generalize pullRuntimeBinaries. pullRuntimeBinaries has been generalized to work with krepel and outside project as well. kr_client_copy_dlls for krepel projects is now obsolete. This makes it easier for client projects to retrieve runtime files whenever they want. --- CMakeLists.txt | 4 +++- build/CMake/kr_client_copy_dlls.cmake | 19 ------------------- ...kr_create_target_pullRuntimeBinaries.cmake | 13 +++++++++++++ build/CMake/kr_setup.cmake | 12 ------------ 4 files changed, 16 insertions(+), 32 deletions(-) delete mode 100644 build/CMake/kr_client_copy_dlls.cmake create mode 100644 build/CMake/kr_create_target_pullRuntimeBinaries.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 382cfc0..c4b18a4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,4 +46,6 @@ if(KREPEL_INSTALL_TARGETS_FILE) RENAME "krepel.cmake") endif() -kr_post_config() +include(kr_create_target_pullRuntimeBinaries) +# Note: KREPEL_BINARY_PULL_PATH is created within kr_setup. +kr_create_target_pullRuntimeBinaries("${KREPEL_DIR}/bin" ${KREPEL_BINARY_PULL_PATH}) diff --git a/build/CMake/kr_client_copy_dlls.cmake b/build/CMake/kr_client_copy_dlls.cmake deleted file mode 100644 index 36f9b33..0000000 --- a/build/CMake/kr_client_copy_dlls.cmake +++ /dev/null @@ -1,19 +0,0 @@ - -function(kr_client_copy_dlls CLIENT_DIR) - if(NOT KREPEL_DIR) - message("Unable to copy krepel dlls without KREPEL_DIR.") - return() - endif() - - set(desc "Whether to copy krepel DLLs to client directory.") - set(KREPEL_CLIENT_COPY_DLLS ON CACHE BOOL "${desc}") - if(KREPEL_CLIENT_COPY_DLLS) - file(INSTALL "${KREPEL_DIR}/bin/" - DESTINATION "${CLIENT_DIR}" - FILES_MATCHING - PATTERN "kr*Launcher.exe" - PATTERN "*.dll" - PATTERN "*.pdb") - set(KREPEL_CLIENT_COPY_DLLS OFF CACHE BOOL "${desc}" FORCE) - endif() -endfunction() diff --git a/build/CMake/kr_create_target_pullRuntimeBinaries.cmake b/build/CMake/kr_create_target_pullRuntimeBinaries.cmake new file mode 100644 index 0000000..9cbdc75 --- /dev/null +++ b/build/CMake/kr_create_target_pullRuntimeBinaries.cmake @@ -0,0 +1,13 @@ + +# kr_create_target_pullRuntimeBinaries(./bin ../output/binary_files ${somewhere_else}/binaries) +function(kr_create_target_pullRuntimeBinaries PULL_DESTINATION) + set(PULL_PATH ${ARGN}) + + # Set up the "pull binaries" script + configure_file("${KREPEL_DIR}/build/CMake/pullRuntimeBinaries.cmake.in" "${CMAKE_BINARY_DIR}/pullRuntimeBinaries.cmake" @ONLY) + + add_custom_target(pullRuntimeBinaries ALL + COMMAND ${CMAKE_COMMAND} -P "${CMAKE_BINARY_DIR}/pullRuntimeBinaries.cmake" + COMMENT "Pulling external binaries (.dll, .pdb, ...) from the toolbox.") + +endfunction() diff --git a/build/CMake/kr_setup.cmake b/build/CMake/kr_setup.cmake index 31c5f34..53aca69 100644 --- a/build/CMake/kr_setup.cmake +++ b/build/CMake/kr_setup.cmake @@ -26,10 +26,6 @@ macro(kr_setup) unset(KREPEL_VERSION_CATCH CACHE) endif() - add_custom_target(pullRuntimeBinaries ALL - COMMAND ${CMAKE_COMMAND} -P "${CMAKE_BINARY_DIR}/pullRuntimeBinaries.cmake" - COMMENT "Pulling external binaries (.dll, .pdb, ...) from the toolbox.") - # Use at least C++11 set(CMAKE_CXX_STANDARD 11) @@ -51,11 +47,3 @@ macro(kr_setup) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${CONFIG} "${CMAKE_SOURCE_DIR}/lib") endforeach() endmacro() - -function(kr_post_config) - # Set up the "pull binaries" script - set(PULL_PATH ${KREPEL_BINARY_PULL_PATH}) - set(PULL_DESTINATION "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") - configure_file("${KREPEL_DIR}/build/CMake/pullRuntimeBinaries.cmake.in" "${CMAKE_BINARY_DIR}/pullRuntimeBinaries.cmake" @ONLY) -endfunction() - From 979bcb19e3d1d2949eae63f97c59f997db132b5e Mon Sep 17 00:00:00 2001 From: Manuel Maier Date: Sun, 15 Nov 2015 12:00:17 +0100 Subject: [PATCH 13/23] DefaultMainModule now uses kr::Window. Instead of using ezWindow directly. --- code/krEngine/game/defaultMainModule.h | 7 +++++-- .../game/implementation/defaultMainModule.cpp | 20 ++++++++++--------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/code/krEngine/game/defaultMainModule.h b/code/krEngine/game/defaultMainModule.h index ee2899f..b7e4c94 100644 --- a/code/krEngine/game/defaultMainModule.h +++ b/code/krEngine/game/defaultMainModule.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -8,6 +9,8 @@ namespace kr { + class Window; + class KR_ENGINE_API DefaultWindow : public ezWindow { public: // *** Inherited from ezWindow @@ -37,12 +40,12 @@ namespace kr void tick(); public: // *** Accessors - DefaultWindow* window() { return &m_window; } + Borrowed window() { return m_pWindow; } protected: // *** Data ezPlugin m_plugin{ false }; ezWindowCreationDesc m_windowDesc; - DefaultWindow m_window; + Owned m_pWindow; GameLoop m_moduleLoop; ezLogWriter::HTML m_htmlLog; }; diff --git a/code/krEngine/game/implementation/defaultMainModule.cpp b/code/krEngine/game/implementation/defaultMainModule.cpp index 109f62f..e9d6882 100644 --- a/code/krEngine/game/implementation/defaultMainModule.cpp +++ b/code/krEngine/game/implementation/defaultMainModule.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -47,7 +48,14 @@ void kr::DefaultMainModule::OnCoreStartup() void kr::DefaultMainModule::OnEngineStartup() { - EZ_VERIFY(m_window.Initialize(m_windowDesc).Succeeded(), "Failed to open window"); + m_pWindow = Window::createAndOpen(m_windowDesc); + EZ_VERIFY(m_pWindow != nullptr, "Failed to open window."); + + m_pWindow->getEvent().AddEventHandler([](const WindowEventArgs& e) + { + auto userRequestsClose{ e.type == WindowEventArgs::ClickClose }; + GlobalGameLoopRegistry::setKeepTicking(!userRequestsClose); + }); // We own this game loop, so the following call should never fail. EZ_VERIFY(m_moduleLoop.addCallback("main", { &DefaultMainModule::tick, this }).Succeeded(), "Failed to register module tick function."); @@ -63,7 +71,7 @@ void kr::DefaultMainModule::OnEngineShutdown() { GlobalGameLoopRegistry::remove("module", ezGlobalLog::GetInstance()); - if(m_window.Destroy().Failed()) + if(m_pWindow->close().Failed()) { ezLog::SeriousWarning("Failed to destroy window."); } @@ -78,11 +86,5 @@ void kr::DefaultMainModule::OnCoreShutdown() void kr::DefaultMainModule::tick() { - m_window.ProcessWindowMessages(); - - if(m_window.userRequestsClose()) - { - GlobalGameLoopRegistry::setKeepTicking(false); - return; - } + processWindowMessages(m_pWindow); } From 52e1512a9bb2255780b43621103d56a5dbd7f0a7 Mon Sep 17 00:00:00 2001 From: Manuel Maier Date: Sun, 15 Nov 2015 12:17:24 +0100 Subject: [PATCH 14/23] Make use of ezClock. The state in this commit is not to be taken to seriously, as things will change a lot soon. --- code/krEngine/game/defaultMainModule.h | 3 +++ .../game/implementation/defaultMainModule.cpp | 15 +++++++++++++++ .../rendering/implementation/renderer.cpp | 8 +++++++- code/krEngine/rendering/renderer.h | 2 +- 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/code/krEngine/game/defaultMainModule.h b/code/krEngine/game/defaultMainModule.h index b7e4c94..23763f3 100644 --- a/code/krEngine/game/defaultMainModule.h +++ b/code/krEngine/game/defaultMainModule.h @@ -7,6 +7,8 @@ #include #include +class ezClock; + namespace kr { class Window; @@ -41,6 +43,7 @@ namespace kr public: // *** Accessors Borrowed window() { return m_pWindow; } + ezClock* clock(); protected: // *** Data ezPlugin m_plugin{ false }; diff --git a/code/krEngine/game/implementation/defaultMainModule.cpp b/code/krEngine/game/implementation/defaultMainModule.cpp index e9d6882..07365ae 100644 --- a/code/krEngine/game/implementation/defaultMainModule.cpp +++ b/code/krEngine/game/implementation/defaultMainModule.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -16,6 +17,7 @@ void kr::DefaultWindow::OnClickCloseMessage() kr::DefaultMainModule::DefaultMainModule() { + ezClock::SetNumGlobalClocks(ezGlobalClockCount); ezFileSystem::RegisterDataDirectoryFactory(ezDataDirectory::FolderType::Factory); } @@ -86,5 +88,18 @@ void kr::DefaultMainModule::OnCoreShutdown() void kr::DefaultMainModule::tick() { + clock()->Update(); + auto dt = clock()->GetTimeDiff(); + // So far, the delta time is not used here. + EZ_IGNORE_UNUSED(dt); + processWindowMessages(m_pWindow); + + kr::Renderer::extract(); + kr::Renderer::update(m_pWindow); +} + +ezClock* kr::DefaultMainModule::clock() +{ + return ezClock::Get(ezGlobalClock_GameLogic); } diff --git a/code/krEngine/rendering/implementation/renderer.cpp b/code/krEngine/rendering/implementation/renderer.cpp index e0092cb..887e374 100755 --- a/code/krEngine/rendering/implementation/renderer.cpp +++ b/code/krEngine/rendering/implementation/renderer.cpp @@ -174,8 +174,14 @@ void kr::Renderer::extract() } } -void kr::Renderer::update(ezTime dt, Borrowed pTarget) +void kr::Renderer::update(Borrowed pTarget) { + auto pClock = ezClock::Get(ezGlobalClock_UI); + pClock->Update(); + auto dt = pClock->GetTimeDiff(); + // So far, the delta time is not used in the renderer update. + EZ_IGNORE_UNUSED(dt); + if (!pTarget) { ezLog::Warning(g_pLog, "Invalid target window."); diff --git a/code/krEngine/rendering/renderer.h b/code/krEngine/rendering/renderer.h index 1f2cd7e..3ca01b1 100755 --- a/code/krEngine/rendering/renderer.h +++ b/code/krEngine/rendering/renderer.h @@ -18,6 +18,6 @@ namespace kr KR_ENGINE_API void removeExtractionListener(ExtractionEventListener listener); KR_ENGINE_API void extract(); - KR_ENGINE_API void update(ezTime dt, Borrowed pTarget); + KR_ENGINE_API void update(Borrowed pTarget); }; } From b96235287a29a9272bf7b502a6e3dc8c74865404 Mon Sep 17 00:00:00 2001 From: Manuel Maier Date: Sun, 15 Nov 2015 14:13:21 +0100 Subject: [PATCH 15/23] GlobalGameLoopRegistry takes callbacks directly now. kr::GameLoop was removed and the global registry now associates a name with a given callback. Dependency management is still NOT implemented yet. Also fix issue with global clocks. Renderer and module now keep their own instances. --- code/krEngine/game/defaultMainModule.h | 10 +- code/krEngine/game/gameLoop.h | 80 ++----- .../game/implementation/defaultMainModule.cpp | 30 +-- .../krEngine/game/implementation/gameLoop.cpp | 200 ++++++++---------- .../rendering/implementation/renderer.cpp | 18 +- code/krEngine/rendering/renderer.h | 4 + tests/krEngineTests/test_game.cpp | 92 ++------ tests/krEngineTests/test_renderer.cpp | 6 +- tests/krEngineTests/test_sprite.cpp | 3 +- 9 files changed, 162 insertions(+), 281 deletions(-) diff --git a/code/krEngine/game/defaultMainModule.h b/code/krEngine/game/defaultMainModule.h index 23763f3..cd3df4d 100644 --- a/code/krEngine/game/defaultMainModule.h +++ b/code/krEngine/game/defaultMainModule.h @@ -6,8 +6,8 @@ #include #include +#include -class ezClock; namespace kr { @@ -39,17 +39,17 @@ namespace kr virtual void OnCoreShutdown() override; public: // *** Runtime - void tick(); + virtual void tick(); - public: // *** Accessors + public: // *** Properties Borrowed window() { return m_pWindow; } - ezClock* clock(); + ezClock* clock() { return &m_clock; } protected: // *** Data ezPlugin m_plugin{ false }; + ezClock m_clock; ezWindowCreationDesc m_windowDesc; Owned m_pWindow; - GameLoop m_moduleLoop; ezLogWriter::HTML m_htmlLog; }; } diff --git a/code/krEngine/game/gameLoop.h b/code/krEngine/game/gameLoop.h index 19a02ce..2849a14 100644 --- a/code/krEngine/game/gameLoop.h +++ b/code/krEngine/game/gameLoop.h @@ -2,48 +2,7 @@ namespace kr { - class KR_ENGINE_API GameLoop - { - public: // *** Construction - GameLoop() = default; - - public: // *** Runtime - - /// \brief Add a named callback function to this game loop. - /// - /// Will not overwrite an existing callback with the name \p callbackName. - /// - /// \return Returns \c EZ_SUCCESS if no other callback with the same name existed yet, else \c EZ_FAILURE. - /// \see overwriteCallback() - ezResult addCallback(ezStringView callbackName, ezDelegate callback); - - /// \brief Overwrite or add a named callback function to this game loop. - /// - /// Will add the callback if it didn't exist yet. - /// - /// \see addCallback() - void updateCallback(ezStringView callbackName, ezDelegate callback); - - /// \brief Remove a named callback from this game loop. - /// - /// If the callback existed, will return EZ_SUCCESS. - /// Otherwise EZ_FAILURE is returned and nothing else is done. - ezResult removeCallback(ezStringView callbackName); - - void tick(ezLogInterface* pLogInterface = nullptr); - - private: // *** Internal - struct Callback - { - ezString name; - ezDelegate func; - }; - - Callback* getCallback(ezStringView name); - - private: // *** Data - ezDynamicArray m_callbacks; - }; + using GameLoopCallback = ezDelegate; /// \brief Static class to register game loops with a name. /// @@ -53,27 +12,20 @@ namespace kr GlobalGameLoopRegistry() = delete; public: - /// \brief Add a named gameloop to the global gameloop registry. - /// \return \c EZ_SUCCESS if the names gameloop did not exist yet, \c EZ_FAILURE otherwise. - static ezResult add(ezStringView name, GameLoop* pLoop, ezLogInterface* pLogInterface = nullptr); - - /// \brief Get a registered gameloop instance by name. - /// \return \c nullptr if the named gameloop does not exist. - static GameLoop* get(ezStringView name, ezLogInterface* pLogInterface = nullptr); + /// \brief Set or add a named game loop to the global game loop registry. + /// \return \c EZ_SUCCESS if the names game loop did not exist yet, \c EZ_FAILURE otherwise. + static void set(ezStringView name, GameLoopCallback callback, ezLogInterface* pLogInterface = nullptr); - /// \brief Remove a registered gameloop by pointer. - /// - /// You can also remove a registered gameloop by name with the overloaded version of remove(). - /// - /// \return \c EZ_SUCCESS if the gameloop could successfully be removed, \c EZ_FAILURE if \p pLoop is null or not registered. - /// - /// \see remove() - static ezResult remove(GameLoop* pLoop, ezLogInterface* pLogInterface = nullptr); + /// \brief Get a registered game loop instance by name. + /// \note It is not safe to store a pointer to the callback, as it might be moved in memory the next time set() or remove() is called. + /// \return \c nullptr if the named game loop does not exist. + static GameLoopCallback* get(ezStringView name, ezLogInterface* pLogInterface = nullptr); - /// \brief Remove a registered gameloop by name. - /// \see remove() + /// \brief Remove a registered game loop by name. + /// \return \c EZ_SUCCESS if a game loop of the given name could be found. static ezResult remove(ezStringView name, ezLogInterface* pLogInterface = nullptr); + /// \brief Ticks all registered game loops in order. static void tick(ezLogInterface* pLogInterface = nullptr); /// \brief Whether the global game loop should be ticked or not. @@ -85,5 +37,15 @@ namespace kr /// \note Calls to tick() can still be made. /// \see keepTicking() static void setKeepTicking(bool value); + + /// \brief Whether the global game loop is currently ticking or not. + static bool isTicking(); + + /// \brief Prints the order in which the registered callbacks are executed in for each tick, one per line. + static void printTickOrder(ezLogInterface* pLogInterface = nullptr); + + /// \brief Resets the internal state of the global game loop registry. + /// \pre The global game loop must not be ticking when calling this function. + static void reset(); }; } diff --git a/code/krEngine/game/implementation/defaultMainModule.cpp b/code/krEngine/game/implementation/defaultMainModule.cpp index 07365ae..a00f60a 100644 --- a/code/krEngine/game/implementation/defaultMainModule.cpp +++ b/code/krEngine/game/implementation/defaultMainModule.cpp @@ -59,19 +59,30 @@ void kr::DefaultMainModule::OnEngineStartup() GlobalGameLoopRegistry::setKeepTicking(!userRequestsClose); }); - // We own this game loop, so the following call should never fail. - EZ_VERIFY(m_moduleLoop.addCallback("main", { &DefaultMainModule::tick, this }).Succeeded(), "Failed to register module tick function."); + GlobalGameLoopRegistry::set("clock", [=]() + { + clock()->Update(); + }, ezGlobalLog::GetInstance()); + + GlobalGameLoopRegistry::set("tick", { &DefaultMainModule::tick, this }, ezGlobalLog::GetInstance()); - // The following call may fail if the user supplied their own "module" game loop. - if(GlobalGameLoopRegistry::add("module", &m_moduleLoop, ezGlobalLog::GetInstance()).Failed()) + GlobalGameLoopRegistry::set("message-pump", [=]() { - ezLog::Error("Failed to register the main module's game loop. Things will probably not work as intended."); - } + kr::processWindowMessages(window()); + }, ezGlobalLog::GetInstance()); + + GlobalGameLoopRegistry::set("rendering", [=]() + { + kr::Renderer::extract(); + kr::Renderer::update(window()); + }, ezGlobalLog::GetInstance()); } void kr::DefaultMainModule::OnEngineShutdown() { - GlobalGameLoopRegistry::remove("module", ezGlobalLog::GetInstance()); + GlobalGameLoopRegistry::remove("message-pump", ezGlobalLog::GetInstance()); + GlobalGameLoopRegistry::remove("clock", ezGlobalLog::GetInstance()); + GlobalGameLoopRegistry::remove("tick", ezGlobalLog::GetInstance()); if(m_pWindow->close().Failed()) { @@ -98,8 +109,3 @@ void kr::DefaultMainModule::tick() kr::Renderer::extract(); kr::Renderer::update(m_pWindow); } - -ezClock* kr::DefaultMainModule::clock() -{ - return ezClock::Get(ezGlobalClock_GameLogic); -} diff --git a/code/krEngine/game/implementation/gameLoop.cpp b/code/krEngine/game/implementation/gameLoop.cpp index 1df311b..9492d8c 100644 --- a/code/krEngine/game/implementation/gameLoop.cpp +++ b/code/krEngine/game/implementation/gameLoop.cpp @@ -1,143 +1,71 @@ #include +#include -ezResult kr::GameLoop::addCallback(ezStringView callbackName, ezDelegate callback) -{ - EZ_LOG_BLOCK("kr::GameLoop::addCallback", ezStringBuilder(callbackName).GetData()); - - auto pCallback{ getCallback(callbackName) }; - if(pCallback != nullptr) - { - ezLog::Warning("Callback with the given name already exists."); - return EZ_FAILURE; - } - - ezLog::VerboseDebugMessage("Adding callback."); - pCallback = &m_callbacks.ExpandAndGetRef(); - pCallback->name = callbackName; - pCallback->func = callback; - - return EZ_SUCCESS; -} - -void kr::GameLoop::updateCallback(ezStringView callbackName, ezDelegate callback) +EZ_BEGIN_SUBSYSTEM_DECLARATION(krEngine, GlobalGameLoopRegistry) +ON_CORE_SHUTDOWN { - EZ_LOG_BLOCK("kr::GameLoop::updateCallback", ezStringBuilder(callbackName).GetData()); - - auto pCallback{ getCallback(callbackName) }; - if(pCallback) - { - ezLog::VerboseDebugMessage("Overwriting existing instance."); - } - else - { - ezLog::VerboseDebugMessage("Creating new instance."); - pCallback = &m_callbacks.ExpandAndGetRef(); - } - - pCallback->name = callbackName; - pCallback->func = callback; + kr::GlobalGameLoopRegistry::reset(); } +EZ_END_SUBSYSTEM_DECLARATION -ezResult kr::GameLoop::removeCallback(ezStringView callbackName) -{ - EZ_LOG_BLOCK("kr::GameLoop::removeCallback", ezStringBuilder(callbackName).GetData()); - - auto count{ m_callbacks.GetCount() }; - for(ezUInt32 i = 0; i < count; i++) - { - if(m_callbacks[i].name == callbackName) - { - m_callbacks.RemoveAt(i); - return EZ_SUCCESS; - } - } - - ezLog::Info("Unable to find the callback with the given name."); - return EZ_FAILURE; -} - -void kr::GameLoop::tick(ezLogInterface* pLogInterface) -{ - for(auto& callback : m_callbacks) - { - ezStringBuilder callbackName{ callback.name }; - EZ_LOG_BLOCK(pLogInterface, "Ticking Callback", callbackName); - - callback.func(); - } -} - -kr::GameLoop::Callback* kr::GameLoop::getCallback(ezStringView name) -{ - for(auto& cb : m_callbacks) - { - if(cb.name == name) - { - return &cb; - } - } - - return nullptr; -} namespace { - struct NamedGameLoop + struct GameLoop { + bool isGarbage{ false }; ezString name; - kr::GameLoop* pInstance{ nullptr }; + kr::GameLoopCallback callback; - NamedGameLoop() = default; - NamedGameLoop(ezStringView name, kr::GameLoop* pInstance) : name{ name }, pInstance{ pInstance } {} - ~NamedGameLoop() { pInstance = nullptr; } + GameLoop(ezStringView name, kr::GameLoopCallback cb) : name{ kr::move(name) }, callback{ kr::move(cb) } + { + } }; } -static ezDynamicArray& globalGameLoops() +static ezDynamicArray& globalGameLoops() { - static ezDynamicArray value; + static ezDynamicArray value; return value; } -ezResult kr::GlobalGameLoopRegistry::add(ezStringView loopName, GameLoop* pLoop, ezLogInterface* pLogInterface) +void kr::GlobalGameLoopRegistry::set(ezStringView loopName, GameLoopCallback callback, ezLogInterface* pLogInterface) { EZ_LOG_BLOCK(pLogInterface, "Add Global Game Loop", ezStringBuilder(loopName).GetData()); - if(pLoop == nullptr) + if (!callback.IsValid()) { - ezLog::Warning(pLogInterface, "Given game loop instance is null."); - return EZ_FAILURE; + ezLog::Warning(pLogInterface, "The given game loop callback is not valid."); } auto& all = globalGameLoops(); - for(auto& namedLoop : all) + for(auto& loop : all) { - if(namedLoop.pInstance == pLoop) + if(loop.name == loopName) { - ezLog::Info(pLogInterface, "Global game loop with the given name already exists."); - ezLog::Dev(pLogInterface, "If you want to overwrite it, call remove() first."); - return EZ_FAILURE; + ezLog::Info(pLogInterface, "Overwriting existing game loop."); + loop.callback = move(callback); + return; } } - all.PushBack(move(NamedGameLoop{ loopName, pLoop })); + all.PushBack(move(GameLoop{ move(ezString{ loopName }), move(callback) })); ezLog::Success(pLogInterface, "Game loop added successfully."); - return EZ_SUCCESS; } -kr::GameLoop* kr::GlobalGameLoopRegistry::get(ezStringView loopName, ezLogInterface* pLogInterface) +kr::GameLoopCallback* kr::GlobalGameLoopRegistry::get(ezStringView loopName, ezLogInterface* pLogInterface) { EZ_LOG_BLOCK(pLogInterface, "Get Global Game Loop", ezStringBuilder{ loopName }); auto& all = globalGameLoops(); - for(auto& namedLoop : all) + for(auto& loop : all) { - if(namedLoop.name == loopName) + if(loop.name == loopName) { ezLog::VerboseDebugMessage(pLogInterface, "Found global game loop with the given name."); - return namedLoop.pInstance; + return &loop.callback; } } @@ -145,23 +73,27 @@ kr::GameLoop* kr::GlobalGameLoopRegistry::get(ezStringView loopName, ezLogInterf return nullptr; } -ezResult kr::GlobalGameLoopRegistry::remove(GameLoop* pLoop, ezLogInterface* pLogInterface) +ezResult kr::GlobalGameLoopRegistry::remove(ezStringView loopName, ezLogInterface* pLogInterface /*= nullptr*/) { - EZ_LOG_BLOCK(pLogInterface, "Removing Global Game Loop"); - - if(pLoop == nullptr) - { - ezLog::Warning(pLogInterface, "Given game loop instance is null!"); - return EZ_FAILURE; - } + EZ_LOG_BLOCK(pLogInterface, "Removing Global Game Loop", ezStringBuilder(loopName)); auto& all{ globalGameLoops() }; for(ezUInt32 i = 0; i < all.GetCount(); ++i) { - if(all[i].pInstance == pLoop) + if(all[i].name == loopName) { - ezLog::Success(pLogInterface, "Removed game loop named '%s'.", all[i].name.GetData()); - all.RemoveAt(i); + if (GlobalGameLoopRegistry::isTicking()) + { + // While ticking, we cannot simply remove the loop. We mark it as garbage and remove them once we're done ticking. + all[i].isGarbage = true; + } + else + { + // Since we are not ticking the global game loop, we can immediately remove it. + all.RemoveAt(i); + } + + ezLog::Success(pLogInterface, "Removed game loop named '%s'."); return EZ_SUCCESS; } } @@ -170,17 +102,35 @@ ezResult kr::GlobalGameLoopRegistry::remove(GameLoop* pLoop, ezLogInterface* pLo return EZ_FAILURE; } -ezResult kr::GlobalGameLoopRegistry::remove(ezStringView name, ezLogInterface* pLogInterface /*= nullptr*/) -{ - return remove(get(name, pLogInterface), pLogInterface); -} +static bool g_globalGameLoopIsTicking{ false }; void kr::GlobalGameLoopRegistry::tick(ezLogInterface* pLogInterface) { - for(auto& namedLoop : globalGameLoops()) + g_globalGameLoopIsTicking = true; + KR_ON_SCOPE_EXIT{ g_globalGameLoopIsTicking = false; }; + + // Tick all loops. + auto& all = globalGameLoops(); + for(auto& loop : all) { - EZ_LOG_BLOCK(pLogInterface, "Ticking Global Game Loop", namedLoop.name); - namedLoop.pInstance->tick(pLogInterface); + EZ_LOG_BLOCK(pLogInterface, "Ticking Global Game Loop", loop.name); + if (!loop.isGarbage && loop.callback.IsValid()) + { + loop.callback(); + } + } + + // Collect Garbage + // =============== + ezUInt32 leftToCheck{ all.GetCount() }; + while(leftToCheck > 0) + { + auto index = leftToCheck - 1; + if (all[index].isGarbage) + { + all.RemoveAt(index); + } + --leftToCheck; } } @@ -195,3 +145,21 @@ void kr::GlobalGameLoopRegistry::setKeepTicking(bool value) { g_keepGlobalGameLoopTicking = value; } + +bool kr::GlobalGameLoopRegistry::isTicking() +{ + return g_globalGameLoopIsTicking; +} + +void kr::GlobalGameLoopRegistry::printTickOrder(ezLogInterface* pLogInterface /*= nullptr*/) +{ + for (auto& loop : globalGameLoops()) + { + ezLog::Info(pLogInterface, "%s", loop.name); + } +} + +void kr::GlobalGameLoopRegistry::reset() +{ + globalGameLoops().Clear(); +} diff --git a/code/krEngine/rendering/implementation/renderer.cpp b/code/krEngine/rendering/implementation/renderer.cpp index 887e374..6059267 100755 --- a/code/krEngine/rendering/implementation/renderer.cpp +++ b/code/krEngine/rendering/implementation/renderer.cpp @@ -176,9 +176,8 @@ void kr::Renderer::extract() void kr::Renderer::update(Borrowed pTarget) { - auto pClock = ezClock::Get(ezGlobalClock_UI); - pClock->Update(); - auto dt = pClock->GetTimeDiff(); + clock()->Update(); + auto dt = clock()->GetTimeDiff(); // So far, the delta time is not used in the renderer update. EZ_IGNORE_UNUSED(dt); @@ -223,6 +222,19 @@ void kr::Renderer::update(Borrowed pTarget) } } +static ezClock createRendererClock() +{ + ezClock clock; + clock.SetClockName("RenderClock"); + return clock; +} + +ezClock* kr::Renderer::clock() +{ + static ezClock instance{ createRendererClock() }; + return &instance; +} + void kr::Renderer::addExtractionListener(ExtractionEventListener listener) { g_ExtractionEvent.AddEventHandler(listener); diff --git a/code/krEngine/rendering/renderer.h b/code/krEngine/rendering/renderer.h index 3ca01b1..8bd3f69 100755 --- a/code/krEngine/rendering/renderer.h +++ b/code/krEngine/rendering/renderer.h @@ -1,6 +1,8 @@ #pragma once #include +class ezClock; + namespace kr { namespace Renderer @@ -19,5 +21,7 @@ namespace kr KR_ENGINE_API void extract(); KR_ENGINE_API void update(Borrowed pTarget); + + KR_ENGINE_API ezClock* clock(); }; } diff --git a/tests/krEngineTests/test_game.cpp b/tests/krEngineTests/test_game.cpp index 1e791b3..5d92a48 100644 --- a/tests/krEngineTests/test_game.cpp +++ b/tests/krEngineTests/test_game.cpp @@ -3,92 +3,26 @@ #include -TEST_CASE("Game Loop", "[game]") -{ - using namespace kr; - - SECTION("Ticking an empty main loop") - { - GameLoop loop; - loop.tick(); - } - - SECTION("Single channel usage") - { - GameLoop loop; - int check = 0; - - REQUIRE(loop.addCallback("foo", [&]() { ++check; }).Succeeded()); - REQUIRE(check == 0); - - loop.tick(); - REQUIRE(check == 1); - - loop.tick(); - loop.tick(); - REQUIRE(check == 3); - - REQUIRE(loop.removeCallback("foo").Succeeded()); - REQUIRE(check == 3); - loop.tick(); - REQUIRE(check == 3); - } - - SECTION("Multi-channel usage") - { - GameLoop loop; - int checkFoo = 0; - int checkBar = 0; - - auto cbBoth = [&]() { ++checkFoo; ++checkBar; }; - auto cbFoo = [&]() { ++checkFoo; }; - - REQUIRE(loop.addCallback("fooAndBar", cbBoth).Succeeded()); - REQUIRE(loop.addCallback("foo", cbFoo).Succeeded()); - - loop.tick(); - REQUIRE(checkFoo == 2); - REQUIRE(checkBar == 1); - - loop.tick(); - loop.tick(); - REQUIRE(checkFoo == 6); - REQUIRE(checkBar == 3); - - REQUIRE(loop.removeCallback("fooAndBar").Succeeded()); - loop.tick(); - REQUIRE(checkFoo == 7); - REQUIRE(checkBar == 3); - - REQUIRE(loop.addCallback("fooAndBar", cbBoth).Succeeded()); - REQUIRE(loop.addCallback("fooAndBar", cbBoth).Failed()); - loop.tick(); - REQUIRE(checkFoo == 9); - REQUIRE(checkBar == 4); - } -} - - TEST_CASE("Global Game Loop Registry", "[game]") { using namespace kr; - - GameLoop loop1; - REQUIRE(GlobalGameLoopRegistry::add("loop1", &loop1).Succeeded()); - - GameLoop loop2; - REQUIRE(GlobalGameLoopRegistry::add("loop2", &loop2).Succeeded()); + GlobalGameLoopRegistry::reset(); + KR_ON_SCOPE_EXIT{ GlobalGameLoopRegistry::reset(); }; int check1 = 0; int check2 = 0; - REQUIRE(loop1.addCallback("foo", [&]() { ++check1; }).Succeeded()); - REQUIRE(loop2.addCallback("foo", [&]() { ++check2; }).Succeeded()); + SECTION("Basics") + { + GlobalGameLoopRegistry::set("foo", [&]() { ++check1; }); + GlobalGameLoopRegistry::set("bar", [&]() { ++check2; }); - GlobalGameLoopRegistry::tick(); - REQUIRE(check1 == 1); - REQUIRE(check2 == 1); + GlobalGameLoopRegistry::tick(); + REQUIRE(check1 == 1); + REQUIRE(check2 == 1); - REQUIRE(GlobalGameLoopRegistry::remove("loop2").Succeeded()); - REQUIRE(GlobalGameLoopRegistry::remove("loop1").Succeeded()); + REQUIRE(GlobalGameLoopRegistry::remove("foo").Succeeded()); + REQUIRE(GlobalGameLoopRegistry::remove("bar").Succeeded()); + REQUIRE(GlobalGameLoopRegistry::remove("bar").Failed()); + } } diff --git a/tests/krEngineTests/test_renderer.cpp b/tests/krEngineTests/test_renderer.cpp index 986f8c2..f863adc 100755 --- a/tests/krEngineTests/test_renderer.cpp +++ b/tests/krEngineTests/test_renderer.cpp @@ -123,13 +123,9 @@ TEST_CASE("Experiments", "[renderer][experiments]") //while(now < targetTime) while(run) { - auto dt = ezTime::Now() - now; - processWindowMessages(pWindow); Renderer::extract(); - Renderer::update(dt, pWindow); - - now += dt; + Renderer::update(pWindow); } } } diff --git a/tests/krEngineTests/test_sprite.cpp b/tests/krEngineTests/test_sprite.cpp index f835d3e..4f753fe 100755 --- a/tests/krEngineTests/test_sprite.cpp +++ b/tests/krEngineTests/test_sprite.cpp @@ -58,11 +58,10 @@ TEST_CASE("Workflow", "[sprite]") extract(e, sprite, t); }); - ezTime dt; while(run) { processWindowMessages(pWindow); Renderer::extract(); - Renderer::update(dt, pWindow); + Renderer::update(pWindow); } } From bf664e388ab72a2d71f3ada294a91072561d4b33 Mon Sep 17 00:00:00 2001 From: Manuel Maier Date: Sun, 15 Nov 2015 14:20:18 +0100 Subject: [PATCH 16/23] Fix log message formatting. --- code/krEngine/game/implementation/gameLoop.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/krEngine/game/implementation/gameLoop.cpp b/code/krEngine/game/implementation/gameLoop.cpp index 9492d8c..a75afcf 100644 --- a/code/krEngine/game/implementation/gameLoop.cpp +++ b/code/krEngine/game/implementation/gameLoop.cpp @@ -93,7 +93,7 @@ ezResult kr::GlobalGameLoopRegistry::remove(ezStringView loopName, ezLogInterfac all.RemoveAt(i); } - ezLog::Success(pLogInterface, "Removed game loop named '%s'."); + ezLog::Success(pLogInterface, "Removed game loop."); return EZ_SUCCESS; } } From bf8a672d5de5e4cec557cb995c59b36d8506592a Mon Sep 17 00:00:00 2001 From: Manuel Maier Date: Sun, 15 Nov 2015 17:39:13 +0100 Subject: [PATCH 17/23] Game loop priority. Game loops can now be sorted by priority, which ist just a signed 32 bit integer. I refrained from implemented a full-blown dependency management system as it is probably overkill for us. There is a convenience function to print the tick order, which can be used for debugging (kr::GlobalGameLoopRegistry::printTickOrder). --- code/krEngine/game/defaultMainModule.h | 13 +++ code/krEngine/game/gameLoop.h | 16 ++- .../game/implementation/defaultMainModule.cpp | 26 +++-- .../krEngine/game/implementation/gameLoop.cpp | 102 ++++++++++++++---- tests/krEngineTests/test_game.cpp | 17 ++- 5 files changed, 144 insertions(+), 30 deletions(-) diff --git a/code/krEngine/game/defaultMainModule.h b/code/krEngine/game/defaultMainModule.h index cd3df4d..52dafcb 100644 --- a/code/krEngine/game/defaultMainModule.h +++ b/code/krEngine/game/defaultMainModule.h @@ -13,6 +13,19 @@ namespace kr { class Window; + struct DefaultGameLoopPriorities + { + enum Enum + { + Clock = -20, + MessagePump = -10, + Input = -5, + Rendering = 20, + }; + + DefaultGameLoopPriorities() = delete; + }; + class KR_ENGINE_API DefaultWindow : public ezWindow { public: // *** Inherited from ezWindow diff --git a/code/krEngine/game/gameLoop.h b/code/krEngine/game/gameLoop.h index 2849a14..59bcc32 100644 --- a/code/krEngine/game/gameLoop.h +++ b/code/krEngine/game/gameLoop.h @@ -14,16 +14,26 @@ namespace kr /// \brief Set or add a named game loop to the global game loop registry. /// \return \c EZ_SUCCESS if the names game loop did not exist yet, \c EZ_FAILURE otherwise. - static void set(ezStringView name, GameLoopCallback callback, ezLogInterface* pLogInterface = nullptr); + static void set(ezStringView loopName, GameLoopCallback callback, ezLogInterface* pLogInterface = nullptr); /// \brief Get a registered game loop instance by name. /// \note It is not safe to store a pointer to the callback, as it might be moved in memory the next time set() or remove() is called. /// \return \c nullptr if the named game loop does not exist. - static GameLoopCallback* get(ezStringView name, ezLogInterface* pLogInterface = nullptr); + static GameLoopCallback* get(ezStringView loopName, ezLogInterface* pLogInterface = nullptr); /// \brief Remove a registered game loop by name. /// \return \c EZ_SUCCESS if a game loop of the given name could be found. - static ezResult remove(ezStringView name, ezLogInterface* pLogInterface = nullptr); + static ezResult remove(ezStringView loopName, ezLogInterface* pLogInterface = nullptr); + + /// \brief Set the priority for a named game loop. + /// \pre A game loop with the given name must already exist. + /// \see set() + static void setPriority(ezStringView loopName, ezInt32 priority, ezLogInterface* pLogInterface = nullptr); + + /// \brief Get the priority for a named game loop. + /// \pre A game loop with the given name must already exist. + /// \see set() + static ezInt32 getPriority(ezStringView loopName, ezLogInterface* pLogInterface = nullptr); /// \brief Ticks all registered game loops in order. static void tick(ezLogInterface* pLogInterface = nullptr); diff --git a/code/krEngine/game/implementation/defaultMainModule.cpp b/code/krEngine/game/implementation/defaultMainModule.cpp index a00f60a..6f45d84 100644 --- a/code/krEngine/game/implementation/defaultMainModule.cpp +++ b/code/krEngine/game/implementation/defaultMainModule.cpp @@ -59,30 +59,42 @@ void kr::DefaultMainModule::OnEngineStartup() GlobalGameLoopRegistry::setKeepTicking(!userRequestsClose); }); + auto pLog{ ezGlobalLog::GetInstance() }; + GlobalGameLoopRegistry::set("clock", [=]() { clock()->Update(); - }, ezGlobalLog::GetInstance()); + }, pLog); + GlobalGameLoopRegistry::setPriority("clock", DefaultGameLoopPriorities::Clock, pLog); - GlobalGameLoopRegistry::set("tick", { &DefaultMainModule::tick, this }, ezGlobalLog::GetInstance()); + GlobalGameLoopRegistry::set("tick", { &DefaultMainModule::tick, this }, pLog); GlobalGameLoopRegistry::set("message-pump", [=]() { kr::processWindowMessages(window()); - }, ezGlobalLog::GetInstance()); + }, pLog); + GlobalGameLoopRegistry::setPriority("message-pump", DefaultGameLoopPriorities::MessagePump, pLog); + + GlobalGameLoopRegistry::set("input", [=]() + { + ezInputManager::Update(clock()->GetTimeDiff()); + }, pLog); + GlobalGameLoopRegistry::setPriority("input", DefaultGameLoopPriorities::Input, pLog); GlobalGameLoopRegistry::set("rendering", [=]() { kr::Renderer::extract(); kr::Renderer::update(window()); - }, ezGlobalLog::GetInstance()); + }, pLog); + GlobalGameLoopRegistry::setPriority("rendering", DefaultGameLoopPriorities::Rendering, pLog); } void kr::DefaultMainModule::OnEngineShutdown() { - GlobalGameLoopRegistry::remove("message-pump", ezGlobalLog::GetInstance()); - GlobalGameLoopRegistry::remove("clock", ezGlobalLog::GetInstance()); - GlobalGameLoopRegistry::remove("tick", ezGlobalLog::GetInstance()); + auto pLog{ ezGlobalLog::GetInstance() }; + GlobalGameLoopRegistry::remove("message-pump", pLog); + GlobalGameLoopRegistry::remove("clock", pLog); + GlobalGameLoopRegistry::remove("tick", pLog); if(m_pWindow->close().Failed()) { diff --git a/code/krEngine/game/implementation/gameLoop.cpp b/code/krEngine/game/implementation/gameLoop.cpp index a75afcf..86fce5a 100644 --- a/code/krEngine/game/implementation/gameLoop.cpp +++ b/code/krEngine/game/implementation/gameLoop.cpp @@ -4,10 +4,10 @@ EZ_BEGIN_SUBSYSTEM_DECLARATION(krEngine, GlobalGameLoopRegistry) -ON_CORE_SHUTDOWN -{ - kr::GlobalGameLoopRegistry::reset(); -} + ON_CORE_SHUTDOWN + { + kr::GlobalGameLoopRegistry::reset(); + } EZ_END_SUBSYSTEM_DECLARATION @@ -17,6 +17,7 @@ namespace { bool isGarbage{ false }; ezString name; + ezInt32 priority{ 0 }; kr::GameLoopCallback callback; GameLoop(ezStringView name, kr::GameLoopCallback cb) : name{ kr::move(name) }, callback{ kr::move(cb) } @@ -31,6 +32,35 @@ static ezDynamicArray& globalGameLoops() return value; } +static bool g_gameLoopsNeedSorting{ false }; + +#include + +static void sortByPriority() +{ + auto& loops{ globalGameLoops() }; + + std::stable_sort(begin(loops), end(loops), [](const GameLoop& a, const GameLoop& b){ return a.priority > b.priority; }); + + g_gameLoopsNeedSorting = false; +} + +GameLoop* internalGet(ezStringView loopName, ezLogInterface* pLogInterface) +{ + auto& all = globalGameLoops(); + for(auto& loop : all) + { + if(loop.name == loopName) + { + ezLog::VerboseDebugMessage(pLogInterface, "Found global game loop with the given name."); + return &loop; + } + } + + ezLog::Warning(pLogInterface, "Unable to find global game loop with the given name."); + return nullptr; +} + void kr::GlobalGameLoopRegistry::set(ezStringView loopName, GameLoopCallback callback, ezLogInterface* pLogInterface) { EZ_LOG_BLOCK(pLogInterface, "Add Global Game Loop", ezStringBuilder(loopName).GetData()); @@ -53,24 +83,16 @@ void kr::GlobalGameLoopRegistry::set(ezStringView loopName, GameLoopCallback cal all.PushBack(move(GameLoop{ move(ezString{ loopName }), move(callback) })); ezLog::Success(pLogInterface, "Game loop added successfully."); + + g_gameLoopsNeedSorting = true; } kr::GameLoopCallback* kr::GlobalGameLoopRegistry::get(ezStringView loopName, ezLogInterface* pLogInterface) { EZ_LOG_BLOCK(pLogInterface, "Get Global Game Loop", ezStringBuilder{ loopName }); - auto& all = globalGameLoops(); - for(auto& loop : all) - { - if(loop.name == loopName) - { - ezLog::VerboseDebugMessage(pLogInterface, "Found global game loop with the given name."); - return &loop.callback; - } - } - - ezLog::Warning(pLogInterface, "Unable to find global game loop with the given name."); - return nullptr; + auto ptr{ internalGet(loopName, pLogInterface) }; + return ptr == nullptr ? nullptr : &ptr->callback; } ezResult kr::GlobalGameLoopRegistry::remove(ezStringView loopName, ezLogInterface* pLogInterface /*= nullptr*/) @@ -102,6 +124,35 @@ ezResult kr::GlobalGameLoopRegistry::remove(ezStringView loopName, ezLogInterfac return EZ_FAILURE; } +void kr::GlobalGameLoopRegistry::setPriority(ezStringView loopName, ezInt32 priority, ezLogInterface* pLogInterface /*= nullptr*/) +{ + EZ_LOG_BLOCK(pLogInterface, "Set Priority For Global Game Loop", ezStringBuilder(loopName)); + + auto pLoop = internalGet(loopName, pLogInterface); + if(pLoop == nullptr) + { + ezLog::Warning(pLogInterface, "Unable to set priority on loop "); + return; + } + + pLoop->priority = priority; + g_gameLoopsNeedSorting = true; +} + +ezInt32 kr::GlobalGameLoopRegistry::getPriority(ezStringView loopName, ezLogInterface* pLogInterface /*= nullptr*/) +{ + EZ_LOG_BLOCK(pLogInterface, "Get Priority of Global Game Loop", ezStringBuilder(loopName)); + + auto pLoop = internalGet(loopName, pLogInterface); + if(pLoop == nullptr) + { + ezLog::Warning(pLogInterface, "Unable to set priority on loop "); + return 0; + } + + return pLoop->priority; +} + static bool g_globalGameLoopIsTicking{ false }; void kr::GlobalGameLoopRegistry::tick(ezLogInterface* pLogInterface) @@ -109,6 +160,12 @@ void kr::GlobalGameLoopRegistry::tick(ezLogInterface* pLogInterface) g_globalGameLoopIsTicking = true; KR_ON_SCOPE_EXIT{ g_globalGameLoopIsTicking = false; }; + if (g_gameLoopsNeedSorting) + { + ezLog::Info(pLogInterface, "Sorting game loops by priority."); + sortByPriority(); + } + // Tick all loops. auto& all = globalGameLoops(); for(auto& loop : all) @@ -153,13 +210,22 @@ bool kr::GlobalGameLoopRegistry::isTicking() void kr::GlobalGameLoopRegistry::printTickOrder(ezLogInterface* pLogInterface /*= nullptr*/) { - for (auto& loop : globalGameLoops()) + if (g_gameLoopsNeedSorting) + { + sortByPriority(); + } + + auto& loops{ globalGameLoops() }; + for (auto& loop : loops) { - ezLog::Info(pLogInterface, "%s", loop.name); + ezLog::Info(pLogInterface, "%s", loop.name.GetData()); } } void kr::GlobalGameLoopRegistry::reset() { globalGameLoops().Clear(); + g_gameLoopsNeedSorting = false; + g_globalGameLoopIsTicking = false; + g_keepGlobalGameLoopTicking = true; } diff --git a/tests/krEngineTests/test_game.cpp b/tests/krEngineTests/test_game.cpp index 5d92a48..e8202cc 100644 --- a/tests/krEngineTests/test_game.cpp +++ b/tests/krEngineTests/test_game.cpp @@ -6,8 +6,8 @@ TEST_CASE("Global Game Loop Registry", "[game]") { using namespace kr; - GlobalGameLoopRegistry::reset(); - KR_ON_SCOPE_EXIT{ GlobalGameLoopRegistry::reset(); }; + + KR_TESTS_RAII_CORE_STARTUP; int check1 = 0; int check2 = 0; @@ -25,4 +25,17 @@ TEST_CASE("Global Game Loop Registry", "[game]") REQUIRE(GlobalGameLoopRegistry::remove("bar").Succeeded()); REQUIRE(GlobalGameLoopRegistry::remove("bar").Failed()); } + + SECTION("Priority") + { + GlobalGameLoopRegistry::set("bar", [&]() { REQUIRE(check1 == 1); ++check1; }); + GlobalGameLoopRegistry::set("foo", [&]() { REQUIRE(check1 == 0); ++check1; }); + + GlobalGameLoopRegistry::setPriority("foo", 10); + GlobalGameLoopRegistry::setPriority("bar", -10); + + GlobalGameLoopRegistry::tick(); + REQUIRE(check1 == 2); + REQUIRE(check2 == 0); + } } From f5b89e2f8104a333c1ae65689e45ddfd3012dcdc Mon Sep 17 00:00:00 2001 From: Manuel Maier Date: Sun, 15 Nov 2015 17:42:49 +0100 Subject: [PATCH 18/23] Rename kr::GlobalGameLoop{ Registry => } Because by now its not just a registry anymore. --- code/krEngine/game/gameLoop.h | 14 +++++++--- .../game/implementation/defaultMainModule.cpp | 26 ++++++++--------- .../krEngine/game/implementation/gameLoop.cpp | 28 +++++++++---------- code/krGameLauncher/main.cpp | 4 +-- tests/krEngineTests/test_game.cpp | 22 +++++++-------- 5 files changed, 50 insertions(+), 44 deletions(-) diff --git a/code/krEngine/game/gameLoop.h b/code/krEngine/game/gameLoop.h index 59bcc32..3b48de5 100644 --- a/code/krEngine/game/gameLoop.h +++ b/code/krEngine/game/gameLoop.h @@ -7,12 +7,12 @@ namespace kr /// \brief Static class to register game loops with a name. /// /// \todo Dependency management. Something like \c depend(GameLoop* pLoop, GameLoop* pDependency);. Fail for cyclic deps. - class KR_ENGINE_API GlobalGameLoopRegistry + class KR_ENGINE_API GlobalGameLoop { - GlobalGameLoopRegistry() = delete; + GlobalGameLoop() = delete; public: - /// \brief Set or add a named game loop to the global game loop registry. + /// \brief Set or add a named loop in / to the global game loop. /// \return \c EZ_SUCCESS if the names game loop did not exist yet, \c EZ_FAILURE otherwise. static void set(ezStringView loopName, GameLoopCallback callback, ezLogInterface* pLogInterface = nullptr); @@ -26,11 +26,17 @@ namespace kr static ezResult remove(ezStringView loopName, ezLogInterface* pLogInterface = nullptr); /// \brief Set the priority for a named game loop. + /// + /// A higher priority value means that the loop is executed earlier. + /// /// \pre A game loop with the given name must already exist. /// \see set() static void setPriority(ezStringView loopName, ezInt32 priority, ezLogInterface* pLogInterface = nullptr); /// \brief Get the priority for a named game loop. + /// + /// A higher priority value means that the loop is executed earlier. + /// /// \pre A game loop with the given name must already exist. /// \see set() static ezInt32 getPriority(ezStringView loopName, ezLogInterface* pLogInterface = nullptr); @@ -54,7 +60,7 @@ namespace kr /// \brief Prints the order in which the registered callbacks are executed in for each tick, one per line. static void printTickOrder(ezLogInterface* pLogInterface = nullptr); - /// \brief Resets the internal state of the global game loop registry. + /// \brief Resets the internal state of the global game loop. /// \pre The global game loop must not be ticking when calling this function. static void reset(); }; diff --git a/code/krEngine/game/implementation/defaultMainModule.cpp b/code/krEngine/game/implementation/defaultMainModule.cpp index 6f45d84..94bd587 100644 --- a/code/krEngine/game/implementation/defaultMainModule.cpp +++ b/code/krEngine/game/implementation/defaultMainModule.cpp @@ -56,45 +56,45 @@ void kr::DefaultMainModule::OnEngineStartup() m_pWindow->getEvent().AddEventHandler([](const WindowEventArgs& e) { auto userRequestsClose{ e.type == WindowEventArgs::ClickClose }; - GlobalGameLoopRegistry::setKeepTicking(!userRequestsClose); + GlobalGameLoop::setKeepTicking(!userRequestsClose); }); auto pLog{ ezGlobalLog::GetInstance() }; - GlobalGameLoopRegistry::set("clock", [=]() + GlobalGameLoop::set("clock", [=]() { clock()->Update(); }, pLog); - GlobalGameLoopRegistry::setPriority("clock", DefaultGameLoopPriorities::Clock, pLog); + GlobalGameLoop::setPriority("clock", DefaultGameLoopPriorities::Clock, pLog); - GlobalGameLoopRegistry::set("tick", { &DefaultMainModule::tick, this }, pLog); + GlobalGameLoop::set("tick", { &DefaultMainModule::tick, this }, pLog); - GlobalGameLoopRegistry::set("message-pump", [=]() + GlobalGameLoop::set("message-pump", [=]() { kr::processWindowMessages(window()); }, pLog); - GlobalGameLoopRegistry::setPriority("message-pump", DefaultGameLoopPriorities::MessagePump, pLog); + GlobalGameLoop::setPriority("message-pump", DefaultGameLoopPriorities::MessagePump, pLog); - GlobalGameLoopRegistry::set("input", [=]() + GlobalGameLoop::set("input", [=]() { ezInputManager::Update(clock()->GetTimeDiff()); }, pLog); - GlobalGameLoopRegistry::setPriority("input", DefaultGameLoopPriorities::Input, pLog); + GlobalGameLoop::setPriority("input", DefaultGameLoopPriorities::Input, pLog); - GlobalGameLoopRegistry::set("rendering", [=]() + GlobalGameLoop::set("rendering", [=]() { kr::Renderer::extract(); kr::Renderer::update(window()); }, pLog); - GlobalGameLoopRegistry::setPriority("rendering", DefaultGameLoopPriorities::Rendering, pLog); + GlobalGameLoop::setPriority("rendering", DefaultGameLoopPriorities::Rendering, pLog); } void kr::DefaultMainModule::OnEngineShutdown() { auto pLog{ ezGlobalLog::GetInstance() }; - GlobalGameLoopRegistry::remove("message-pump", pLog); - GlobalGameLoopRegistry::remove("clock", pLog); - GlobalGameLoopRegistry::remove("tick", pLog); + GlobalGameLoop::remove("message-pump", pLog); + GlobalGameLoop::remove("clock", pLog); + GlobalGameLoop::remove("tick", pLog); if(m_pWindow->close().Failed()) { diff --git a/code/krEngine/game/implementation/gameLoop.cpp b/code/krEngine/game/implementation/gameLoop.cpp index 86fce5a..87f6c28 100644 --- a/code/krEngine/game/implementation/gameLoop.cpp +++ b/code/krEngine/game/implementation/gameLoop.cpp @@ -3,10 +3,10 @@ #include -EZ_BEGIN_SUBSYSTEM_DECLARATION(krEngine, GlobalGameLoopRegistry) +EZ_BEGIN_SUBSYSTEM_DECLARATION(krEngine, GlobalGameLoop) ON_CORE_SHUTDOWN { - kr::GlobalGameLoopRegistry::reset(); + kr::GlobalGameLoop::reset(); } EZ_END_SUBSYSTEM_DECLARATION @@ -61,7 +61,7 @@ GameLoop* internalGet(ezStringView loopName, ezLogInterface* pLogInterface) return nullptr; } -void kr::GlobalGameLoopRegistry::set(ezStringView loopName, GameLoopCallback callback, ezLogInterface* pLogInterface) +void kr::GlobalGameLoop::set(ezStringView loopName, GameLoopCallback callback, ezLogInterface* pLogInterface) { EZ_LOG_BLOCK(pLogInterface, "Add Global Game Loop", ezStringBuilder(loopName).GetData()); @@ -87,7 +87,7 @@ void kr::GlobalGameLoopRegistry::set(ezStringView loopName, GameLoopCallback cal g_gameLoopsNeedSorting = true; } -kr::GameLoopCallback* kr::GlobalGameLoopRegistry::get(ezStringView loopName, ezLogInterface* pLogInterface) +kr::GameLoopCallback* kr::GlobalGameLoop::get(ezStringView loopName, ezLogInterface* pLogInterface) { EZ_LOG_BLOCK(pLogInterface, "Get Global Game Loop", ezStringBuilder{ loopName }); @@ -95,7 +95,7 @@ kr::GameLoopCallback* kr::GlobalGameLoopRegistry::get(ezStringView loopName, ezL return ptr == nullptr ? nullptr : &ptr->callback; } -ezResult kr::GlobalGameLoopRegistry::remove(ezStringView loopName, ezLogInterface* pLogInterface /*= nullptr*/) +ezResult kr::GlobalGameLoop::remove(ezStringView loopName, ezLogInterface* pLogInterface /*= nullptr*/) { EZ_LOG_BLOCK(pLogInterface, "Removing Global Game Loop", ezStringBuilder(loopName)); @@ -104,7 +104,7 @@ ezResult kr::GlobalGameLoopRegistry::remove(ezStringView loopName, ezLogInterfac { if(all[i].name == loopName) { - if (GlobalGameLoopRegistry::isTicking()) + if (GlobalGameLoop::isTicking()) { // While ticking, we cannot simply remove the loop. We mark it as garbage and remove them once we're done ticking. all[i].isGarbage = true; @@ -124,7 +124,7 @@ ezResult kr::GlobalGameLoopRegistry::remove(ezStringView loopName, ezLogInterfac return EZ_FAILURE; } -void kr::GlobalGameLoopRegistry::setPriority(ezStringView loopName, ezInt32 priority, ezLogInterface* pLogInterface /*= nullptr*/) +void kr::GlobalGameLoop::setPriority(ezStringView loopName, ezInt32 priority, ezLogInterface* pLogInterface /*= nullptr*/) { EZ_LOG_BLOCK(pLogInterface, "Set Priority For Global Game Loop", ezStringBuilder(loopName)); @@ -139,7 +139,7 @@ void kr::GlobalGameLoopRegistry::setPriority(ezStringView loopName, ezInt32 prio g_gameLoopsNeedSorting = true; } -ezInt32 kr::GlobalGameLoopRegistry::getPriority(ezStringView loopName, ezLogInterface* pLogInterface /*= nullptr*/) +ezInt32 kr::GlobalGameLoop::getPriority(ezStringView loopName, ezLogInterface* pLogInterface /*= nullptr*/) { EZ_LOG_BLOCK(pLogInterface, "Get Priority of Global Game Loop", ezStringBuilder(loopName)); @@ -155,7 +155,7 @@ ezInt32 kr::GlobalGameLoopRegistry::getPriority(ezStringView loopName, ezLogInte static bool g_globalGameLoopIsTicking{ false }; -void kr::GlobalGameLoopRegistry::tick(ezLogInterface* pLogInterface) +void kr::GlobalGameLoop::tick(ezLogInterface* pLogInterface) { g_globalGameLoopIsTicking = true; KR_ON_SCOPE_EXIT{ g_globalGameLoopIsTicking = false; }; @@ -193,22 +193,22 @@ void kr::GlobalGameLoopRegistry::tick(ezLogInterface* pLogInterface) static bool g_keepGlobalGameLoopTicking{ true }; -bool kr::GlobalGameLoopRegistry::keepTicking() +bool kr::GlobalGameLoop::keepTicking() { return g_keepGlobalGameLoopTicking; } -void kr::GlobalGameLoopRegistry::setKeepTicking(bool value) +void kr::GlobalGameLoop::setKeepTicking(bool value) { g_keepGlobalGameLoopTicking = value; } -bool kr::GlobalGameLoopRegistry::isTicking() +bool kr::GlobalGameLoop::isTicking() { return g_globalGameLoopIsTicking; } -void kr::GlobalGameLoopRegistry::printTickOrder(ezLogInterface* pLogInterface /*= nullptr*/) +void kr::GlobalGameLoop::printTickOrder(ezLogInterface* pLogInterface /*= nullptr*/) { if (g_gameLoopsNeedSorting) { @@ -222,7 +222,7 @@ void kr::GlobalGameLoopRegistry::printTickOrder(ezLogInterface* pLogInterface /* } } -void kr::GlobalGameLoopRegistry::reset() +void kr::GlobalGameLoop::reset() { globalGameLoops().Clear(); g_gameLoopsNeedSorting = false; diff --git a/code/krGameLauncher/main.cpp b/code/krGameLauncher/main.cpp index b561761..7ae39d5 100644 --- a/code/krGameLauncher/main.cpp +++ b/code/krGameLauncher/main.cpp @@ -71,9 +71,9 @@ static int runGame(const ezStringBuilder& name) ezStartup::StartupEngine(); - while(kr::GlobalGameLoopRegistry::keepTicking()) + while(kr::GlobalGameLoop::keepTicking()) { - kr::GlobalGameLoopRegistry::tick(); + kr::GlobalGameLoop::tick(); } ezStartup::ShutdownEngine(); diff --git a/tests/krEngineTests/test_game.cpp b/tests/krEngineTests/test_game.cpp index e8202cc..97d5af6 100644 --- a/tests/krEngineTests/test_game.cpp +++ b/tests/krEngineTests/test_game.cpp @@ -14,27 +14,27 @@ TEST_CASE("Global Game Loop Registry", "[game]") SECTION("Basics") { - GlobalGameLoopRegistry::set("foo", [&]() { ++check1; }); - GlobalGameLoopRegistry::set("bar", [&]() { ++check2; }); + GlobalGameLoop::set("foo", [&]() { ++check1; }); + GlobalGameLoop::set("bar", [&]() { ++check2; }); - GlobalGameLoopRegistry::tick(); + GlobalGameLoop::tick(); REQUIRE(check1 == 1); REQUIRE(check2 == 1); - REQUIRE(GlobalGameLoopRegistry::remove("foo").Succeeded()); - REQUIRE(GlobalGameLoopRegistry::remove("bar").Succeeded()); - REQUIRE(GlobalGameLoopRegistry::remove("bar").Failed()); + REQUIRE(GlobalGameLoop::remove("foo").Succeeded()); + REQUIRE(GlobalGameLoop::remove("bar").Succeeded()); + REQUIRE(GlobalGameLoop::remove("bar").Failed()); } SECTION("Priority") { - GlobalGameLoopRegistry::set("bar", [&]() { REQUIRE(check1 == 1); ++check1; }); - GlobalGameLoopRegistry::set("foo", [&]() { REQUIRE(check1 == 0); ++check1; }); + GlobalGameLoop::set("bar", [&]() { REQUIRE(check1 == 1); ++check1; }); + GlobalGameLoop::set("foo", [&]() { REQUIRE(check1 == 0); ++check1; }); - GlobalGameLoopRegistry::setPriority("foo", 10); - GlobalGameLoopRegistry::setPriority("bar", -10); + GlobalGameLoop::setPriority("foo", 10); + GlobalGameLoop::setPriority("bar", -10); - GlobalGameLoopRegistry::tick(); + GlobalGameLoop::tick(); REQUIRE(check1 == 2); REQUIRE(check2 == 0); } From 49cd0566d72f3a46a792449d05bd5c81a02a5b29 Mon Sep 17 00:00:00 2001 From: Manuel Maier Date: Sun, 15 Nov 2015 18:01:26 +0100 Subject: [PATCH 19/23] Make GlobalGameLoop a namespace instead of a struct. Also add some docs for kr::DefaultMainModule::tick. --- code/krEngine/game/defaultMainModule.h | 11 +++- code/krEngine/game/gameLoop.h | 29 +++++----- .../game/implementation/defaultMainModule.cpp | 57 ++++++++++--------- .../krEngine/game/implementation/gameLoop.cpp | 8 +-- 4 files changed, 58 insertions(+), 47 deletions(-) diff --git a/code/krEngine/game/defaultMainModule.h b/code/krEngine/game/defaultMainModule.h index 52dafcb..73020c8 100644 --- a/code/krEngine/game/defaultMainModule.h +++ b/code/krEngine/game/defaultMainModule.h @@ -20,6 +20,7 @@ namespace kr Clock = -20, MessagePump = -10, Input = -5, + Module = 0, Rendering = 20, }; @@ -52,7 +53,15 @@ namespace kr virtual void OnCoreShutdown() override; public: // *** Runtime - virtual void tick(); + + /// \brief The module tick function. + /// + /// Override this to hook into the global game loop after the clock + /// and input has been processed. + /// + /// Alternatively, you can directly use kr::GlobalGameLoop to hook + /// into there. Use kr::DefaultGameLoopPriorities to set your own priority. + virtual void tick() {} public: // *** Properties Borrowed window() { return m_pWindow; } diff --git a/code/krEngine/game/gameLoop.h b/code/krEngine/game/gameLoop.h index 3b48de5..914fc30 100644 --- a/code/krEngine/game/gameLoop.h +++ b/code/krEngine/game/gameLoop.h @@ -2,28 +2,25 @@ namespace kr { - using GameLoopCallback = ezDelegate; + using GlobalGameLoopCallback = ezDelegate; /// \brief Static class to register game loops with a name. /// /// \todo Dependency management. Something like \c depend(GameLoop* pLoop, GameLoop* pDependency);. Fail for cyclic deps. - class KR_ENGINE_API GlobalGameLoop + namespace GlobalGameLoop { - GlobalGameLoop() = delete; - public: - /// \brief Set or add a named loop in / to the global game loop. /// \return \c EZ_SUCCESS if the names game loop did not exist yet, \c EZ_FAILURE otherwise. - static void set(ezStringView loopName, GameLoopCallback callback, ezLogInterface* pLogInterface = nullptr); + KR_ENGINE_API void set(ezStringView loopName, GlobalGameLoopCallback callback, ezLogInterface* pLogInterface = nullptr); /// \brief Get a registered game loop instance by name. /// \note It is not safe to store a pointer to the callback, as it might be moved in memory the next time set() or remove() is called. /// \return \c nullptr if the named game loop does not exist. - static GameLoopCallback* get(ezStringView loopName, ezLogInterface* pLogInterface = nullptr); + KR_ENGINE_API GlobalGameLoopCallback* get(ezStringView loopName, ezLogInterface* pLogInterface = nullptr); /// \brief Remove a registered game loop by name. /// \return \c EZ_SUCCESS if a game loop of the given name could be found. - static ezResult remove(ezStringView loopName, ezLogInterface* pLogInterface = nullptr); + KR_ENGINE_API ezResult remove(ezStringView loopName, ezLogInterface* pLogInterface = nullptr); /// \brief Set the priority for a named game loop. /// @@ -31,7 +28,7 @@ namespace kr /// /// \pre A game loop with the given name must already exist. /// \see set() - static void setPriority(ezStringView loopName, ezInt32 priority, ezLogInterface* pLogInterface = nullptr); + KR_ENGINE_API void setPriority(ezStringView loopName, ezInt32 priority, ezLogInterface* pLogInterface = nullptr); /// \brief Get the priority for a named game loop. /// @@ -39,29 +36,29 @@ namespace kr /// /// \pre A game loop with the given name must already exist. /// \see set() - static ezInt32 getPriority(ezStringView loopName, ezLogInterface* pLogInterface = nullptr); + KR_ENGINE_API ezInt32 getPriority(ezStringView loopName, ezLogInterface* pLogInterface = nullptr); /// \brief Ticks all registered game loops in order. - static void tick(ezLogInterface* pLogInterface = nullptr); + KR_ENGINE_API void tick(ezLogInterface* pLogInterface = nullptr); /// \brief Whether the global game loop should be ticked or not. /// \note This value is just a hint, so calls to tick() can still be made. /// \see setKeepTicking() - static bool keepTicking(); + KR_ENGINE_API bool keepTicking(); /// \brief Set whether the global game loop should be ticked or not. /// \note Calls to tick() can still be made. /// \see keepTicking() - static void setKeepTicking(bool value); + KR_ENGINE_API void setKeepTicking(bool value); /// \brief Whether the global game loop is currently ticking or not. - static bool isTicking(); + KR_ENGINE_API bool isTicking(); /// \brief Prints the order in which the registered callbacks are executed in for each tick, one per line. - static void printTickOrder(ezLogInterface* pLogInterface = nullptr); + KR_ENGINE_API void printTickOrder(ezLogInterface* pLogInterface = nullptr); /// \brief Resets the internal state of the global game loop. /// \pre The global game loop must not be ticking when calling this function. - static void reset(); + KR_ENGINE_API void reset(); }; } diff --git a/code/krEngine/game/implementation/defaultMainModule.cpp b/code/krEngine/game/implementation/defaultMainModule.cpp index 94bd587..cd69dcc 100644 --- a/code/krEngine/game/implementation/defaultMainModule.cpp +++ b/code/krEngine/game/implementation/defaultMainModule.cpp @@ -61,40 +61,58 @@ void kr::DefaultMainModule::OnEngineStartup() auto pLog{ ezGlobalLog::GetInstance() }; - GlobalGameLoop::set("clock", [=]() + // Clock + // ===== + auto clockTick = [=]() { clock()->Update(); - }, pLog); - GlobalGameLoop::setPriority("clock", DefaultGameLoopPriorities::Clock, pLog); + }; - GlobalGameLoop::set("tick", { &DefaultMainModule::tick, this }, pLog); + GlobalGameLoop::set("clock", clockTick, pLog); + GlobalGameLoop::setPriority("clock", DefaultGameLoopPriorities::Clock, pLog); - GlobalGameLoop::set("message-pump", [=]() + // Message Pump + // ============ + auto tickMessagePump = [=]() { kr::processWindowMessages(window()); - }, pLog); - GlobalGameLoop::setPriority("message-pump", DefaultGameLoopPriorities::MessagePump, pLog); + }; + GlobalGameLoop::set("messagePump", tickMessagePump, pLog); + GlobalGameLoop::setPriority("messagePump", DefaultGameLoopPriorities::MessagePump, pLog); - GlobalGameLoop::set("input", [=]() + // Input + // ===== + auto inputTick = [=]() { ezInputManager::Update(clock()->GetTimeDiff()); - }, pLog); + }; + GlobalGameLoop::set("input", inputTick, pLog); GlobalGameLoop::setPriority("input", DefaultGameLoopPriorities::Input, pLog); - GlobalGameLoop::set("rendering", [=]() + // Rendering + // ========= + auto renderingTick = [=]() { kr::Renderer::extract(); kr::Renderer::update(window()); - }, pLog); + }; + GlobalGameLoop::set("rendering", renderingTick, pLog); GlobalGameLoop::setPriority("rendering", DefaultGameLoopPriorities::Rendering, pLog); + + // Module Tick + // =========== + GlobalGameLoop::set("module", { &DefaultMainModule::tick, this }, pLog); + GlobalGameLoop::setPriority("module", DefaultGameLoopPriorities::Module, pLog); } void kr::DefaultMainModule::OnEngineShutdown() { auto pLog{ ezGlobalLog::GetInstance() }; - GlobalGameLoop::remove("message-pump", pLog); + GlobalGameLoop::remove("module", pLog); + GlobalGameLoop::remove("rendering", pLog); + GlobalGameLoop::remove("input", pLog); + GlobalGameLoop::remove("messagePump", pLog); GlobalGameLoop::remove("clock", pLog); - GlobalGameLoop::remove("tick", pLog); if(m_pWindow->close().Failed()) { @@ -108,16 +126,3 @@ void kr::DefaultMainModule::OnCoreShutdown() m_htmlLog.EndLog(); ezFileSystem::RemoveDataDirectoryGroup(""); } - -void kr::DefaultMainModule::tick() -{ - clock()->Update(); - auto dt = clock()->GetTimeDiff(); - // So far, the delta time is not used here. - EZ_IGNORE_UNUSED(dt); - - processWindowMessages(m_pWindow); - - kr::Renderer::extract(); - kr::Renderer::update(m_pWindow); -} diff --git a/code/krEngine/game/implementation/gameLoop.cpp b/code/krEngine/game/implementation/gameLoop.cpp index 87f6c28..172ab9b 100644 --- a/code/krEngine/game/implementation/gameLoop.cpp +++ b/code/krEngine/game/implementation/gameLoop.cpp @@ -18,9 +18,9 @@ namespace bool isGarbage{ false }; ezString name; ezInt32 priority{ 0 }; - kr::GameLoopCallback callback; + kr::GlobalGameLoopCallback callback; - GameLoop(ezStringView name, kr::GameLoopCallback cb) : name{ kr::move(name) }, callback{ kr::move(cb) } + GameLoop(ezStringView name, kr::GlobalGameLoopCallback cb) : name{ kr::move(name) }, callback{ kr::move(cb) } { } }; @@ -61,7 +61,7 @@ GameLoop* internalGet(ezStringView loopName, ezLogInterface* pLogInterface) return nullptr; } -void kr::GlobalGameLoop::set(ezStringView loopName, GameLoopCallback callback, ezLogInterface* pLogInterface) +void kr::GlobalGameLoop::set(ezStringView loopName, GlobalGameLoopCallback callback, ezLogInterface* pLogInterface) { EZ_LOG_BLOCK(pLogInterface, "Add Global Game Loop", ezStringBuilder(loopName).GetData()); @@ -87,7 +87,7 @@ void kr::GlobalGameLoop::set(ezStringView loopName, GameLoopCallback callback, e g_gameLoopsNeedSorting = true; } -kr::GameLoopCallback* kr::GlobalGameLoop::get(ezStringView loopName, ezLogInterface* pLogInterface) +kr::GlobalGameLoopCallback* kr::GlobalGameLoop::get(ezStringView loopName, ezLogInterface* pLogInterface) { EZ_LOG_BLOCK(pLogInterface, "Get Global Game Loop", ezStringBuilder{ loopName }); From f9bb5d56e8e82c87da5dbbf9af248a0fe866e5cb Mon Sep 17 00:00:00 2001 From: Manuel Maier Date: Tue, 17 Nov 2015 20:01:25 +0100 Subject: [PATCH 20/23] Proper Project Template We now have a proper project template (as opposed to having everything hardcoded as string within a single python script). Every files' name and the contents of the files that belong to the project template can use the substitution syntax ~{var_name}. The krGameLauncher is basically obsolete now because it is always generted to the new projects and will be called like the project with a `_launcher` suffix. --- build/CMake/project_template/.editorconfig | 25 ++ build/CMake/project_template/.gitignore | 9 + build/CMake/project_template/CMakeLists.txt | 33 +++ .../project_template/code/CMakeLists.txt | 2 + .../code/~{friendly_name}/CMakeLists.txt | 16 ++ .../code/~{friendly_name}/game.cpp | 11 + .../code/~{friendly_name}/pch.h | 1 + .../~{friendly_name}_launcher/CMakeLists.txt | 10 + .../code/~{friendly_name}_launcher/main.cpp | 170 +++++++++++++ create_krepel_project.py | 226 ++++++------------ 10 files changed, 345 insertions(+), 158 deletions(-) create mode 100644 build/CMake/project_template/.editorconfig create mode 100644 build/CMake/project_template/.gitignore create mode 100644 build/CMake/project_template/CMakeLists.txt create mode 100644 build/CMake/project_template/code/CMakeLists.txt create mode 100644 build/CMake/project_template/code/~{friendly_name}/CMakeLists.txt create mode 100644 build/CMake/project_template/code/~{friendly_name}/game.cpp create mode 100644 build/CMake/project_template/code/~{friendly_name}/pch.h create mode 100644 build/CMake/project_template/code/~{friendly_name}_launcher/CMakeLists.txt create mode 100644 build/CMake/project_template/code/~{friendly_name}_launcher/main.cpp diff --git a/build/CMake/project_template/.editorconfig b/build/CMake/project_template/.editorconfig new file mode 100644 index 0000000..5a99cc1 --- /dev/null +++ b/build/CMake/project_template/.editorconfig @@ -0,0 +1,25 @@ +# EditorConfig settings file. +# Check it out: http://editorconfig.org/ +# There is a plugin for visual studio, sublime text 2/3, notepad++, vim, and more + +# Editor config plugins will stop searching for any other .editorconfig file +# if the following is set to true +root = true + +# Use unix line endings for all files +[*] +# Unix style line endings (\\n) +end_of_line = lf + +# Use 2 spaces for indentation +indent_style = space +indent_size = 2 + +# At least 1 blank line at the end of the file +insert_final_newline = true + +# Use UTF-8 as encoding +charset = utf8 + +# Remove annoying white space at the end of a line +trim_trailing_whitespace = true diff --git a/build/CMake/project_template/.gitignore b/build/CMake/project_template/.gitignore new file mode 100644 index 0000000..443a286 --- /dev/null +++ b/build/CMake/project_template/.gitignore @@ -0,0 +1,9 @@ +# Note: To check in binaries or libs, use `git add --force /bin/whatever.dll`. +/bin/ +/lib/ + +# This is the intended place to generate CMake stuff in, i.e. the CMake binary dir. +/workspace/ + +# Windows thumbnails. +[tT]humbs.[dD][bB] diff --git a/build/CMake/project_template/CMakeLists.txt b/build/CMake/project_template/CMakeLists.txt new file mode 100644 index 0000000..d55885a --- /dev/null +++ b/build/CMake/project_template/CMakeLists.txt @@ -0,0 +1,33 @@ + +cmake_minimum_required(VERSION 3.2 FATAL_ERROR) + +# Name of the entire project. +project("~friendly_name") + +find_path(KREPEL_DIR lib/krEngine.lib + PATHS "$ENV{KREPEL_DIR}" "C:/Program Files/krepel" "$ENV{PATH}" + DOC "Path to the krepel installation." + NO_DEFAULT_PATH) + +if(NOT EXISTS "${KREPEL_DIR}") + message(FATAL_ERROR "Please specify the path to the krepel installation.") +endif() + +set(CMAKE_MODULE_PATH "${~{cmake_name}_DIR}/build/CMake" "${KREPEL_DIR}/build/CMake") +include(kr_setup) +kr_setup() + +include(platforms/${CMAKE_SYSTEM_NAME}) +include(targets/glew) +include(targets/ezEngine) +include(targets/krepel) + + +set(~{cmake_name}_DIR "${CMAKE_SOURCE_DIR}" CACHE PATH "Root directory of ~{friendly_name}.") +mark_as_advanced(~{cmake_name}_DIR) + +include_directories("code") +add_subdirectory("code") + +include(kr_create_target_pullRuntimeBinaries) +kr_create_target_pullRuntimeBinaries("${~{cmake_name}_DIR}/bin" "${KREPEL_DIR}/bin/") diff --git a/build/CMake/project_template/code/CMakeLists.txt b/build/CMake/project_template/code/CMakeLists.txt new file mode 100644 index 0000000..c9f3922 --- /dev/null +++ b/build/CMake/project_template/code/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory("~{friendly_name}_launcher") +add_subdirectory("~{friendly_name}") diff --git a/build/CMake/project_template/code/~{friendly_name}/CMakeLists.txt b/build/CMake/project_template/code/~{friendly_name}/CMakeLists.txt new file mode 100644 index 0000000..921b58e --- /dev/null +++ b/build/CMake/project_template/code/~{friendly_name}/CMakeLists.txt @@ -0,0 +1,16 @@ +include(kr_set_pch) +include(kr_mirror_source_tree) + +# Source Files +# ============ +file(GLOB_RECURSE SOURCES *.h *.inl *.cpp) + +# Target Setup +# ============ +add_library(~{friendly_name} SHARED ${SOURCES}) +target_include_directories(~{friendly_name} PUBLIC ../../code) +target_include_directories(~{friendly_name} PUBLIC ..) +kr_set_pch(~{friendly_name} "pch.h") +kr_mirror_source_tree("${CMAKE_CURRENT_LIST_DIR}" ${SOURCES}) + +target_link_libraries(~{friendly_name} krEngine) diff --git a/build/CMake/project_template/code/~{friendly_name}/game.cpp b/build/CMake/project_template/code/~{friendly_name}/game.cpp new file mode 100644 index 0000000..6a6bbf6 --- /dev/null +++ b/build/CMake/project_template/code/~{friendly_name}/game.cpp @@ -0,0 +1,11 @@ +#include + +class ~{friendly_name}_module : public kr::DefaultMainModule +{ +public: + ~{friendly_name}_module() + { + m_windowDesc.m_Title = "~{friendly_name} - Powered by krepel"; + } + +} s_mainModule; // <-- Static instance is required. diff --git a/build/CMake/project_template/code/~{friendly_name}/pch.h b/build/CMake/project_template/code/~{friendly_name}/pch.h new file mode 100644 index 0000000..ee11d00 --- /dev/null +++ b/build/CMake/project_template/code/~{friendly_name}/pch.h @@ -0,0 +1 @@ +#include diff --git a/build/CMake/project_template/code/~{friendly_name}_launcher/CMakeLists.txt b/build/CMake/project_template/code/~{friendly_name}_launcher/CMakeLists.txt new file mode 100644 index 0000000..a8c882c --- /dev/null +++ b/build/CMake/project_template/code/~{friendly_name}_launcher/CMakeLists.txt @@ -0,0 +1,10 @@ +include(kr_mirror_source_tree) + +file(GLOB_RECURSE SOURCES *.h *.inl *.cpp) + +add_executable(~{friendly_name}_launcher ${SOURCES}) +kr_mirror_source_tree("${CMAKE_CURRENT_LIST_DIR}" ${SOURCES}) + +target_link_libraries(~{friendly_name}_launcher krEngine) + +add_dependencies(~{friendly_name}_launcher ~{friendly_name}) diff --git a/build/CMake/project_template/code/~{friendly_name}_launcher/main.cpp b/build/CMake/project_template/code/~{friendly_name}_launcher/main.cpp new file mode 100644 index 0000000..a113235 --- /dev/null +++ b/build/CMake/project_template/code/~{friendly_name}_launcher/main.cpp @@ -0,0 +1,170 @@ +#include +#include +#include + +#include +#include +#include +#include +#include + +#define DEFAULT_MODULE_NAME "~{friendly_name}" + +namespace +{ + struct JsonFileReader + { + ezOSFile file; + ezJSONReader reader; + }; +} + +// pData may NOT be a nullptr. +static ezStringBuilder extractValue(const ezVariantDictionary* pData, const char* key) +{ + EZ_ASSERT_DEBUG(pData != nullptr, "Unexpected nullptr."); + + ezVariant value; + if(!pData->TryGetValue(key, value)) + { + ezLog::Error("Unable to find key '%s'.", key); + return ezStringBuilder(); + } + + if(value.GetType() != ezVariantType::String) + { + ezLog::Error("Expected a string value for the key '%s'.", key); + return ezStringBuilder(); + } + + return ezStringBuilder(value.Get()); +} + +// pData may be a nullptr. +static ezStringBuilder extractValue(const ezCommandLineUtils& cmd, const ezVariantDictionary* pData, const char* key) +{ + ezStringBuilder gamePath = cmd.GetStringOption(key); + + if(gamePath.IsEmpty() && pData != nullptr) + { + // Game path was not specified via command line switch. + gamePath = extractValue(pData, key); + } + + return gamePath; +} + +static ezResult loadGame(const ezStringBuilder& name) +{ + EZ_LOG_BLOCK("Loading Game", name); + + return ezPlugin::LoadPlugin(name); +} + +static ezResult unloadGame(const ezStringBuilder& name) +{ + EZ_LOG_BLOCK("Unloading Game", name); + + return ezPlugin::UnloadPlugin(name); +} + +static int runGame(const ezStringBuilder& name) +{ + EZ_LOG_BLOCK("Running Game", name); + + ezStartup::StartupEngine(); + + while(kr::GlobalGameLoop::keepTicking()) + { + kr::GlobalGameLoop::tick(); + } + + ezStartup::ShutdownEngine(); + + return 0; +} + +int main(int argc, char* argv[]) +{ + ezGlobalLog::AddLogWriter(ezLogWriter::Console::LogMessageHandler); + KR_ON_SCOPE_EXIT{ ezGlobalLog::RemoveLogWriter(ezLogWriter::Console::LogMessageHandler); }; + ezGlobalLog::AddLogWriter(ezLogWriter::VisualStudio::LogMessageHandler); + KR_ON_SCOPE_EXIT{ ezGlobalLog::RemoveLogWriter(ezLogWriter::VisualStudio::LogMessageHandler); }; + + ezStartup::StartupCore(); + KR_ON_SCOPE_EXIT{ ezStartup::ShutdownCore(); }; + + ezCommandLineUtils cmd; + cmd.SetCommandLine(argc, const_cast(argv)); + + ezStringBuilder configFileName{ cmd.GetStringOption("--config") }; + + if(configFileName.IsEmpty()) + { + configFileName = "config.json"; + } + + const ezVariantDictionary* pData{ nullptr }; + ezExtendedJSONReader jsonReader; + ezOSFile jsonFile; + + if (jsonFile.Open(configFileName, ezFileMode::Read).Failed()) + { + ezLog::Info("Unable to open JSON config file."); + } + else + { + auto fileData{ EZ_DEFAULT_NEW_ARRAY(ezUInt8, static_cast(jsonFile.GetFileSize())) }; + jsonFile.Read(fileData.GetPtr(), fileData.GetCount()); + + { + ezMemoryStreamStorage mem{ fileData.GetCount() }; + ezMemoryStreamWriter{ &mem }.WriteBytes(fileData.GetPtr(), fileData.GetCount()); + if(jsonReader.Parse(ezMemoryStreamReader{ &mem }).Failed()) + { + ezLog::SeriousWarning("Discarding config file due to error when parsing. Check for a valid JSON format."); + } + else + { + pData = &jsonReader.GetTopLevelObject(); + } + } + } + + auto gameName{ extractValue(cmd, pData, "--game") }; + if(gameName.IsEmpty()) + { + gameName = DEFAULT_MODULE_NAME; + } + + auto gameModuleName{ gameName }; + gameModuleName.Append(kr::configPostfix()); + + auto result{ 0 }; + + if(loadGame(gameModuleName).Failed()) + { + ezLog::Error("Failed to load game."); + result = 1; + } + + if (result == 0) + { + // Now that the game module is loaded, re-init to Core again so the game module also gets the event. + ezStartup::ReinitToCurrentState(); + + auto result{ runGame(gameModuleName) }; + if(result != 0) + { + return result; + } + } + + if(unloadGame(gameModuleName).Failed()) + { + ezLog::Error("Failed to unload game."); + result = 1; + } + + return result; +} diff --git a/create_krepel_project.py b/create_krepel_project.py index ab26eb6..09beb36 100644 --- a/create_krepel_project.py +++ b/create_krepel_project.py @@ -8,128 +8,12 @@ import subprocess import os import stat +import string -gitignoreTemplate = """ -# Note: To check in binaries or libs, use `git add --force /bin/whatever.dll`. -/bin/ -/lib/ -# This is the intended place to generate CMake stuff in, i.e. the CMake binary dir. -/workspace/ +class ProjectTemplate(string.Template): + delimiter = "~" -# Windows thumbnails. -[tT]humbs.[dD][bB] -""" - -editorconfigTemplate = """ -# EditorConfig settings file. -# Check it out: http://editorconfig.org/ -# There is a plugin for visual studio, sublime text 2/3, notepad++, vim, and more - -# Editor config plugins will stop searching for any other .editorconfig file -# if the following is set to true -root = true - -# Use unix line endings for all files -[*] -# Unix style line endings (\\n) -end_of_line = lf - -# Use 2 spaces for indentation -indent_style = space -indent_size = 2 - -# At least 1 blank line at the end of the file -insert_final_newline = true - -# Use UTF-8 as encoding -charset = utf8 - -# Remove annoying white space at the end of a line -trim_trailing_whitespace = true -""" - -toplevelCMakeListsTemplate = """ -cmake_minimum_required(VERSION 3.2 FATAL_ERROR) - -# Name of the entire project. -project("{friendly_name}") - -find_path(KREPEL_DIR lib/krEngine.lib - PATHS "$ENV{{KREPEL_DIR}}" "C:/Program Files/krepel" "$ENV{{PATH}}" - DOC "Path to the krepel installation." - NO_DEFAULT_PATH) - -if(NOT EXISTS "${{KREPEL_DIR}}") - message(FATAL_ERROR "Please specify the path to the krepel installation.") -endif() - -set(CMAKE_MODULE_PATH "${{{cmake_name}_DIR}}/build/CMake" "${{KREPEL_DIR}}/build/CMake") -include(kr_setup) -kr_setup() - -include(platforms/${{CMAKE_SYSTEM_NAME}}) -include(targets/glew) -include(targets/ezEngine) -include(targets/krepel) - - -set({cmake_name}_DIR "${{CMAKE_SOURCE_DIR}}" CACHE PATH "Root directory of {friendly_name}.") -mark_as_advanced({cmake_name}_DIR) - - -include_directories("code") -add_subdirectory("code") - -kr_post_config() -""" - -codeCMakeListsTemplate = """ -add_subdirectory("{cmake_name}") -""" - -mainprojectCMakeListsTemplate = """include(kr_set_pch) -include(kr_mirror_source_tree) - -# Source Files -# ============ -file(GLOB_RECURSE SOURCES *.h *.inl *.cpp) - -# Target Setup -# ============ -add_executable({cmake_name} ${{SOURCES}}) -target_include_directories({cmake_name} PUBLIC ../../code) -target_include_directories({cmake_name} PUBLIC ..) -kr_set_pch({cmake_name} "pch.h") -kr_mirror_source_tree("${{CMAKE_CURRENT_LIST_DIR}}" ${{SOURCES}}) - -# Dependencies -# ============ -find_package(OpenGL REQUIRED) -if(OPENGL_INCLUDE_DIR) - # Needed on non-windows platforms (I think). - target_include_directories({cmake_name} PUBLIC ${{OPENGL_INCLUDE_DIR}}) -endif() - -target_link_libraries({cmake_name} - krEngine - ${{OPENGL_LIBRARIES}}) -""" - -mainStubCpp = """#include - -int main(int argc, char* argv[]) -{{ - std::printf("Hello {friendly_name}!\\n"); - return 0; -}} -""" -pchH = """#pragma once - -// krepel - krEngine -#include -#include -""" def get_args(): """Get arguments to this script.""" @@ -143,8 +27,13 @@ def get_args(): parser.add_argument("--force", action="store_true", help="If set, always assume 'yes' for prompts.") + parser.add_argument("-k", "--krepel-dir", + type=Path, + default=os.environ.get("KREPEL_DIR"), + help="The path to the krepel directory. This is where the project template is taken from.") return parser.parse_args() + def prompt_user_bool(question, *, max_attempts=3): """Prompt the user with a yes/no question.""" for _ in range(max_attempts): @@ -158,6 +47,7 @@ def prompt_user_bool(question, *, max_attempts=3): "Assuming 'no' is your answer.".format(max_attempts)) return False + def rm_tree(top): """Equivalent to `rm -rf `.""" for root, dirs, files in os.walk(top.as_posix(), topdown=False): @@ -169,43 +59,54 @@ def rm_tree(top): os.rmdir(os.path.join(root, name)) os.rmdir(top.as_posix()) -def create_editorconfig_file(): - """Create the .editorconfig file""" - with Path(".editorconfig").open("w") as outfile: - outfile.write(editorconfigTemplate) - -def create_toplevel_cmakelists_file(friendly_name, cmake_name): - """Create the top-level CMakeLists.txt file""" - with Path("CMakeLists.txt").open("w") as outfile: - outfile.write(toplevelCMakeListsTemplate.format(friendly_name=friendly_name, cmake_name=cmake_name.upper())) - -def create_code_and_mainproject_cmakelists_file(friendly_name, cmake_name): - """Create the CMakeLists.txt files in :/code and :/code/""" - code_dir = Path("code") - code_dir.mkdir(parents=True) - with (code_dir / "CMakeLists.txt").open("w") as outfile: - outfile.write(codeCMakeListsTemplate.format(cmake_name=cmake_name)) - project_dir = code_dir / cmake_name - project_dir.mkdir(parents=True) - with (project_dir / "CMakeLists.txt").open("w") as outfile: - outfile.write(mainprojectCMakeListsTemplate.format(cmake_name=cmake_name)) - with (project_dir / "main.cpp").open("w") as outfile: - outfile.write(mainStubCpp.format(friendly_name=friendly_name, cmake_name=cmake_name)) - with (project_dir / "pch.h").open("w") as outfile: - outfile.write(pchH) - -def create_gitignore_file(): - """Create the .gitignore file""" - with Path(".gitignore").open("w") as outfile: - outfile.write(gitignoreTemplate) + +def transform_template_file(*, file_path, dest_path, substitution_mapping): + """Read a file, format the contents, and write to the destination.""" + if not dest_path.parent.exists(): + dest_path.parent.mkdir(parents=True) + assert dest_path.parent.is_dir(), "Given destination path is not a directory!" + + content = "" + with open(file_path.as_posix(), "r", encoding="utf-8") as the_file: + content = the_file.read() + content = ProjectTemplate(content).substitute(substitution_mapping) + with open(dest_path.as_posix(), "w", encoding="utf-8") as the_file: + the_file.write(content) + + +def all_files_in_tree(root): + """Get all files within a path. They will be relative to that path.""" + all_files = [] + for parent, dirs, files in os.walk(root.as_posix(), topdown=False): + # Make the given parent relative to `root`. + parent = Path(parent).relative_to(root) + + # Add all files in this directory, relative to the parent. + all_files.extend(parent / the_file for the_file in files) + + # Add all files in the directories below this one. + for the_dir in dirs: + all_files.extend(all_files_in_tree(Path(the_dir))) + + return all_files + def main(): """Entry point.""" args = get_args() dest = args.dest_dir - # Replace all whitespaces with _: Hello World => Hello_World - friendly_name = args.name or dest.name - cmake_name = "_".join(friendly_name.split()) + + # Replace all whitespaces with _ and make it upper case: + # Hello World => Hello_World + friendly_name = "_".join((args.name or dest.name).split()) + + # Upper case the friendly_name for use as CMake variable names. + # Hello_World => HELLO_WORLD + cmake_name = friendly_name.upper() + + krepel_dir = args.krepel_dir.resolve() + project_template_dir = (krepel_dir / "build" / "CMake" / "project_template").resolve() + print("Creating project {}".format(friendly_name)) if dest.is_file(): print("Destination dir already exists and is a file! Aborting.") @@ -218,19 +119,28 @@ def main(): dest = dest.resolve() os.chdir(dest.as_posix()) - # .editorconfig - create_editorconfig_file() + substitution_mapping = { + "friendly_name" : friendly_name, + "cmake_name" : cmake_name, + "krepel_dir" : krepel_dir, + "project_template_dir" : project_template_dir, + "dest_dir" : dest, + } - # CMakeLists.txt - create_toplevel_cmakelists_file(friendly_name, cmake_name) - create_code_and_mainproject_cmakelists_file(friendly_name, cmake_name) + all_files = all_files_in_tree(project_template_dir) - # .gitignore - create_gitignore_file() + for the_file in all_files: + file_path = (project_template_dir / the_file).resolve() + dest_path = dest / ProjectTemplate(the_file.as_posix()).substitute(substitution_mapping) + print("", file_path, "=>", dest_path) + transform_template_file(file_path=file_path, + dest_path=dest_path, + substitution_mapping=substitution_mapping) print("Your project has been created.") print("Please use CMake to generate a build system for your new project.") print("Full path: {}".format(dest.as_posix())) + if __name__ == "__main__": main() From 77970363f0da9e6202836b4f63526f143cece591 Mon Sep 17 00:00:00 2001 From: Manuel Maier Date: Tue, 17 Nov 2015 19:03:06 +0000 Subject: [PATCH 21/23] Remove krGameLauncher as well as GameTest1 which depended on it. --- code/CMakeLists.txt | 1 - code/krGameLauncher/CMakeLists.txt | 25 ----- code/krGameLauncher/main.cpp | 168 ----------------------------- code/krGameLauncher/pch.h | 3 - tests/krGameTest1/CMakeLists.txt | 19 ---- tests/krGameTest1/game.cpp | 13 --- tests/krGameTest1/pch.h | 3 - 7 files changed, 232 deletions(-) delete mode 100644 code/krGameLauncher/CMakeLists.txt delete mode 100644 code/krGameLauncher/main.cpp delete mode 100644 code/krGameLauncher/pch.h delete mode 100644 tests/krGameTest1/CMakeLists.txt delete mode 100644 tests/krGameTest1/game.cpp delete mode 100644 tests/krGameTest1/pch.h diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 55c776a..ffd71cd 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -1,2 +1 @@ add_subdirectory("krEngine") -add_subdirectory("krGameLauncher") diff --git a/code/krGameLauncher/CMakeLists.txt b/code/krGameLauncher/CMakeLists.txt deleted file mode 100644 index 4b88fa3..0000000 --- a/code/krGameLauncher/CMakeLists.txt +++ /dev/null @@ -1,25 +0,0 @@ -include(kr_set_pch) -include(kr_mirror_source_tree) - -# Source Files -# ============ -file(GLOB_RECURSE SOURCES *.h *.inl *.cpp) - -# Target Setup -# ============ -add_executable(krGameLauncher ${SOURCES}) -set_target_properties(krGameLauncher PROPERTIES - FOLDER engine) -target_include_directories(krGameLauncher PUBLIC ..) -kr_set_pch(krGameLauncher "pch.h") -kr_mirror_source_tree("${CMAKE_CURRENT_LIST_DIR}" ${SOURCES}) - -# Dependencies -# ============ - -target_link_libraries(krGameLauncher krEngine) - -# Install Target -# ============== -include(kr_install_target) -kr_install_target(krGameLauncher) diff --git a/code/krGameLauncher/main.cpp b/code/krGameLauncher/main.cpp deleted file mode 100644 index 7ae39d5..0000000 --- a/code/krGameLauncher/main.cpp +++ /dev/null @@ -1,168 +0,0 @@ -#include -#include - -#include -#include -#include -#include -#include - -namespace -{ - struct JsonFileReader - { - ezOSFile file; - ezJSONReader reader; - }; -} - -// pData may NOT be a nullptr. -static ezStringBuilder extractValue(const ezVariantDictionary* pData, const char* key) -{ - EZ_ASSERT_DEBUG(pData != nullptr, "Unexpected nullptr."); - - ezVariant value; - if(!pData->TryGetValue(key, value)) - { - ezLog::Error("Unable to find key '%s'.", key); - return ezStringBuilder(); - } - - if(value.GetType() != ezVariantType::String) - { - ezLog::Error("Expected a string value for the key '%s'.", key); - return ezStringBuilder(); - } - - return ezStringBuilder(value.Get()); -} - -// pData may be a nullptr. -static ezStringBuilder extractValue(const ezCommandLineUtils& cmd, const ezVariantDictionary* pData, const char* key) -{ - ezStringBuilder gamePath = cmd.GetStringOption(key); - - if(gamePath.IsEmpty() && pData != nullptr) - { - // Game path was not specified via command line switch. - gamePath = extractValue(pData, key); - } - - return gamePath; -} - -static ezResult loadGame(const ezStringBuilder& name) -{ - EZ_LOG_BLOCK("Loading Game", name); - - return ezPlugin::LoadPlugin(name); -} - -static ezResult unloadGame(const ezStringBuilder& name) -{ - EZ_LOG_BLOCK("Unloading Game", name); - - return ezPlugin::UnloadPlugin(name); -} - -static int runGame(const ezStringBuilder& name) -{ - EZ_LOG_BLOCK("Running Game", name); - - ezStartup::StartupEngine(); - - while(kr::GlobalGameLoop::keepTicking()) - { - kr::GlobalGameLoop::tick(); - } - - ezStartup::ShutdownEngine(); - - return 0; -} - -int main(int argc, char* argv[]) -{ - ezGlobalLog::AddLogWriter(ezLogWriter::Console::LogMessageHandler); - KR_ON_SCOPE_EXIT{ ezGlobalLog::RemoveLogWriter(ezLogWriter::Console::LogMessageHandler); }; - ezGlobalLog::AddLogWriter(ezLogWriter::VisualStudio::LogMessageHandler); - KR_ON_SCOPE_EXIT{ ezGlobalLog::RemoveLogWriter(ezLogWriter::VisualStudio::LogMessageHandler); }; - - ezStartup::StartupCore(); - KR_ON_SCOPE_EXIT{ ezStartup::ShutdownCore(); }; - - ezCommandLineUtils cmd; - cmd.SetCommandLine(argc, const_cast(argv)); - - ezStringBuilder configFileName{ cmd.GetStringOption("--config") }; - - if(configFileName.IsEmpty()) - { - configFileName = "config.json"; - } - - const ezVariantDictionary* pData{ nullptr }; - ezExtendedJSONReader jsonReader; - ezOSFile jsonFile; - - if (jsonFile.Open(configFileName, ezFileMode::Read).Failed()) - { - ezLog::Info("Unable to open JSON config file."); - } - else - { - auto fileData{ EZ_DEFAULT_NEW_ARRAY(ezUInt8, static_cast(jsonFile.GetFileSize())) }; - jsonFile.Read(fileData.GetPtr(), fileData.GetCount()); - - { - ezMemoryStreamStorage mem{ fileData.GetCount() }; - ezMemoryStreamWriter{ &mem }.WriteBytes(fileData.GetPtr(), fileData.GetCount()); - if(jsonReader.Parse(ezMemoryStreamReader{ &mem }).Failed()) - { - ezLog::SeriousWarning("Discarding config file due to error when parsing. Check for a valid JSON format."); - } - else - { - pData = &jsonReader.GetTopLevelObject(); - } - } - } - - auto gameName{ extractValue(cmd, pData, "--game") }; - if(gameName.IsEmpty()) - { - ezLog::Error("No game module specified. use either --game on the command line or \"game\" = \"\" in the config file."); - return -1; - } - - auto gameModuleName{ gameName }; - gameModuleName.Append(kr::configPostfix()); - - auto result{ 0 }; - - if(loadGame(gameModuleName).Failed()) - { - ezLog::Error("Failed to load game."); - result = 1; - } - - if (result == 0) - { - // Now that the game module is loaded, re-init to Core again so the game module also gets the event. - ezStartup::ReinitToCurrentState(); - - auto result{ runGame(gameModuleName) }; - if(result != 0) - { - return result; - } - } - - if(unloadGame(gameModuleName).Failed()) - { - ezLog::Error("Failed to unload game."); - result = 1; - } - - return result; -} diff --git a/code/krGameLauncher/pch.h b/code/krGameLauncher/pch.h deleted file mode 100644 index 327ad9e..0000000 --- a/code/krGameLauncher/pch.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -#include diff --git a/tests/krGameTest1/CMakeLists.txt b/tests/krGameTest1/CMakeLists.txt deleted file mode 100644 index 366e0db..0000000 --- a/tests/krGameTest1/CMakeLists.txt +++ /dev/null @@ -1,19 +0,0 @@ -include(kr_set_pch) -include(kr_mirror_source_tree) - -# Source Files -# ============ -file(GLOB_RECURSE SOURCES *.h *.inl *.cpp) - -# Target Setup -# ============ -add_library(krGameTest1 SHARED ${SOURCES}) -set_target_properties(krGameTest1 PROPERTIES - FOLDER tests) -target_include_directories(krGameTest1 PUBLIC ../../code) -target_include_directories(krGameTest1 PUBLIC ..) -kr_set_pch(krGameTest1 "pch.h") -kr_mirror_source_tree("${CMAKE_CURRENT_LIST_DIR}" ${SOURCES}) - -target_link_libraries(krGameTest1 krEngine) -add_dependencies(krGameTest1 krGameLauncher) diff --git a/tests/krGameTest1/game.cpp b/tests/krGameTest1/game.cpp deleted file mode 100644 index fc78331..0000000 --- a/tests/krGameTest1/game.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include - -class krGameTest1Module : public kr::DefaultMainModule -{ -public: - krGameTest1Module() - { - m_windowDesc.m_Title = "krGameTest1"; - - EZ_VERIFY(ezFileSystem::AddDataDirectory(kr::makePath(kr::defaultRoot(), "testData", "textures" ), ezFileSystem::ReadOnly, "", "textures").Succeeded(), - "Failed to mount textures directory."); - } -} static g_mainModule; // <-- A static instance is mandatory! diff --git a/tests/krGameTest1/pch.h b/tests/krGameTest1/pch.h deleted file mode 100644 index d44b223..0000000 --- a/tests/krGameTest1/pch.h +++ /dev/null @@ -1,3 +0,0 @@ - -#include -#include From cd4220df4867f543f404def3bd0465b2e5b0eac2 Mon Sep 17 00:00:00 2001 From: Manuel Maier Date: Tue, 17 Nov 2015 20:07:10 +0100 Subject: [PATCH 22/23] Remove last remnants of krGameLauncher. --- build/CMake/kr_target_make_launchable.cmake | 17 ----------------- build/CMake/launchableGame.vcxproj.user.in | 8 -------- build/CMake/pullRuntimeBinaries.cmake.in | 1 - build/CMake/targets/krepel.cmake | 14 -------------- tests/CMakeLists.txt | 1 - 5 files changed, 41 deletions(-) delete mode 100644 build/CMake/kr_target_make_launchable.cmake delete mode 100644 build/CMake/launchableGame.vcxproj.user.in diff --git a/build/CMake/kr_target_make_launchable.cmake b/build/CMake/kr_target_make_launchable.cmake deleted file mode 100644 index 89018e1..0000000 --- a/build/CMake/kr_target_make_launchable.cmake +++ /dev/null @@ -1,17 +0,0 @@ - -function(_kr_target_make_launchable_msvc TARGET_NAME KREPEL_GAME_LAUNCHER) - configure_file("${KREPEL_DIR}/build/CMake/launchableGame.vcxproj.user.in" - "${CMAKE_BINARY_DIR}/code/${TARGET_NAME}/${TARGET_NAME}.vcxproj.user" - @ONLY) -endfunction() - -function(kr_target_make_launchable TARGET_NAME) - find_program(KREPEL_GAME_LAUNCHER - krGameLauncher - HINTS ${CMAKE_SOURCE_DIR}/bin) - if(MSVC) - _kr_target_make_launchable_msvc(${TARGET_NAME} ${KREPEL_GAME_LAUNCHER}) - else() - message(WARNING "Making a target launchable is not supported on this platform.") - endif() -endfunction() diff --git a/build/CMake/launchableGame.vcxproj.user.in b/build/CMake/launchableGame.vcxproj.user.in deleted file mode 100644 index a9bb86b..0000000 --- a/build/CMake/launchableGame.vcxproj.user.in +++ /dev/null @@ -1,8 +0,0 @@ - - - - @KREPEL_GAME_LAUNCHER@ - WindowsLocalDebugger - --game @TARGET_NAME@ - - diff --git a/build/CMake/pullRuntimeBinaries.cmake.in b/build/CMake/pullRuntimeBinaries.cmake.in index f336dc5..529515a 100644 --- a/build/CMake/pullRuntimeBinaries.cmake.in +++ b/build/CMake/pullRuntimeBinaries.cmake.in @@ -1,6 +1,5 @@ set(CMAKE_INSTALL_MESSAGE "LAZY") file(INSTALL @PULL_PATH@ DESTINATION "@PULL_DESTINATION@" - PATTERN "krGameLauncher.exe" PATTERN "*.dll" PATTERN "*.pdb" ) diff --git a/build/CMake/targets/krepel.cmake b/build/CMake/targets/krepel.cmake index 71878e5..1008afe 100644 --- a/build/CMake/targets/krepel.cmake +++ b/build/CMake/targets/krepel.cmake @@ -14,17 +14,3 @@ set_property(TARGET IMPORTED_krEngine PROPERTY IMPORTED_IMPLIB_RELWITHDEBINFO add_library(krEngine INTERFACE) target_link_libraries(krEngine INTERFACE IMPORTED_krEngine glew;ezThirdParty;ezFoundation;ezCore;ezCoreUtils;ezSystem;glu32;opengl32) target_include_directories(krEngine INTERFACE "${KREPEL_DIR}/code") - -# Target: krGameLauncher -add_library(IMPORTED_krGameLauncher SHARED IMPORTED GLOBAL) -set_property(TARGET IMPORTED_krGameLauncher PROPERTY IMPORTED_LOCATION_DEBUG "${KREPEL_DIR}/bin/krGameLauncher-debug.dll") -set_property(TARGET IMPORTED_krGameLauncher PROPERTY IMPORTED_IMPLIB_DEBUG "${KREPEL_DIR}/lib/krGameLauncher-debug.lib") -set_property(TARGET IMPORTED_krGameLauncher PROPERTY IMPORTED_LOCATION_RELEASE "${KREPEL_DIR}/bin/krGameLauncher.dll") -set_property(TARGET IMPORTED_krGameLauncher PROPERTY IMPORTED_IMPLIB_RELEASE "${KREPEL_DIR}/lib/krGameLauncher.lib") -set_property(TARGET IMPORTED_krGameLauncher PROPERTY IMPORTED_LOCATION_MINSIZEREL "${KREPEL_DIR}/bin/krGameLauncher-minsize.dll") -set_property(TARGET IMPORTED_krGameLauncher PROPERTY IMPORTED_IMPLIB_MINSIZEREL "${KREPEL_DIR}/lib/krGameLauncher-minsize.lib") -set_property(TARGET IMPORTED_krGameLauncher PROPERTY IMPORTED_LOCATION_RELWITHDEBINFO "${KREPEL_DIR}/bin/krGameLauncher-reldeb.dll") -set_property(TARGET IMPORTED_krGameLauncher PROPERTY IMPORTED_IMPLIB_RELWITHDEBINFO "${KREPEL_DIR}/lib/krGameLauncher-reldeb.lib") -add_library(krGameLauncher INTERFACE) -target_link_libraries(krGameLauncher INTERFACE IMPORTED_krGameLauncher krEngine) -target_include_directories(krGameLauncher INTERFACE "${KREPEL_DIR}/code") diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 548e7ec..24e2d24 100755 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,2 +1 @@ add_subdirectory("krEngineTests") -add_subdirectory("krGameTest1") From b5b909d99dd4f762a92edd2dc5d210ac13149d0a Mon Sep 17 00:00:00 2001 From: Manuel Maier Date: Wed, 18 Nov 2015 23:47:19 +0000 Subject: [PATCH 23/23] Add default 2D camera. --- code/krEngine/game/camera.h | 24 +++++++++++++++ code/krEngine/game/implementation/camera.cpp | 31 ++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 code/krEngine/game/camera.h create mode 100644 code/krEngine/game/implementation/camera.cpp diff --git a/code/krEngine/game/camera.h b/code/krEngine/game/camera.h new file mode 100644 index 0000000..9041bab --- /dev/null +++ b/code/krEngine/game/camera.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#include + + +namespace kr +{ + class Window; + namespace Renderer { class Extractor; } + + struct KR_ENGINE_API CameraDefault2D + { + CameraDefault2D(kr::Borrowed window); + + ~CameraDefault2D(); + + void extract(kr::Renderer::Extractor& e); + + float aspect; + ezCamera cam; + }; +} diff --git a/code/krEngine/game/implementation/camera.cpp b/code/krEngine/game/implementation/camera.cpp new file mode 100644 index 0000000..f10c5fd --- /dev/null +++ b/code/krEngine/game/implementation/camera.cpp @@ -0,0 +1,31 @@ +#include +#include +#include +#include + +kr::CameraDefault2D::CameraDefault2D(kr::Borrowed window) +{ + auto size = window->getClientAreaSize(); + this->aspect = static_cast(size.width) / size.height; + + this->cam.SetCameraMode(ezCamera::OrthoFixedWidth, // Using an orthographic cam. + static_cast(size.width), // Width of the orthographic cam. + 0.1f, // Near plane. + 1.0f); // Far plane. + this->cam.LookAt(ezVec3(0, 0, -0.5f), // Camera Position. + ezVec3(0, 0, 0), // Target Position. + ezVec3(0, 1, 0)); // Up Vector. + + kr::Renderer::addExtractionListener({ &CameraDefault2D::extract, this }); +} + +kr::CameraDefault2D::~CameraDefault2D() +{ + kr::Renderer::removeExtractionListener({ &CameraDefault2D::extract, this }); +} + +void kr::CameraDefault2D::extract(kr::Renderer::Extractor& e) +{ + kr::extract(e, this->cam, this->aspect); +} +