|
OpenEnroth 63eb83e
|
This document describes the development process we're following. It's required reading for anyone intending to contribute.
Main dependencies:
By default, we are using prebuilt dependencies, and they are resolved automatically during the cmake phase.
Additional dependencies:
Minimum required compiler versions are as follows:
The following IDEs have been tested and should work fine:
This project uses the CMake build system. Use the following commands to clone the repository and build OpenEnroth:
To cross-compile for 32-bit x86, you can pass -m32 via compiler flags to cmake. In the snipped above that would mean running export CFLAGS="-m32" CXXFLAGS="-m32" first.
You can also select platform dependent generator for your favorite IDE.
https://git-scm.com/download/win) and Visual Studio 2022.https://github.com/OpenEnroth/OpenEnroth.c:\Program Files\Microsoft Visual Studio\2022\<edition>\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin).OpenEnroth.exe.If you wish you can also disable prebuilt dependencies by turning off OE_USE_PREBUILT_DEPENDENCIES cmake option and pass your own dependencies source, e.g. via vcpkg integration.
Be aware that Visual Studio has a bug with git submodules not syncing between branches. So when checking out the branch or switching to different branch you may need to run the following command manually: git submodule update --init
For the C++ code we are following the Google C++ Style Guide. Source code is automatically checked against it and pull request will fail if you don't follow it.
To perform a style check before pushing anything you can build check_style target. In Visual Studio you can do that by going to Solution Explorer → Change Views → CMake targets. Right click and build check_style, errors will be listed in output.
We also follow some additional style preferences, as listed below.
Documentation:
@ used for tags, and starting with /** comment introducer.@par Original binary offset:\ilinebr doxygen tag (e.g. @par Original binary offset:\ilinebr 0xCAFEBEEF).Naming:
MM_ prefix for macro naming. Macro names should be in SNAKE_CASE_ALL_CAPS.SNAKE_CASE_ALL_CAPS for enum values. E.g. ITEM_CRATE_OF_ARROWHEADS, ITEM_SLOT_RING6.MONSTER_TROLL_A for a value of enum class MonsterId. For values that are then used in array bounds, put the _FIRST, _LAST and _COUNT right after the name of the type, e.g. ITEM_FIRST_MESSAGE_SCROLL.CamelCase for everything else.IndexedArray, InputAction, LogLevel. This applies to all types, including classes, structs, enums and typedefs, with certain exceptions as listed below.Vec3::length, gridCellToWorldPosX, ceilingHeight.int monsterCount = level->monsterCount().this-> every single time. E.g. _initialized = true, where _initialized is a member field.value_type for iterator value type, and push_back for a method that's adding an element to a container.Code formatting:
* and & in type declarations should be preceded by a space. So it's char *string, and not char* string.Language features:
using Alias = Type instead of typedef Type Alias.enum classes followed by using enum statements instead of ordinary enums. This provides type safety without changing the syntax. For flags, use Flags class.enums if you really need to have implicit casts to integer types, but this is a very rare use case. If you're using enum values to index into some array, consider using enum class coupled with IndexedArray.std::unique_ptr by value. If it allocates its result and passes ownership to the caller, then it should return std::unique_ptr.int unless you need something else. Don’t try to avoid negative values by using unsigned, this implies many changes to the usual behavior of integers and can be a source of bugs. See a section in core guidelines for more info. However, don't overdo it, use size_t for indexing into STL containers.std::string_view passed by value.std::string or passing it by const reference, the performance benefits are almost always negligible.TransparentString* classes if you need to index into a string map using std::string_view keys.std::string or const std::string & parameters where it makes your code simpler (e.g. by avoiding jumping through hoops if you'll need to create an intermediate std::string object anyway).std::string_view with a string literal using operator+, and never will). In most cases you should be fine just using fmt::format for this. If fmt::format looks like an overkill, use join from Utility/String/Transformations.h.ns1::Context and ns2::Context, and when coupled with a bit of using here and there this makes the code harder to read and reason about. Please spend some time coming up with good names for your classes instead.namespace lod.namespace detail that you're encouraged to use to hide implementation details and to prevent cluttering of the global namespace.std::strings in the code are assumed to be UTF8-encoded as advised by utf8everywhere. On Windows this is ensured by the UnicodeCrt class that sets up the standard library to use UTF8 encoding. Thus, you don't need to bother with std::u8string, and you can safely convert std::filesystem::path to string by calling path.string().Error handling:
asserts to check for coding errors and conditions that must never be false, no matter how the program is run.class Exception.Logger for warnings and recoverable errors.There is a lot of code in the project that doesn't follow these conventions. Please feel free to fix it, preferably not mixing up style and logical changes in the same PR.
OpenEnroth code is broken up as follows:
thirdparty – this is where all external libraries go.Utility – generic utility classes and functions go here. Utility classes should be domain-independent (e.g. should make sense in a context of some other project) and should depend only on thirdparty libraries.Library – collection of independent libraries that the engine is built on top of. Code here can depend on Utility and other libraries in Library. However, there should be no cyclical dependencies between libraries here.Library/Platform is our platform abstraction layer on top of SDL.Our basic guidelines for code organization are:
CMakeLists.txt file per folder. Exceptions are /android, /CMakeModules and /resources.We strive for a good test coverage of the project, and while we're not there yet, the current policy is to add tests for all the bugs we fix, as long as the fix is testable. E.g. graphical glitches are generally very hard to test, but we have the infrastructure to test game logic and small isolated classes.
Tests in OpenEnroth fall into two categories:
src/Utility/Tests.Game tests work by instrumenting the engine, and then running test code in between the game frames. This code usually sends events to the engine (e.g. mouse clicks), which are then processed by the engine in the next frame, but it can do pretty much anything else – all of engine's data is accessible and writable from inside the game test.
As typing out all the events to send inside the test code can be pretty tedious, we also have an event recording functionality, so that the test creation workflow usually looks as follows:
Ctrl+Shift+R to start recording an event trace. Check logs to make sure that trace recording has started.Ctrl+Shift+R again to stop trace recording. You will get two files generated in the current folder – trace.json and trace.mm7.issue_XXX.json and issue_XXX.mm7) and create a PR to the OpenEnroth_TestData repo.TestController::playTraceFromTestData to play back your trace, and add the necessary checks around it.If you need to record a trace with non-standard FPS (e.g. if an issue doesn't reproduce on lower FPS values), set debug.trace_frame_time_ms config value before starting OpenEnroth for recording.
To run all unit tests locally, build a OpenEnroth_UnitTest cmake target and run <build-dir>/test/Bin/UnitTest/OpenEnroth_UnitTest.
To run all game tests locally, set OPENENROTH_MM7_PATH environment variable to point to the location of the game assets, then build Run_GameTest_Headless_Parallel cmake target. Alternatively, you can build OpenEnroth_GameTest, and run it manually, passing the paths to both game assets and the test data via command line. Run OpenEnroth_GameTest --help for a list of options. Note that you can pass --headless to run tests in headless mode. You can use the test data from <build-dir>/test/Bin/test_data/data, which is automatically downloaded and updated by the OpenEnroth_TestData cmake target. Alternatively, if you cloned the OpenEnroth_TestData repository, you can use that.
If you need to look closely at the recorded trace, you can play it by running OpenEnroth play --speed 0.5 <path-to-trace.json>. Alternatively, if you already have a unit test that runs the recorded trace, you can run OpenEnroth_GameTest --speed 0.5 --gtest_filter=<test-suite-name>.<test-name> --test-path <path-to-test-data-folder>. Note that --gtest_filter needs that = and won't work if you try passing the test name after a space.
Changing game logic might result in failures in game tests because they check the random number generator state after each frame, and this will show as Random state desynchronized when playing back trace message in test logs. This is intentional – we don't want accidental game logic changes. If the change was actually intentional, then you will need to either retrace or re-record the traces for the failing tests as follows. These instructions assume your change is already submitted as PR backed from your fork, or will be.
OpenEnroth retrace <path-to-trace.json>. Note that you can pass multiple trace paths to this command. Use paths into your clone of the test data (if you retrace the copies in <build-dir>/test/Bin/test_data/data you'll need to copy them over).GIT_TAG key in test/Bin/CMakeLists.txt, replacing the one already there, and update the GIT_REPOSITORY to point to your clone of OpenEnroth_TestData.We're using Lua as the scripting language, and all our scripts are currently located under the resources/scripts folder.
Script files undergo a syntax checking process during the build generation. If you intend to work with scripts, it is recommended to install the Lua Language Server to run the style checker locally. Follow these steps to setup LuaLS locally:
LuaLS through one of the following methods. Ensure that the lua-language-server executable is available in the PATH environment variable.check_style target is now including scripting in its tests.Little note: If LuaLS is not found, everything still build but no checks will be run against the Lua scripts.
To go through a better experience while working with scripts it is strongly recommended to use VS Code and install the LuaLS extension. Just be sure to open the root repository folder in VS Code. By doing so LuaLS reads the correct configuration file used by the project
Scripting is currently used only for debugging purposes. Modding support is not planned for the near future. You can check the milestones to get a better idea.
The game features a console capable of executing various Lua script commands. To activate this feature, please follow these steps:
~ key to open the debug console.Currently, the console is only available while in-game.
Old event decompiler and IDB files can be found here. Feel free to ping zipi#6029 on Discord for more info.