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 fbfb1e0..0000000 --- a/build/CMake/kr_client_copy_dlls.cmake +++ /dev/null @@ -1,18 +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 "*.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_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/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() - diff --git a/build/CMake/platforms/Windows.cmake b/build/CMake/platforms/Windows.cmake index b2782f1..9b02593 100644 --- a/build/CMake/platforms/Windows.cmake +++ b/build/CMake/platforms/Windows.cmake @@ -1,37 +1,41 @@ # 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") + 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") - # 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_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_STATIC_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_EXE_LINKER_FLAGS_RELEASE} /OPT:REF") + 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_STATIC_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_EXE_LINKER_FLAGS_RELEASE} /OPT:ICF") + 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_STATIC_LINKER_FLAGS_RELEASE} /OPT:ICF") + set(CMAKE_STATIC_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /OPT:ICF") + +else() + message(WARNING "Unknown compiler on Windows.") endif() 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/code/krEngine/CMakeLists.txt b/code/krEngine/CMakeLists.txt index 01cc666..8a36491 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) @@ -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 new file mode 100644 index 0000000..740a72b --- /dev/null +++ b/code/krEngine/common/implementation/utils.cpp @@ -0,0 +1,26 @@ +#include + +#if EZ_ENABLED(EZ_PLATFORM_WINDOWS) + #include "utils_Windows.inl" +#else + #error common/utils not implemented for this platform. +#endif + +static ezStringBuilder defaultRootHelper() +{ + ezStringBuilder root{ ezOSFile::GetApplicationDirectory() }; + root.PathParentDirectory(); + root.MakeCleanPath(); + return root; +} + +const ezStringBuilder& kr::defaultRoot() +{ + static auto root{ defaultRootHelper() }; + return root; +} + +const char* kr::configPostfix() +{ + return _KR_CONFIG_POSTFIX; +} diff --git a/code/krEngine/common/implementation/utils_Windows.inl b/code/krEngine/common/implementation/utils_Windows.inl new file mode 100644 index 0000000..b9c9a72 --- /dev/null +++ b/code/krEngine/common/implementation/utils_Windows.inl @@ -0,0 +1,22 @@ + +static ezStringBuilder cwdHelper() +{ + auto bufferSize = GetCurrentDirectory(0, nullptr); + auto buffer = new TCHAR[bufferSize]; + KR_ON_SCOPE_EXIT{ delete[] buffer; }; + auto result = GetCurrentDirectory(bufferSize, buffer); + if(result != bufferSize - 1) + { + 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..ef530f0 100755 --- a/code/krEngine/common/utils.h +++ b/code/krEngine/common/utils.h @@ -28,3 +28,24 @@ 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(); + + /// \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; + } + + /// \return One of "-debug", "-minsize", "-reldeb", or "" (i.e. the empty string). + KR_ENGINE_API const char* configPostfix(); +} diff --git a/code/krEngine/game.h b/code/krEngine/game.h new file mode 100644 index 0000000..b482a62 --- /dev/null +++ b/code/krEngine/game.h @@ -0,0 +1,5 @@ +#pragma once + +#include +#include +#include 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/defaultMainModule.h b/code/krEngine/game/defaultMainModule.h new file mode 100644 index 0000000..73020c8 --- /dev/null +++ b/code/krEngine/game/defaultMainModule.h @@ -0,0 +1,77 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include + + +namespace kr +{ + class Window; + + struct DefaultGameLoopPriorities + { + enum Enum + { + Clock = -20, + MessagePump = -10, + Input = -5, + Module = 0, + Rendering = 20, + }; + + DefaultGameLoopPriorities() = delete; + }; + + class KR_ENGINE_API DefaultWindow : public ezWindow + { + public: // *** Inherited from ezWindow + virtual void OnClickCloseMessage(); + + public: // *** Accessors + + bool userRequestsClose() const { return m_userRequestsClose; } + + protected: // *** Data + bool m_userRequestsClose; + }; + + class KR_ENGINE_API DefaultMainModule : public MainModule + { + public: // *** Construction + DefaultMainModule(); + virtual ~DefaultMainModule(); + + public: // *** Inherited via kr::MainModule + virtual void OnCoreStartup() override; + virtual void OnEngineStartup() override; + virtual void OnEngineShutdown() override; + virtual void OnCoreShutdown() override; + + public: // *** Runtime + + /// \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; } + ezClock* clock() { return &m_clock; } + + protected: // *** Data + ezPlugin m_plugin{ false }; + ezClock m_clock; + ezWindowCreationDesc m_windowDesc; + Owned m_pWindow; + ezLogWriter::HTML m_htmlLog; + }; +} diff --git a/code/krEngine/game/gameLoop.h b/code/krEngine/game/gameLoop.h new file mode 100644 index 0000000..914fc30 --- /dev/null +++ b/code/krEngine/game/gameLoop.h @@ -0,0 +1,64 @@ +#pragma once + +namespace kr +{ + 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. + namespace GlobalGameLoop + { + /// \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. + 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. + 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. + KR_ENGINE_API 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() + KR_ENGINE_API 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() + KR_ENGINE_API ezInt32 getPriority(ezStringView loopName, ezLogInterface* pLogInterface = nullptr); + + /// \brief Ticks all registered game loops in order. + 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() + 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() + KR_ENGINE_API void setKeepTicking(bool value); + + /// \brief Whether the global game loop is currently ticking or not. + KR_ENGINE_API bool isTicking(); + + /// \brief Prints the order in which the registered callbacks are executed in for each tick, one per line. + 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. + KR_ENGINE_API void reset(); + }; +} 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); +} + diff --git a/code/krEngine/game/implementation/defaultMainModule.cpp b/code/krEngine/game/implementation/defaultMainModule.cpp new file mode 100644 index 0000000..cd69dcc --- /dev/null +++ b/code/krEngine/game/implementation/defaultMainModule.cpp @@ -0,0 +1,128 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include + + +void kr::DefaultWindow::OnClickCloseMessage() +{ + m_userRequestsClose = true; +} + +kr::DefaultMainModule::DefaultMainModule() +{ + ezClock::SetNumGlobalClocks(ezGlobalClockCount); + ezFileSystem::RegisterDataDirectoryFactory(ezDataDirectory::FolderType::Factory); +} + +kr::DefaultMainModule::~DefaultMainModule() +{ +} + +void kr::DefaultMainModule::OnCoreStartup() +{ + // The directory right above the executable belongs to the empty group. + if(ezFileSystem::AddDataDirectory(kr::defaultRoot(), ezFileSystem::ReadOnly).Failed()) + { + 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()) + { + 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() +{ + 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 }; + GlobalGameLoop::setKeepTicking(!userRequestsClose); + }); + + auto pLog{ ezGlobalLog::GetInstance() }; + + // Clock + // ===== + auto clockTick = [=]() + { + clock()->Update(); + }; + + GlobalGameLoop::set("clock", clockTick, pLog); + GlobalGameLoop::setPriority("clock", DefaultGameLoopPriorities::Clock, pLog); + + // Message Pump + // ============ + auto tickMessagePump = [=]() + { + kr::processWindowMessages(window()); + }; + GlobalGameLoop::set("messagePump", tickMessagePump, pLog); + GlobalGameLoop::setPriority("messagePump", DefaultGameLoopPriorities::MessagePump, pLog); + + // Input + // ===== + auto inputTick = [=]() + { + ezInputManager::Update(clock()->GetTimeDiff()); + }; + GlobalGameLoop::set("input", inputTick, pLog); + GlobalGameLoop::setPriority("input", DefaultGameLoopPriorities::Input, pLog); + + // Rendering + // ========= + auto renderingTick = [=]() + { + kr::Renderer::extract(); + kr::Renderer::update(window()); + }; + 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("module", pLog); + GlobalGameLoop::remove("rendering", pLog); + GlobalGameLoop::remove("input", pLog); + GlobalGameLoop::remove("messagePump", pLog); + GlobalGameLoop::remove("clock", pLog); + + if(m_pWindow->close().Failed()) + { + ezLog::SeriousWarning("Failed to destroy window."); + } +} + +void kr::DefaultMainModule::OnCoreShutdown() +{ + ezGlobalLog::RemoveLogWriter({ &ezLogWriter::HTML::LogMessageHandler, &m_htmlLog }); + m_htmlLog.EndLog(); + ezFileSystem::RemoveDataDirectoryGroup(""); +} diff --git a/code/krEngine/game/implementation/gameLoop.cpp b/code/krEngine/game/implementation/gameLoop.cpp new file mode 100644 index 0000000..172ab9b --- /dev/null +++ b/code/krEngine/game/implementation/gameLoop.cpp @@ -0,0 +1,231 @@ +#include + +#include + + +EZ_BEGIN_SUBSYSTEM_DECLARATION(krEngine, GlobalGameLoop) + ON_CORE_SHUTDOWN + { + kr::GlobalGameLoop::reset(); + } +EZ_END_SUBSYSTEM_DECLARATION + + +namespace +{ + struct GameLoop + { + bool isGarbage{ false }; + ezString name; + ezInt32 priority{ 0 }; + kr::GlobalGameLoopCallback callback; + + GameLoop(ezStringView name, kr::GlobalGameLoopCallback cb) : name{ kr::move(name) }, callback{ kr::move(cb) } + { + } + }; +} + +static ezDynamicArray& globalGameLoops() +{ + static ezDynamicArray value; + 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::GlobalGameLoop::set(ezStringView loopName, GlobalGameLoopCallback callback, ezLogInterface* pLogInterface) +{ + EZ_LOG_BLOCK(pLogInterface, "Add Global Game Loop", ezStringBuilder(loopName).GetData()); + + if (!callback.IsValid()) + { + ezLog::Warning(pLogInterface, "The given game loop callback is not valid."); + } + + auto& all = globalGameLoops(); + for(auto& loop : all) + { + if(loop.name == loopName) + { + ezLog::Info(pLogInterface, "Overwriting existing game loop."); + loop.callback = move(callback); + return; + } + } + + all.PushBack(move(GameLoop{ move(ezString{ loopName }), move(callback) })); + ezLog::Success(pLogInterface, "Game loop added successfully."); + + g_gameLoopsNeedSorting = true; +} + +kr::GlobalGameLoopCallback* kr::GlobalGameLoop::get(ezStringView loopName, ezLogInterface* pLogInterface) +{ + EZ_LOG_BLOCK(pLogInterface, "Get Global Game Loop", ezStringBuilder{ loopName }); + + auto ptr{ internalGet(loopName, pLogInterface) }; + return ptr == nullptr ? nullptr : &ptr->callback; +} + +ezResult kr::GlobalGameLoop::remove(ezStringView loopName, ezLogInterface* pLogInterface /*= nullptr*/) +{ + EZ_LOG_BLOCK(pLogInterface, "Removing Global Game Loop", ezStringBuilder(loopName)); + + auto& all{ globalGameLoops() }; + for(ezUInt32 i = 0; i < all.GetCount(); ++i) + { + if(all[i].name == loopName) + { + 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; + } + else + { + // Since we are not ticking the global game loop, we can immediately remove it. + all.RemoveAt(i); + } + + ezLog::Success(pLogInterface, "Removed game loop."); + return EZ_SUCCESS; + } + } + + ezLog::Warning(pLogInterface, "Given game loop instance is not registered. Unable to remove."); + return EZ_FAILURE; +} + +void kr::GlobalGameLoop::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::GlobalGameLoop::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::GlobalGameLoop::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) + { + 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; + } +} + +static bool g_keepGlobalGameLoopTicking{ true }; + +bool kr::GlobalGameLoop::keepTicking() +{ + return g_keepGlobalGameLoopTicking; +} + +void kr::GlobalGameLoop::setKeepTicking(bool value) +{ + g_keepGlobalGameLoopTicking = value; +} + +bool kr::GlobalGameLoop::isTicking() +{ + return g_globalGameLoopIsTicking; +} + +void kr::GlobalGameLoop::printTickOrder(ezLogInterface* pLogInterface /*= nullptr*/) +{ + if (g_gameLoopsNeedSorting) + { + sortByPriority(); + } + + auto& loops{ globalGameLoops() }; + for (auto& loop : loops) + { + ezLog::Info(pLogInterface, "%s", loop.name.GetData()); + } +} + +void kr::GlobalGameLoop::reset() +{ + globalGameLoops().Clear(); + g_gameLoopsNeedSorting = false; + g_globalGameLoopIsTicking = false; + g_keepGlobalGameLoopTicking = true; +} diff --git a/code/krEngine/game/implementation/mainModule.cpp b/code/krEngine/game/implementation/mainModule.cpp new file mode 100644 index 0000000..26ceabc --- /dev/null +++ b/code/krEngine/game/implementation/mainModule.cpp @@ -0,0 +1,40 @@ +#include + +static kr::MainModule* g_pMainModule{ nullptr }; + +void kr::MainModule::setGlobalInstance(MainModule* pModule) +{ + g_pMainModule = pModule; +} + +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 new file mode 100644 index 0000000..d38e63b --- /dev/null +++ b/code/krEngine/game/mainModule.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include + +namespace kr +{ + class KR_ENGINE_API MainModule : public ezSubSystem + { + public: // *** Static + static void setGlobalInstance(MainModule* pModule); + static MainModule* globalInstance(); + + public: // *** Construction + MainModule(); + virtual ~MainModule(); + + public: // *** Inherited from ezSubSystem + virtual const char* GetSubSystemName() const override; + virtual const char* GetGroupName() const override; + virtual const char* GetDependency(ezInt32 depIndex) override; + }; +} diff --git a/code/krEngine/rendering/implementation/renderer.cpp b/code/krEngine/rendering/implementation/renderer.cpp index e0092cb..6059267 100755 --- a/code/krEngine/rendering/implementation/renderer.cpp +++ b/code/krEngine/rendering/implementation/renderer.cpp @@ -174,8 +174,13 @@ void kr::Renderer::extract() } } -void kr::Renderer::update(ezTime dt, Borrowed pTarget) +void kr::Renderer::update(Borrowed pTarget) { + clock()->Update(); + auto dt = clock()->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."); @@ -217,6 +222,19 @@ void kr::Renderer::update(ezTime dt, 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 1f2cd7e..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 @@ -18,6 +20,8 @@ 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); + + KR_ENGINE_API ezClock* clock(); }; } 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() 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/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_common.cpp b/tests/krEngineTests/test_common.cpp index d317210..1bcfcce 100755 --- a/tests/krEngineTests/test_common.cpp +++ b/tests/krEngineTests/test_common.cpp @@ -133,17 +133,21 @@ TEST_CASE("ezContainer Extension", "[common]") */ } -TEST_CASE("Utils", "[common]") +TEST_CASE("makePath", "[common]") { using namespace kr; - int data = 42; - auto pData = &data; + auto path = makePath("C:", "hello", "world"); + REQUIRE(path == "C:/hello/world"); // Yes, on all systems. +} - REQUIRE(pData != nullptr); - REQUIRE_FALSE(pData == nullptr); +TEST_CASE("configPostfix", "[common]") +{ + ezStringView postfix{ kr::configPostfix() }; - pData = nullptr; - REQUIRE_FALSE(pData != nullptr); - REQUIRE(pData == nullptr); +#if EZ_ENABLED(EZ_COMPILE_FOR_DEBUG) + REQUIRE(postfix == "-debug"); +#else + REQUIRE(postfix.IsEmpty()); +#endif } diff --git a/tests/krEngineTests/test_game.cpp b/tests/krEngineTests/test_game.cpp new file mode 100644 index 0000000..97d5af6 --- /dev/null +++ b/tests/krEngineTests/test_game.cpp @@ -0,0 +1,41 @@ +#include +#include + +#include + +TEST_CASE("Global Game Loop Registry", "[game]") +{ + using namespace kr; + + KR_TESTS_RAII_CORE_STARTUP; + + int check1 = 0; + int check2 = 0; + + SECTION("Basics") + { + GlobalGameLoop::set("foo", [&]() { ++check1; }); + GlobalGameLoop::set("bar", [&]() { ++check2; }); + + GlobalGameLoop::tick(); + REQUIRE(check1 == 1); + REQUIRE(check2 == 1); + + REQUIRE(GlobalGameLoop::remove("foo").Succeeded()); + REQUIRE(GlobalGameLoop::remove("bar").Succeeded()); + REQUIRE(GlobalGameLoop::remove("bar").Failed()); + } + + SECTION("Priority") + { + GlobalGameLoop::set("bar", [&]() { REQUIRE(check1 == 1); ++check1; }); + GlobalGameLoop::set("foo", [&]() { REQUIRE(check1 == 0); ++check1; }); + + GlobalGameLoop::setPriority("foo", 10); + GlobalGameLoop::setPriority("bar", -10); + + GlobalGameLoop::tick(); + REQUIRE(check1 == 2); + REQUIRE(check2 == 0); + } +} 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); } }