From efa88c722fcb6c8fea7a86e1b3b8a83f1f59eb31 Mon Sep 17 00:00:00 2001 From: Hop311 Date: Thu, 24 Aug 2023 00:32:23 +0100 Subject: Big Dataloader Commit (openvic-simulation) --- SConstruct | 295 +++++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 267 insertions(+), 28 deletions(-) (limited to 'SConstruct') diff --git a/SConstruct b/SConstruct index 50edb8f..632e97b 100644 --- a/SConstruct +++ b/SConstruct @@ -1,20 +1,221 @@ #!/usr/bin/env python + +# This file is heavily based on https://github.com/godotengine/godot-cpp/blob/8155f35b29b4b08bc54b2eb0c57e1e9effe9f093/SConstruct import os +import platform import sys -from glob import glob -from pathlib import Path - -def GlobRecursive(pattern, nodes=['.']): - import SCons - results = [] - for node in nodes: - nnodes = [] - for f in Glob(str(node) + '/*', source=True): - if type(f) is SCons.Node.FS.Dir: - nnodes.append(f) - results += GlobRecursive(pattern, nnodes) - results += Glob(str(node) + '/' + pattern, source=True) - return results +import importlib + +import SCons + +# Local +from scripts.build.option_handler import OptionsClass +from scripts.build.glob_recursive import GlobRecursive +from scripts.build.cache import show_progress + +# Try to detect the host platform automatically. +# This is used if no `platform` argument is passed +if sys.platform.startswith("linux"): + default_platform = "linux" +elif sys.platform == "darwin": + default_platform = "macos" +elif sys.platform == "win32" or sys.platform == "msys": + default_platform = "windows" +elif ARGUMENTS.get("platform", ""): + default_platform = ARGUMENTS.get("platform") +else: + raise ValueError("Could not detect platform automatically, please specify with platform=") + +is_standalone = SCons.Script.sconscript_reading == 1 + +BINDIR = "bin" +TOOLPATH = ["deps/openvic-dataloader/tools"] + +try: + Import("env") + old_env = env + env = old_env.Clone() +except: + # Default tools with no platform defaults to gnu toolchain. + # We apply platform specific toolchains via our custom tools. + env = Environment(tools=["default"], PLATFORM="") + old_env = env + +env.PrependENVPath("PATH", os.getenv("PATH")) + +# Default num_jobs to local cpu count if not user specified. +# SCons has a peculiarity where user-specified options won't be overridden +# by SetOption, so we can rely on this to know if we should use our default. +initial_num_jobs = env.GetOption("num_jobs") +altered_num_jobs = initial_num_jobs + 1 +env.SetOption("num_jobs", altered_num_jobs) +if env.GetOption("num_jobs") == altered_num_jobs: + cpu_count = os.cpu_count() + if cpu_count is None: + print("Couldn't auto-detect CPU count to configure build parallelism. Specify it with the -j argument.") + else: + safer_cpu_count = cpu_count if cpu_count <= 4 else cpu_count - 1 + print( + "Auto-detected %d CPU cores available for build parallelism. Using %d cores by default. You can override it with the -j argument." + % (cpu_count, safer_cpu_count) + ) + env.SetOption("num_jobs", safer_cpu_count) + +opts = OptionsClass(ARGUMENTS) + +platforms = ("linux", "macos", "windows", "android", "ios", "javascript") +unsupported_known_platforms = ("android", "ios", "javascript") +opts.Add( + EnumVariable( + key="platform", + help="Target platform", + default=env.get("platform", default_platform), + allowed_values=platforms, + ignorecase=2, + ) +) + +opts.Add( + EnumVariable( + key="target", + help="Compilation target", + default=env.get("target", "template_debug"), + allowed_values=("editor", "template_release", "template_debug"), + ) +) + +opts.Add(BoolVariable(key="build_ovsim_library", help="Build the openvic simulation library.", default=env.get("build_ovsim_library", not is_standalone))) +opts.Add( + EnumVariable( + key="precision", + help="Set the floating-point precision level", + default=env.get("precision", "single"), + allowed_values=("single", "double"), + ) +) + +# Add platform options +tools = {} +for pl in set(platforms) - set(unsupported_known_platforms): + tool = Tool(pl, toolpath=TOOLPATH) + if hasattr(tool, "options"): + tool.options(opts) + tools[pl] = tool + +# CPU architecture options. +architecture_array = ["", "universal", "x86_32", "x86_64", "arm32", "arm64", "rv64", "ppc32", "ppc64", "wasm32"] +architecture_aliases = { + "x64": "x86_64", + "amd64": "x86_64", + "armv7": "arm32", + "armv8": "arm64", + "arm64v8": "arm64", + "aarch64": "arm64", + "rv": "rv64", + "riscv": "rv64", + "riscv64": "rv64", + "ppcle": "ppc32", + "ppc": "ppc32", + "ppc64le": "ppc64", +} +opts.Add( + EnumVariable( + key="arch", + help="CPU architecture", + default=env.get("arch", ""), + allowed_values=architecture_array, + map=architecture_aliases, + ) +) + +opts.Add(BoolVariable("build_ovsim_headless", "Build the openvic simulation headless executable", is_standalone)) + +opts.Add(BoolVariable("compiledb", "Generate compilation DB (`compile_commands.json`) for external tools", False)) +opts.Add(BoolVariable("verbose", "Enable verbose output for the compilation", False)) +opts.Add(BoolVariable("intermediate_delete", "Enables automatically deleting unassociated intermediate binary files.", True)) +opts.Add(BoolVariable("progress", "Show a progress indicator during compilation", True)) + +# Targets flags tool (optimizations, debug symbols) +target_tool = Tool("targets", toolpath=TOOLPATH) +target_tool.options(opts) + +# Custom options and profile flags. +opts.Make(["custom.py"]) +opts.Finalize(env) +Help(opts.GenerateHelpText(env)) + +if env["platform"] in unsupported_known_platforms: + print("Unsupported platform: " + env["platform"]+". Only supports " + ", ".join(set(platforms) - set(unsupported_known_platforms))) + Exit() + +# Process CPU architecture argument. +if env["arch"] == "": + # No architecture specified. Default to arm64 if building for Android, + # universal if building for macOS or iOS, wasm32 if building for web, + # otherwise default to the host architecture. + if env["platform"] in ["macos", "ios"]: + env["arch"] = "universal" + elif env["platform"] == "android": + env["arch"] = "arm64" + elif env["platform"] == "javascript": + env["arch"] = "wasm32" + else: + host_machine = platform.machine().lower() + if host_machine in architecture_array: + env["arch"] = host_machine + elif host_machine in architecture_aliases.keys(): + env["arch"] = architecture_aliases[host_machine] + elif "86" in host_machine: + # Catches x86, i386, i486, i586, i686, etc. + env["arch"] = "x86_32" + else: + print("Unsupported CPU architecture: " + host_machine) + Exit() + +tool = Tool(env["platform"], toolpath=TOOLPATH) + +if tool is None or not tool.exists(env): + raise ValueError("Required toolchain not found for platform " + env["platform"]) + +tool.generate(env) +target_tool.generate(env) + +print("Building for architecture " + env["arch"] + " on platform " + env["platform"]) + +# Require C++20 +if env.get("is_msvc", False): + env.Append(CXXFLAGS=["/std:c++20"]) +else: + env.Append(CXXFLAGS=["-std=c++20"]) + +if env["precision"] == "double": + env.Append(CPPDEFINES=["REAL_T_IS_DOUBLE"]) + +scons_cache_path = os.environ.get("SCONS_CACHE") +if scons_cache_path != None: + CacheDir(scons_cache_path) + print("Scons cache enabled... (path: '" + scons_cache_path + "')") + +if env["compiledb"]: + # Generating the compilation DB (`compile_commands.json`) requires SCons 4.0.0 or later. + from SCons import __version__ as scons_raw_version + + scons_ver = env._get_major_minor_revision(scons_raw_version) + + if scons_ver < (4, 0, 0): + print("The `compiledb=yes` option requires SCons 4.0 or later, but your version is %s." % scons_raw_version) + Exit(255) + + env.Tool("compilation_db") + env.Alias("compiledb", env.CompilationDatabase()) + +ovdl_env = SConscript("deps/openvic-dataloader/SConstruct") + +env.Append(LIBPATH=ovdl_env.openvic_dataloader["LIBPATH"]) +env.Append(LIBS=ovdl_env.openvic_dataloader["LIBS"]) +env.Append(CPPPATH=ovdl_env.openvic_dataloader["INCPATH"]) + +env.openvic_simulation = {} # For future reference: # - CCFLAGS are compilation flags shared between C and C++ @@ -23,23 +224,61 @@ def GlobRecursive(pattern, nodes=['.']): # - CPPFLAGS are for pre-processor flags # - CPPDEFINES are for pre-processor defines # - LINKFLAGS are for linking flags -env = Environment( - CPPDEFINES=["OPENVIC_HEADLESS_SIM"] -) - -# Require C++20 -if sys.platform == "win32" or sys.platform == "msys": - env.Append(CXXFLAGS=["/std:c++20", "/EHsc"]) -else: - env.Append(CXXFLAGS=["-std=c++20"]) # Tweak this if you want to use different folders, or more folders, to store your source code in. -paths = ["src"] -env.Append(CPPPATH=paths) -sources = GlobRecursive("*.cpp", paths) +source_path = "src/openvic" +include_path = "src" +env.Append(CPPPATH=[[env.Dir(p) for p in [source_path, include_path]]]) +sources = GlobRecursive("*.cpp", [source_path]) +env.simulation_sources = sources + +suffix = ".{}.{}".format(env["platform"], env["target"]) +if env.dev_build: + suffix += ".dev" +if env["precision"] == "double": + suffix += ".double" +suffix += "." + env["arch"] + +# Expose it when included from another project +env["suffix"] = suffix + +library = None +env["OBJSUFFIX"] = suffix + env["OBJSUFFIX"] +library_name = "libopenvic-simulation{}{}".format(suffix, env["LIBSUFFIX"]) + +if env["build_ovsim_library"]: + library = env.StaticLibrary(target=env.File(os.path.join(BINDIR, library_name)), source=sources) + Default(library) + + env.Append(LIBPATH=[env.Dir(BINDIR)]) + env.Append(LIBS=[library_name]) + + env.openvic_simulation["LIBPATH"] = env["LIBPATH"] + env.openvic_simulation["LIBS"] = env["LIBS"] + env.openvic_simulation["INCPATH"] = [env.Dir(include_path)] + ovdl_env.openvic_dataloader["INCPATH"] + +headless_program = None +env["PROGSUFFIX"] = suffix + env["PROGSUFFIX"] -program = env.Program("headless-sim", sources) +if env["build_ovsim_headless"]: + headless_name = "openvic-simulation" + headless_env = env.Clone() + headless_path = ["src/headless"] + headless_env.Append(CPPDEFINES=["OPENVIC_SIM_HEADLESS"]) + headless_env.Append(CPPPATH=[headless_env.Dir(headless_path)]) + headless_env.headless_sources = GlobRecursive("*.cpp", headless_path) + if not env["build_ovsim_library"]: + headless_env.headless_sources += sources + headless_program = headless_env.Program( + target=os.path.join(BINDIR, headless_name), + source=headless_env.headless_sources, + PROGSUFFIX=".headless" + env["PROGSUFFIX"] + ) + Default(headless_program) -Default(program) +if "env" in locals(): + # FIXME: This method mixes both cosmetic progress stuff and cache handling... + show_progress(env) +Return("env") -- cgit v1.2.3-56-ga3b1 From 7772f8871348b7b52cb0a478bb76df68d8799a07 Mon Sep 17 00:00:00 2001 From: Hop311 Date: Fri, 8 Sep 2023 17:12:22 +0100 Subject: More refactoring and duplicate code removal --- SConstruct | 2 +- src/headless/main.cpp | 11 +- src/openvic-simulation/GameAdvancementHook.cpp | 76 +++ src/openvic-simulation/GameAdvancementHook.hpp | 44 ++ src/openvic-simulation/GameManager.cpp | 132 +++++ src/openvic-simulation/GameManager.hpp | 40 ++ src/openvic-simulation/dataloader/Dataloader.cpp | 288 ++++++++++ src/openvic-simulation/dataloader/Dataloader.hpp | 35 ++ src/openvic-simulation/dataloader/NodeTools.cpp | 256 +++++++++ src/openvic-simulation/dataloader/NodeTools.hpp | 164 ++++++ src/openvic-simulation/economy/Good.cpp | 140 +++++ src/openvic-simulation/economy/Good.hpp | 79 +++ src/openvic-simulation/map/Building.cpp | 127 +++++ src/openvic-simulation/map/Building.hpp | 85 +++ src/openvic-simulation/map/Map.cpp | 509 ++++++++++++++++++ src/openvic-simulation/map/Map.hpp | 117 +++++ src/openvic-simulation/map/Province.cpp | 129 +++++ src/openvic-simulation/map/Province.hpp | 65 +++ src/openvic-simulation/map/Region.cpp | 69 +++ src/openvic-simulation/map/Region.hpp | 38 ++ src/openvic-simulation/pop/Culture.cpp | 199 +++++++ src/openvic-simulation/pop/Culture.hpp | 83 +++ src/openvic-simulation/pop/Pop.cpp | 163 ++++++ src/openvic-simulation/pop/Pop.hpp | 101 ++++ src/openvic-simulation/pop/Religion.cpp | 110 ++++ src/openvic-simulation/pop/Religion.hpp | 56 ++ src/openvic-simulation/types/Colour.hpp | 36 ++ src/openvic-simulation/types/Date.cpp | 262 +++++++++ src/openvic-simulation/types/Date.hpp | 96 ++++ .../types/IdentifierRegistry.cpp | 37 ++ .../types/IdentifierRegistry.hpp | 197 +++++++ .../types/fixed_point/FixedPoint.hpp | 584 +++++++++++++++++++++ .../types/fixed_point/FixedPointLUT.hpp | 30 ++ .../types/fixed_point/FixedPointLUT_sin.hpp | 42 ++ .../types/fixed_point/FixedPointMath.hpp | 11 + .../fixed_point/lut_generator/lut_generator.py | 63 +++ src/openvic-simulation/utility/BMP.cpp | 163 ++++++ src/openvic-simulation/utility/BMP.hpp | 51 ++ src/openvic-simulation/utility/Logger.cpp | 20 + src/openvic-simulation/utility/Logger.hpp | 87 +++ src/openvic-simulation/utility/NumberUtils.hpp | 27 + src/openvic-simulation/utility/StringUtils.hpp | 127 +++++ src/openvic/GameAdvancementHook.cpp | 76 --- src/openvic/GameAdvancementHook.hpp | 44 -- src/openvic/GameManager.cpp | 132 ----- src/openvic/GameManager.hpp | 40 -- src/openvic/dataloader/Dataloader.cpp | 288 ---------- src/openvic/dataloader/Dataloader.hpp | 35 -- src/openvic/dataloader/NodeTools.cpp | 256 --------- src/openvic/dataloader/NodeTools.hpp | 161 ------ src/openvic/economy/Good.cpp | 176 ------- src/openvic/economy/Good.hpp | 86 --- src/openvic/map/Building.cpp | 135 ----- src/openvic/map/Building.hpp | 86 --- src/openvic/map/Map.cpp | 529 ------------------- src/openvic/map/Map.hpp | 121 ----- src/openvic/map/Province.cpp | 145 ----- src/openvic/map/Province.hpp | 68 --- src/openvic/map/Region.cpp | 69 --- src/openvic/map/Region.hpp | 38 -- src/openvic/pop/Culture.cpp | 233 -------- src/openvic/pop/Culture.hpp | 89 ---- src/openvic/pop/Pop.cpp | 195 ------- src/openvic/pop/Pop.hpp | 104 ---- src/openvic/pop/Religion.cpp | 142 ----- src/openvic/pop/Religion.hpp | 62 --- src/openvic/types/Colour.hpp | 36 -- src/openvic/types/Date.cpp | 262 --------- src/openvic/types/Date.hpp | 96 ---- src/openvic/types/IdentifierRegistry.cpp | 37 -- src/openvic/types/IdentifierRegistry.hpp | 152 ------ src/openvic/types/fixed_point/FP.hpp | 581 -------------------- src/openvic/types/fixed_point/FPLUT.hpp | 33 -- src/openvic/types/fixed_point/FPLUT_sin_512.hpp | 58 -- src/openvic/types/fixed_point/FPMath.hpp | 11 - .../fixed_point/lut_generator/lut_generator.py | 39 -- src/openvic/utility/BMP.cpp | 163 ------ src/openvic/utility/BMP.hpp | 51 -- src/openvic/utility/Logger.cpp | 20 - src/openvic/utility/Logger.hpp | 87 --- src/openvic/utility/NumberUtils.hpp | 27 - src/openvic/utility/StringUtils.hpp | 127 ----- 82 files changed, 4943 insertions(+), 5098 deletions(-) create mode 100644 src/openvic-simulation/GameAdvancementHook.cpp create mode 100644 src/openvic-simulation/GameAdvancementHook.hpp create mode 100644 src/openvic-simulation/GameManager.cpp create mode 100644 src/openvic-simulation/GameManager.hpp create mode 100644 src/openvic-simulation/dataloader/Dataloader.cpp create mode 100644 src/openvic-simulation/dataloader/Dataloader.hpp create mode 100644 src/openvic-simulation/dataloader/NodeTools.cpp create mode 100644 src/openvic-simulation/dataloader/NodeTools.hpp create mode 100644 src/openvic-simulation/economy/Good.cpp create mode 100644 src/openvic-simulation/economy/Good.hpp create mode 100644 src/openvic-simulation/map/Building.cpp create mode 100644 src/openvic-simulation/map/Building.hpp create mode 100644 src/openvic-simulation/map/Map.cpp create mode 100644 src/openvic-simulation/map/Map.hpp create mode 100644 src/openvic-simulation/map/Province.cpp create mode 100644 src/openvic-simulation/map/Province.hpp create mode 100644 src/openvic-simulation/map/Region.cpp create mode 100644 src/openvic-simulation/map/Region.hpp create mode 100644 src/openvic-simulation/pop/Culture.cpp create mode 100644 src/openvic-simulation/pop/Culture.hpp create mode 100644 src/openvic-simulation/pop/Pop.cpp create mode 100644 src/openvic-simulation/pop/Pop.hpp create mode 100644 src/openvic-simulation/pop/Religion.cpp create mode 100644 src/openvic-simulation/pop/Religion.hpp create mode 100644 src/openvic-simulation/types/Colour.hpp create mode 100644 src/openvic-simulation/types/Date.cpp create mode 100644 src/openvic-simulation/types/Date.hpp create mode 100644 src/openvic-simulation/types/IdentifierRegistry.cpp create mode 100644 src/openvic-simulation/types/IdentifierRegistry.hpp create mode 100644 src/openvic-simulation/types/fixed_point/FixedPoint.hpp create mode 100644 src/openvic-simulation/types/fixed_point/FixedPointLUT.hpp create mode 100644 src/openvic-simulation/types/fixed_point/FixedPointLUT_sin.hpp create mode 100644 src/openvic-simulation/types/fixed_point/FixedPointMath.hpp create mode 100644 src/openvic-simulation/types/fixed_point/lut_generator/lut_generator.py create mode 100644 src/openvic-simulation/utility/BMP.cpp create mode 100644 src/openvic-simulation/utility/BMP.hpp create mode 100644 src/openvic-simulation/utility/Logger.cpp create mode 100644 src/openvic-simulation/utility/Logger.hpp create mode 100644 src/openvic-simulation/utility/NumberUtils.hpp create mode 100644 src/openvic-simulation/utility/StringUtils.hpp delete mode 100644 src/openvic/GameAdvancementHook.cpp delete mode 100644 src/openvic/GameAdvancementHook.hpp delete mode 100644 src/openvic/GameManager.cpp delete mode 100644 src/openvic/GameManager.hpp delete mode 100644 src/openvic/dataloader/Dataloader.cpp delete mode 100644 src/openvic/dataloader/Dataloader.hpp delete mode 100644 src/openvic/dataloader/NodeTools.cpp delete mode 100644 src/openvic/dataloader/NodeTools.hpp delete mode 100644 src/openvic/economy/Good.cpp delete mode 100644 src/openvic/economy/Good.hpp delete mode 100644 src/openvic/map/Building.cpp delete mode 100644 src/openvic/map/Building.hpp delete mode 100644 src/openvic/map/Map.cpp delete mode 100644 src/openvic/map/Map.hpp delete mode 100644 src/openvic/map/Province.cpp delete mode 100644 src/openvic/map/Province.hpp delete mode 100644 src/openvic/map/Region.cpp delete mode 100644 src/openvic/map/Region.hpp delete mode 100644 src/openvic/pop/Culture.cpp delete mode 100644 src/openvic/pop/Culture.hpp delete mode 100644 src/openvic/pop/Pop.cpp delete mode 100644 src/openvic/pop/Pop.hpp delete mode 100644 src/openvic/pop/Religion.cpp delete mode 100644 src/openvic/pop/Religion.hpp delete mode 100644 src/openvic/types/Colour.hpp delete mode 100644 src/openvic/types/Date.cpp delete mode 100644 src/openvic/types/Date.hpp delete mode 100644 src/openvic/types/IdentifierRegistry.cpp delete mode 100644 src/openvic/types/IdentifierRegistry.hpp delete mode 100644 src/openvic/types/fixed_point/FP.hpp delete mode 100644 src/openvic/types/fixed_point/FPLUT.hpp delete mode 100644 src/openvic/types/fixed_point/FPLUT_sin_512.hpp delete mode 100644 src/openvic/types/fixed_point/FPMath.hpp delete mode 100644 src/openvic/types/fixed_point/lut_generator/lut_generator.py delete mode 100644 src/openvic/utility/BMP.cpp delete mode 100644 src/openvic/utility/BMP.hpp delete mode 100644 src/openvic/utility/Logger.cpp delete mode 100644 src/openvic/utility/Logger.hpp delete mode 100644 src/openvic/utility/NumberUtils.hpp delete mode 100644 src/openvic/utility/StringUtils.hpp (limited to 'SConstruct') diff --git a/SConstruct b/SConstruct index 632e97b..6fa567a 100644 --- a/SConstruct +++ b/SConstruct @@ -226,7 +226,7 @@ env.openvic_simulation = {} # - LINKFLAGS are for linking flags # Tweak this if you want to use different folders, or more folders, to store your source code in. -source_path = "src/openvic" +source_path = "src/openvic-simulation" include_path = "src" env.Append(CPPPATH=[[env.Dir(p) for p in [source_path, include_path]]]) sources = GlobRecursive("*.cpp", [source_path]) diff --git a/src/headless/main.cpp b/src/headless/main.cpp index a5bfc03..3185001 100644 --- a/src/headless/main.cpp +++ b/src/headless/main.cpp @@ -1,8 +1,6 @@ -#ifdef OPENVIC_SIM_HEADLESS - -#include -#include -#include +#include +#include +#include using namespace OpenVic; @@ -70,8 +68,7 @@ int main(int argc, char const* argv[]) { std::cout << "!!! HEADLESS SIMULATION END !!!" << std::endl; - std::cout << "\nLoad returned: " << (ret ? "true" : "false") << std::endl; + std::cout << "\nLoad returned: " << (ret ? "SUCCESS" : "FAILURE") << std::endl; return ret ? 0 : -1; } -#endif diff --git a/src/openvic-simulation/GameAdvancementHook.cpp b/src/openvic-simulation/GameAdvancementHook.cpp new file mode 100644 index 0000000..ac16158 --- /dev/null +++ b/src/openvic-simulation/GameAdvancementHook.cpp @@ -0,0 +1,76 @@ +#include "GameAdvancementHook.hpp" + +using namespace OpenVic; + +const std::vector GameAdvancementHook::GAME_SPEEDS = { + std::chrono::milliseconds { 4000 }, + std::chrono::milliseconds { 3000 }, + std::chrono::milliseconds { 2000 }, + std::chrono::milliseconds { 1000 }, + std::chrono::milliseconds { 100 }, + std::chrono::milliseconds { 1 } +}; + +GameAdvancementHook::GameAdvancementHook(AdvancementFunction tickFunction, + RefreshFunction updateFunction, bool startPaused, speed_t startingSpeed) + : triggerFunction { tickFunction }, + refreshFunction { updateFunction }, + isPaused { startPaused } { + lastPolledTime = std::chrono::high_resolution_clock::now(); + setSimulationSpeed(startingSpeed); +} + +void GameAdvancementHook::setSimulationSpeed(speed_t speed) { + if (speed < 0) + currentSpeed = 0; + else if (speed >= GAME_SPEEDS.size()) + currentSpeed = GAME_SPEEDS.size() - 1; + else + currentSpeed = speed; +} + +GameAdvancementHook::speed_t GameAdvancementHook::getSimulationSpeed() const { + return currentSpeed; +} + +void GameAdvancementHook::increaseSimulationSpeed() { + setSimulationSpeed(currentSpeed + 1); +} + +void GameAdvancementHook::decreaseSimulationSpeed() { + setSimulationSpeed(currentSpeed - 1); +} + +bool GameAdvancementHook::canIncreaseSimulationSpeed() const { + return currentSpeed + 1 < GAME_SPEEDS.size(); +} + +bool GameAdvancementHook::canDecreaseSimulationSpeed() const { + return currentSpeed > 0; +} + +GameAdvancementHook& GameAdvancementHook::operator++() { + increaseSimulationSpeed(); + return *this; +}; + +GameAdvancementHook& GameAdvancementHook::operator--() { + decreaseSimulationSpeed(); + return *this; +}; + +void GameAdvancementHook::conditionallyAdvanceGame() { + if (!isPaused) { + time_point_t currentTime = std::chrono::high_resolution_clock::now(); + if (std::chrono::duration_cast(currentTime - lastPolledTime) >= GAME_SPEEDS[currentSpeed]) { + lastPolledTime = currentTime; + if (triggerFunction) triggerFunction(); + } + } + if (refreshFunction) refreshFunction(); +} + +void GameAdvancementHook::reset() { + isPaused = true; + currentSpeed = 0; +} diff --git a/src/openvic-simulation/GameAdvancementHook.hpp b/src/openvic-simulation/GameAdvancementHook.hpp new file mode 100644 index 0000000..59e43a4 --- /dev/null +++ b/src/openvic-simulation/GameAdvancementHook.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include + +namespace OpenVic { + // Conditionally advances game with provided behaviour + // Class governs game speed and pause state + class GameAdvancementHook { + public: + using AdvancementFunction = std::function; + using RefreshFunction = std::function; + using speed_t = int8_t; + + // Minimum number of miliseconds before the simulation advances + static const std::vector GAME_SPEEDS; + + private: + using time_point_t = std::chrono::time_point; + + time_point_t lastPolledTime; + // A function pointer that advances the simulation, intended to be a capturing lambda or something similar. May need to be reworked later + AdvancementFunction triggerFunction; + RefreshFunction refreshFunction; + speed_t currentSpeed; + + public: + bool isPaused; + + GameAdvancementHook(AdvancementFunction tickFunction, RefreshFunction updateFunction, bool startPaused = true, speed_t startingSpeed = 0); + + void setSimulationSpeed(speed_t speed); + speed_t getSimulationSpeed() const; + void increaseSimulationSpeed(); + void decreaseSimulationSpeed(); + bool canIncreaseSimulationSpeed() const; + bool canDecreaseSimulationSpeed() const; + GameAdvancementHook& operator++(); + GameAdvancementHook& operator--(); + void conditionallyAdvanceGame(); + void reset(); + }; +} \ No newline at end of file diff --git a/src/openvic-simulation/GameManager.cpp b/src/openvic-simulation/GameManager.cpp new file mode 100644 index 0000000..ee5050d --- /dev/null +++ b/src/openvic-simulation/GameManager.cpp @@ -0,0 +1,132 @@ +#include "GameManager.hpp" + +#include "openvic-simulation/utility/Logger.hpp" + +using namespace OpenVic; + +GameManager::GameManager(state_updated_func_t state_updated_callback) + : clock { [this]() { tick(); }, [this]() { update_state(); } }, + state_updated { state_updated_callback } {} + +void GameManager::set_needs_update() { + needs_update = true; +} + +void GameManager::update_state() { + if (needs_update) { + Logger::info("Update: ", today); + map.update_state(today); + if (state_updated) state_updated(); + needs_update = false; + } +} + +/* REQUIREMENTS: + * SS-98, SS-101 + */ +void GameManager::tick() { + today++; + Logger::info("Tick: ", today); + map.tick(today); + set_needs_update(); +} + +bool GameManager::setup() { + session_start = time(nullptr); + clock.reset(); + today = { 1836 }; + good_manager.reset_to_defaults(); + bool ret = map.setup(good_manager, building_manager, pop_manager); + set_needs_update(); + return ret; +} + +Date const& GameManager::get_today() const { + return today; +} + +bool GameManager::expand_building(Province::index_t province_index, const std::string_view building_type_identifier) { + set_needs_update(); + Province* province = map.get_province_by_index(province_index); + if (province == nullptr) { + Logger::error("Invalid province index ", province_index, " while trying to expand building ", building_type_identifier); + return false; + } + return province->expand_building(building_type_identifier); +} + +bool GameManager::load_hardcoded_defines() { + bool ret = true; + + static constexpr colour_t LOW_ALPHA_VALUE = float_to_alpha_value(0.4f); + static constexpr colour_t HIGH_ALPHA_VALUE = float_to_alpha_value(0.7f); + using mapmode_t = std::pair; + const std::vector mapmodes { + { "mapmode_terrain", + [](Map const&, Province const& province) -> colour_t { + return LOW_ALPHA_VALUE | (province.is_water() ? 0x4287F5 : 0x0D7017); + } }, + { "mapmode_province", + [](Map const&, Province const& province) -> colour_t { + return HIGH_ALPHA_VALUE | province.get_colour(); + } }, + { "mapmode_region", + [](Map const&, Province const& province) -> colour_t { + Region const* region = province.get_region(); + if (region != nullptr) return HIGH_ALPHA_VALUE | region->get_colour(); + return NULL_COLOUR; + } }, + { "mapmode_index", + [](Map const& map, Province const& province) -> colour_t { + const colour_t f = fraction_to_colour_byte(province.get_index(), map.get_province_count() + 1); + return HIGH_ALPHA_VALUE | (f << 16) | (f << 8) | f; + } }, + { "mapmode_rgo", + [](Map const& map, Province const& province) -> colour_t { + Good const* rgo = province.get_rgo(); + if (rgo != nullptr) return HIGH_ALPHA_VALUE | rgo->get_colour(); + return NULL_COLOUR; + } }, + { "mapmode_infrastructure", + [](Map const& map, Province const& province) -> colour_t { + Building const* railroad = province.get_building_by_identifier("building_railroad"); + if (railroad != nullptr) { + colour_t val = fraction_to_colour_byte(railroad->get_level(), railroad->get_type().get_max_level() + 1, 0.5f, 1.0f); + switch (railroad->get_expansion_state()) { + case Building::ExpansionState::CannotExpand: val <<= 16; break; + case Building::ExpansionState::CanExpand: break; + default: val <<= 8; break; + } + return HIGH_ALPHA_VALUE | val; + } + return NULL_COLOUR; + } }, + { "mapmode_population", + [](Map const& map, Province const& province) -> colour_t { + return HIGH_ALPHA_VALUE | (fraction_to_colour_byte(province.get_total_population(), map.get_highest_province_population() + 1, 0.1f, 1.0f) << 8); + } }, + { "mapmode_culture", + [](Map const& map, Province const& province) -> colour_t { + HasIdentifierAndColour const* largest = get_largest_item(province.get_culture_distribution()).first; + return largest != nullptr ? HIGH_ALPHA_VALUE | largest->get_colour() : NULL_COLOUR; + } }, + { "mapmode_religion", + [](Map const& map, Province const& province) -> colour_t { + HasIdentifierAndColour const* largest = get_largest_item(province.get_religion_distribution()).first; + return largest != nullptr ? HIGH_ALPHA_VALUE | largest->get_colour() : NULL_COLOUR; + } } + }; + for (mapmode_t const& mapmode : mapmodes) + ret &= map.add_mapmode(mapmode.first, mapmode.second); + map.lock_mapmodes(); + + using building_type_t = std::tuple; + const std::vector building_types { + { "building_fort", 4, 8 }, { "building_naval_base", 6, 15 }, { "building_railroad", 5, 10 } + }; + for (building_type_t const& type : building_types) + ret &= building_manager.add_building_type(std::get<0>(type), std::get<1>(type), std::get<2>(type)); + building_manager.lock_building_types(); + + return ret; +} diff --git a/src/openvic-simulation/GameManager.hpp b/src/openvic-simulation/GameManager.hpp new file mode 100644 index 0000000..d221a99 --- /dev/null +++ b/src/openvic-simulation/GameManager.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include "openvic-simulation/GameAdvancementHook.hpp" +#include "openvic-simulation/economy/Good.hpp" +#include "openvic-simulation/map/Map.hpp" + +namespace OpenVic { + struct GameManager { + using state_updated_func_t = std::function; + + Map map; + BuildingManager building_manager; + GoodManager good_manager; + PopManager pop_manager; + GameAdvancementHook clock; + + private: + time_t session_start; /* SS-54, as well as allowing time-tracking */ + Date today; + state_updated_func_t state_updated; + bool needs_update; + + void set_needs_update(); + void update_state(); + void tick(); + + public: + GameManager(state_updated_func_t state_updated_callback); + + bool setup(); + + Date const& get_today() const; + bool expand_building(Province::index_t province_index, const std::string_view building_type_identifier); + + /* Hardcoded data for defining things for which parsing from files has + * not been implemented, currently mapmodes and building types. + */ + bool load_hardcoded_defines(); + }; +} diff --git a/src/openvic-simulation/dataloader/Dataloader.cpp b/src/openvic-simulation/dataloader/Dataloader.cpp new file mode 100644 index 0000000..334d5b8 --- /dev/null +++ b/src/openvic-simulation/dataloader/Dataloader.cpp @@ -0,0 +1,288 @@ +#include "Dataloader.hpp" + +#include "openvic-simulation/GameManager.hpp" +#include "openvic-simulation/utility/Logger.hpp" + +#include +#include +#include + +using namespace OpenVic; +using namespace OpenVic::NodeTools; +using namespace ovdl; + +bool Dataloader::set_roots(std::vector new_roots) { + if (!roots.empty()) { + Logger::error("Overriding existing dataloader roots!"); + roots.clear(); + } + bool ret = true; + for (std::reverse_iterator::const_iterator> it = new_roots.crbegin(); it != new_roots.crend(); ++it) { + if (std::find(roots.begin(), roots.end(), *it) == roots.end()) { + if (std::filesystem::is_directory(*it)) { + Logger::info("Adding dataloader root: ", *it); + roots.push_back(*it); + } else { + Logger::error("Invalid dataloader root (must be an existing directory): ", *it); + ret = false; + } + } else { + Logger::error("Duplicate dataloader root: ", *it); + ret = false; + } + } + if (roots.empty()) { + Logger::error("Dataloader has no roots after attempting to add ", new_roots.size()); + ret = false; + } + return ret; +} + +std::filesystem::path Dataloader::lookup_file(std::filesystem::path const& path) const { + for (std::filesystem::path const& root : roots) { + const std::filesystem::path composed = root / path; + if (std::filesystem::is_regular_file(composed)) { + return composed; + } + } + Logger::error("Lookup for ", path, " failed!"); + return {}; +} + +const std::filesystem::path Dataloader::TXT = ".txt"; + +static bool contains_file_with_name(std::vector const& paths, + std::filesystem::path const& name) { + + for (std::filesystem::path const& path : paths) { + if (path.filename() == name) return true; + } + return false; +} + +std::vector Dataloader::lookup_files_in_dir(std::filesystem::path const& path, + std::filesystem::path const* extension) const { + + std::vector ret; + for (std::filesystem::path const& root : roots) { + const std::filesystem::path composed = root / path; + std::error_code ec; + for (std::filesystem::directory_entry const& entry : std::filesystem::directory_iterator { composed, ec }) { + if (entry.is_regular_file()) { + const std::filesystem::path file = entry; + if (extension == nullptr || file.extension() == *extension) { + if (!contains_file_with_name(ret, file.filename())) { + ret.push_back(file); + } + } + } + } + } + return ret; +} + +bool Dataloader::apply_to_files_in_dir(std::filesystem::path const& path, + std::function callback, + std::filesystem::path const* extension) const { + + bool ret = true; + for (std::filesystem::path const& file : lookup_files_in_dir(path, extension)) { + ret &= callback(file); + } + return ret; +} + +template Parser, bool(Parser::*parse_func)()> +static Parser _run_ovdl_parser(std::filesystem::path const& path) { + Parser parser; + std::string buffer; + auto error_log_stream = ovdl::detail::CallbackStream { + [](void const* s, std::streamsize n, void* user_data) -> std::streamsize { + if (s != nullptr && n > 0 && user_data != nullptr) { + static_cast(user_data)->append(static_cast(s), n); + return n; + } else { + Logger::error("Invalid input to parser error log callback: ", s, " / ", n, " / ", user_data); + return 0; + } + }, &buffer + }; + parser.set_error_log_to(error_log_stream); + parser.load_from_file(path); + if (!buffer.empty()) { + Logger::error("Parser load errors:\n\n", buffer, "\n"); + buffer.clear(); + } + if (parser.has_fatal_error() || parser.has_error()) { + Logger::error("Parser errors while loading ", path); + return parser; + } + if (!(parser.*parse_func)()) { + Logger::error("Parse function returned false!"); + } + if (!buffer.empty()) { + Logger::error("Parser parse errors:\n\n", buffer, "\n"); + buffer.clear(); + } + if (parser.has_fatal_error() || parser.has_error()) { + Logger::error("Parser errors while parsing ", path); + } + return parser; +} + +static v2script::Parser _parse_defines(std::filesystem::path const& path) { + return _run_ovdl_parser(path); +} + +static csv::Windows1252Parser _parse_csv(std::filesystem::path const& path) { + return _run_ovdl_parser(path); +} + +bool Dataloader::_load_pop_types(PopManager& pop_manager, std::filesystem::path const& pop_type_directory) const { + const bool ret = apply_to_files_in_dir(pop_type_directory, + [&pop_manager](std::filesystem::path const& file) -> bool { + return pop_manager.load_pop_type_file(file, _parse_defines(file).get_file_node()); + } + ); + if (!ret) { + Logger::error("Failed to load pop types!"); + } + pop_manager.lock_pop_types(); + return ret; +} + +bool Dataloader::_load_map_dir(Map& map, std::filesystem::path const& map_directory) const { + static const std::filesystem::path defaults_filename = "default.map"; + static const std::string default_definitions = "definition.csv"; + static const std::string default_provinces = "provinces.bmp"; + static const std::string default_positions = "positions.txt"; + static const std::string default_terrain = "terrain.bmp"; + static const std::string default_rivers = "rivers.bmp"; + static const std::string default_terrain_definition = "terrain.txt"; + static const std::string default_tree_definition = "trees.txt"; + static const std::string default_continent = "continent.txt"; + static const std::string default_adjacencies = "adjacencies.csv"; + static const std::string default_region = "region.txt"; + static const std::string default_region_sea = "region_sea.txt"; + static const std::string default_province_flag_sprite = "province_flag_sprites"; + + const v2script::Parser parser = _parse_defines(lookup_file(map_directory / defaults_filename)); + + std::vector water_province_identifiers; + +#define APPLY_TO_MAP_PATHS(F) \ + F(definitions) F(provinces) F(positions) F(terrain) F(rivers) \ + F(terrain_definition) F(tree_definition) F(continent) F(adjacencies) \ + F(region) F(region_sea) F(province_flag_sprite) + +#define MAP_PATH_VAR(X) std::string_view X = default_##X; + APPLY_TO_MAP_PATHS(MAP_PATH_VAR) +#undef MAP_PATH_VAR + + bool ret = expect_dictionary_keys( + "max_provinces", ONE_EXACTLY, + expect_uint( + [&map](uint64_t val) -> bool { + if (Province::NULL_INDEX < val && val <= Province::MAX_INDEX) { + return map.set_max_provinces(val); + } + Logger::error("Invalid max province count ", val, " (out of valid range ", Province::NULL_INDEX, " < max_provinces <= ", Province::MAX_INDEX, ")"); + return false; + } + ), + "sea_starts", ONE_EXACTLY, + expect_list_reserve_length( + water_province_identifiers, + expect_identifier( + [&water_province_identifiers](std::string_view identifier) -> bool { + water_province_identifiers.push_back(identifier); + return true; + } + ) + ), + +#define MAP_PATH_DICT_ENTRY(X) \ + #X, ONE_EXACTLY, expect_string(assign_variable_callback(X)), + APPLY_TO_MAP_PATHS(MAP_PATH_DICT_ENTRY) +#undef MAP_PATH_DICT_ENTRY + +#undef APPLY_TO_MAP_PATHS + + "border_heights", ZERO_OR_ONE, success_callback, + "terrain_sheet_heights", ZERO_OR_ONE, success_callback, + "tree", ZERO_OR_ONE, success_callback, + "border_cutoff", ZERO_OR_ONE, success_callback + )(parser.get_file_node()); + + if (!ret) { + Logger::error("Failed to load map default file!"); + } + + if (!map.load_province_definitions(_parse_csv(lookup_file(map_directory / definitions)).get_lines())) { + Logger::error("Failed to load province definitions file!"); + ret = false; + } + + if (!map.set_water_province_list(water_province_identifiers)) { + Logger::error("Failed to set water provinces!"); + ret = false; + } + map.lock_water_provinces(); + + return ret; +} + +bool Dataloader::load_defines(GameManager& game_manager) const { + static const std::filesystem::path good_file = "common/goods.txt"; + static const std::filesystem::path pop_type_directory = "poptypes"; + static const std::filesystem::path graphical_culture_type_file = "common/graphicalculturetype.txt"; + static const std::filesystem::path culture_file = "common/cultures.txt"; + static const std::filesystem::path religion_file = "common/religion.txt"; + static const std::filesystem::path map_directory = "map"; + + bool ret = true; + + if (!game_manager.good_manager.load_good_file(_parse_defines(lookup_file(good_file)).get_file_node())) { + Logger::error("Failed to load goods!"); + ret = false; + } + if (!_load_pop_types(game_manager.pop_manager, pop_type_directory)) { + Logger::error("Failed to load pop types!"); + ret = false; + } + if (!game_manager.pop_manager.culture_manager.load_graphical_culture_type_file(_parse_defines(lookup_file(graphical_culture_type_file)).get_file_node())) { + Logger::error("Failed to load graphical culture types!"); + ret = false; + } + if (!game_manager.pop_manager.culture_manager.load_culture_file(_parse_defines(lookup_file(culture_file)).get_file_node())) { + Logger::error("Failed to load cultures!"); + ret = false; + } + if (!game_manager.pop_manager.religion_manager.load_religion_file(_parse_defines(lookup_file(religion_file)).get_file_node())) { + Logger::error("Failed to load religions!"); + ret = false; + } + if (!_load_map_dir(game_manager.map, map_directory)) { + Logger::error("Failed to load map!"); + ret = false; + } + + return ret; +} + +bool Dataloader::load_pop_history(GameManager& game_manager, std::filesystem::path const& path) const { + return apply_to_files_in_dir(path, + [&game_manager](std::filesystem::path const& file) -> bool { + return expect_dictionary( + [&game_manager](std::string_view province_key, ast::NodeCPtr province_node) -> bool { + Province* province = game_manager.map.get_province_by_identifier(province_key); + if (province == nullptr) { + Logger::error("Invalid province id: ", province_key); + return false; + } + return province->load_pop_list(game_manager.pop_manager, province_node); + } + )(_parse_defines(file).get_file_node()); + } + ); +} diff --git a/src/openvic-simulation/dataloader/Dataloader.hpp b/src/openvic-simulation/dataloader/Dataloader.hpp new file mode 100644 index 0000000..f723803 --- /dev/null +++ b/src/openvic-simulation/dataloader/Dataloader.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include + +namespace OpenVic { + struct GameManager; + struct PopManager; + struct Map; + + class Dataloader { + std::vector roots; + + bool _load_pop_types(PopManager& pop_manager, std::filesystem::path const& pop_type_directory) const; + bool _load_map_dir(Map& map, std::filesystem::path const& map_directory) const; + + public: + Dataloader() = default; + + /* In reverse-load order, so base defines first and final loaded mod last */ + bool set_roots(std::vector new_roots); + + std::filesystem::path lookup_file(std::filesystem::path const& path) const; + static const std::filesystem::path TXT; + std::vector lookup_files_in_dir(std::filesystem::path const& path, + std::filesystem::path const* extension = &TXT) const; + bool apply_to_files_in_dir(std::filesystem::path const& path, + std::function callback, + std::filesystem::path const* extension = &TXT) const; + + bool load_defines(GameManager& game_manager) const; + bool load_pop_history(GameManager& game_manager, std::filesystem::path const& path) const; + }; +} diff --git a/src/openvic-simulation/dataloader/NodeTools.cpp b/src/openvic-simulation/dataloader/NodeTools.cpp new file mode 100644 index 0000000..3b05d04 --- /dev/null +++ b/src/openvic-simulation/dataloader/NodeTools.cpp @@ -0,0 +1,256 @@ +#include "NodeTools.hpp" + +#include + +using namespace OpenVic; +using namespace OpenVic::NodeTools; + +template +static node_callback_t _expect_type(callback_t callback) { + return [callback](ast::NodeCPtr node) -> bool { + if (node != nullptr) { + T const* cast_node = node->cast_to(); + if (cast_node != nullptr) { + return callback(*cast_node); + } + Logger::error("Invalid node type ", node->get_type(), " when expecting ", T::get_type_static()); + } else { + Logger::error("Null node when expecting ", T::get_type_static()); + } + return false; + }; +} + +template +requires(std::derived_from) +static callback_t abstract_string_node_callback(callback_t callback) { + return [callback](T const& node) -> bool { + return callback(node._name); + }; +} + +node_callback_t NodeTools::expect_identifier(callback_t callback) { + return _expect_type(abstract_string_node_callback(callback)); +} + +node_callback_t NodeTools::expect_string(callback_t callback) { + return _expect_type(abstract_string_node_callback(callback)); +} + +node_callback_t NodeTools::expect_identifier_or_string(callback_t callback) { + return [callback](ast::NodeCPtr node) -> bool { + if (node != nullptr) { + ast::AbstractStringNode const* cast_node = node->cast_to(); + if (cast_node == nullptr) { + cast_node = node->cast_to(); + } + if (cast_node != nullptr) { + return abstract_string_node_callback(callback)(*cast_node); + } + Logger::error("Invalid node type ", node->get_type(), " when expecting ", ast::IdentifierNode::get_type_static(), " or ", ast::StringNode::get_type_static()); + } else { + Logger::error("Null node when expecting ", ast::IdentifierNode::get_type_static(), " or ", ast::StringNode::get_type_static()); + } + return false; + }; +} + +node_callback_t NodeTools::expect_bool(callback_t callback) { + return expect_identifier( + [callback](std::string_view identifier) -> bool { + if (identifier == "yes") { + return callback(true); + } else if (identifier == "no") { + return callback(false); + } + Logger::error("Invalid bool identifier text: ", identifier); + return false; + } + ); +} + +node_callback_t NodeTools::expect_int(callback_t callback) { + return expect_identifier( + [callback](std::string_view identifier) -> bool { + bool successful = false; + const int64_t val = StringUtils::string_to_int64(identifier, &successful, 10); + if (successful) { + return callback(val); + } + Logger::error("Invalid int identifier text: ", identifier); + return false; + } + ); +} + +node_callback_t NodeTools::expect_uint(callback_t callback) { + return expect_identifier( + [callback](std::string_view identifier) -> bool { + bool successful = false; + const uint64_t val = StringUtils::string_to_uint64(identifier, &successful, 10); + if (successful) { + return callback(val); + } + Logger::error("Invalid uint identifier text: ", identifier); + return false; + } + ); +} + +node_callback_t NodeTools::expect_fixed_point(callback_t callback) { + return expect_identifier( + [callback](std::string_view identifier) -> bool { + bool successful = false; + const fixed_point_t val = fixed_point_t::parse(identifier.data(), identifier.length(), &successful); + if (successful) { + return callback(val); + } + Logger::error("Invalid fixed point identifier text: ", identifier); + return false; + } + ); +} + +node_callback_t NodeTools::expect_colour(callback_t callback) { + return [callback](ast::NodeCPtr node) -> bool { + colour_t col = NULL_COLOUR; + uint32_t components = 0; + bool ret = expect_list_of_length(3, + expect_fixed_point( + [&col, &components](fixed_point_t val) -> bool { + components++; + col <<= 8; + if (val < 0 || val > 255) { + Logger::error("Invalid colour component: ", val); + return false; + } else { + if (val <= 1) val *= 255; + col |= val.to_int32_t(); + return true; + } + } + ) + )(node); + ret &= callback(col << 8 * (3 - components)); + return ret; + }; +} + +node_callback_t NodeTools::expect_date(callback_t callback) { + return expect_identifier( + [callback](std::string_view identifier) -> bool { + bool successful = false; + const Date date = Date::from_string(identifier, &successful); + if (successful) { + return callback(date); + } + Logger::error("Invalid date identifier text: ", identifier); + return false; + } + ); +} + +node_callback_t NodeTools::expect_assign(key_value_callback_t callback) { + return _expect_type( + [callback](ast::AssignNode const& assign_node) -> bool { + return callback(assign_node._name, assign_node._initializer.get()); + } + ); +} + +node_callback_t NodeTools::expect_list_and_length(length_callback_t length_callback, node_callback_t callback) { + return _expect_type( + [length_callback, callback](ast::AbstractListNode const& list_node) -> bool { + std::vector const& list = list_node._statements; + bool ret = true; + size_t size = length_callback(list.size()); + if (size > list.size()) { + Logger::error("Trying to read more values than the list contains: ", size, " > ", list.size()); + size = list.size(); + ret = false; + } + std::for_each(list.begin(), list.begin() + size, + [callback, &ret](ast::NodeUPtr const& sub_node) -> void { + ret &= callback(sub_node.get()); + } + ); + return ret; + } + ); +} + +node_callback_t NodeTools::expect_list_of_length(size_t length, node_callback_t callback) { + return [length, callback](ast::NodeCPtr node) -> bool { + bool ret = true; + ret &= expect_list_and_length( + [length, &ret](size_t size) -> size_t { + if (size != length) { + Logger::error("List length ", size, " does not match expected length ", length); + ret = false; + if (length < size) return length; + } + return size; + }, + callback + )(node); + return ret; + }; +} + +node_callback_t NodeTools::expect_list(node_callback_t callback) { + return expect_list_and_length(default_length_callback, callback); +} + +node_callback_t NodeTools::expect_dictionary_and_length(length_callback_t length_callback, key_value_callback_t callback) { + return expect_list_and_length(length_callback, expect_assign(callback)); +} + +node_callback_t NodeTools::expect_dictionary(key_value_callback_t callback) { + return expect_dictionary_and_length(default_length_callback, callback); +} + +node_callback_t NodeTools::_expect_dictionary_keys_and_length(length_callback_t length_callback, bool allow_other_keys, key_map_t&& key_map) { + return [length_callback, allow_other_keys, key_map = std::move(key_map)](ast::NodeCPtr node) mutable -> bool { + bool ret = expect_dictionary_and_length( + length_callback, + [&key_map, allow_other_keys](std::string_view key, ast::NodeCPtr value) -> bool { + const key_map_t::iterator it = key_map.find(key); + if (it == key_map.end()) { + if (allow_other_keys) return true; + Logger::error("Invalid dictionary key: ", key); + return false; + } + dictionary_entry_t& entry = it->second; + if (++entry.count > 1 && !entry.can_repeat()) { + Logger::error("Invalid repeat of dictionary key: ", key); + return false; + } + return entry.callback(value); + } + )(node); + for (key_map_t::value_type const& key_entry : key_map) { + dictionary_entry_t const& entry = key_entry.second; + if (entry.must_appear() && entry.count < 1) { + Logger::error("Mandatory dictionary key not present: ", key_entry.first); + ret = false; + } + } + return ret; + }; +} + +node_callback_t NodeTools::name_list_callback(std::vector& list) { + return expect_list_reserve_length( + list, + expect_identifier_or_string( + [&list](std::string_view str) -> bool { + if (!str.empty()) { + list.push_back(std::string { str }); + return true; + } + Logger::error("Empty identifier or string"); + return false; + } + ) + ); +} diff --git a/src/openvic-simulation/dataloader/NodeTools.hpp b/src/openvic-simulation/dataloader/NodeTools.hpp new file mode 100644 index 0000000..805de94 --- /dev/null +++ b/src/openvic-simulation/dataloader/NodeTools.hpp @@ -0,0 +1,164 @@ +#pragma once + +#include + +#include "openvic-simulation/types/Colour.hpp" +#include "openvic-simulation/types/Date.hpp" +#include "openvic-simulation/types/fixed_point/FixedPoint.hpp" + +#include + +namespace OpenVic { + namespace ast = ovdl::v2script::ast; + + namespace NodeTools { + + template + using callback_t = std::function; + + using node_callback_t = callback_t; + constexpr bool success_callback(ast::NodeCPtr) { return true; } + + using key_value_callback_t = callback_t; + + node_callback_t expect_identifier(callback_t callback); + node_callback_t expect_string(callback_t callback); + node_callback_t expect_identifier_or_string(callback_t callback); + node_callback_t expect_bool(callback_t callback); + node_callback_t expect_int(callback_t callback); + node_callback_t expect_uint(callback_t callback); + node_callback_t expect_fixed_point(callback_t callback); + node_callback_t expect_colour(callback_t callback); + node_callback_t expect_date(callback_t callback); + node_callback_t expect_assign(key_value_callback_t callback); + + using length_callback_t = std::function; + constexpr size_t default_length_callback(size_t size) { return size; }; + + node_callback_t expect_list_and_length(length_callback_t length_callback, node_callback_t callback); + node_callback_t expect_list_of_length(size_t length, node_callback_t callback); + node_callback_t expect_list(node_callback_t callback); + + node_callback_t expect_dictionary_and_length(length_callback_t length_callback, key_value_callback_t callback); + node_callback_t expect_dictionary(key_value_callback_t callback); + + struct dictionary_entry_t { + const enum class expected_count_t : uint8_t { + _MUST_APPEAR = 0b01, + _CAN_REPEAT = 0b10, + + ZERO_OR_ONE = 0, + ONE_EXACTLY = _MUST_APPEAR, + ZERO_OR_MORE = _CAN_REPEAT, + ONE_OR_MORE = _MUST_APPEAR | _CAN_REPEAT + } expected_count; + const node_callback_t callback; + size_t count; + + dictionary_entry_t(expected_count_t new_expected_count, node_callback_t new_callback) + : expected_count { new_expected_count }, callback { new_callback }, count { 0 } {} + + constexpr bool must_appear() const { + return static_cast(expected_count) & static_cast(expected_count_t::_MUST_APPEAR); + } + constexpr bool can_repeat() const { + return static_cast(expected_count) & static_cast(expected_count_t::_CAN_REPEAT); + } + }; + using enum dictionary_entry_t::expected_count_t; + using key_map_t = std::map>; + + constexpr struct allow_other_keys_t {} ALLOW_OTHER_KEYS; + + node_callback_t _expect_dictionary_keys_and_length(length_callback_t length_callback, bool allow_other_keys, key_map_t&& key_map); + + template + node_callback_t _expect_dictionary_keys_and_length(length_callback_t length_callback, + bool allow_other_keys, key_map_t&& key_map, + const std::string_view key, dictionary_entry_t::expected_count_t expected_count, node_callback_t callback, + Args... args) { + if (key_map.find(key) == key_map.end()) { + key_map.emplace(key, dictionary_entry_t { expected_count, callback }); + } else { + Logger::error("Duplicate expected dictionary key: ", key); + } + return _expect_dictionary_keys_and_length(length_callback, allow_other_keys, std::move(key_map), args...); + } + + template + node_callback_t expect_dictionary_keys_and_length(length_callback_t length_callback, + const std::string_view key, dictionary_entry_t::expected_count_t expected_count, node_callback_t callback, + Args... args) { + return _expect_dictionary_keys_and_length(length_callback, false, {}, key, expected_count, callback, args...); + } + + template + node_callback_t expect_dictionary_keys_and_length(length_callback_t length_callback, + allow_other_keys_t, Args... args) { + return _expect_dictionary_keys_and_length(length_callback, true, {}, args...); + } + + template + node_callback_t expect_dictionary_keys(Args... args) { + return expect_dictionary_keys_and_length(default_length_callback, args...); + } + + template + concept Reservable = requires(T& t) { + { t.size() } -> std::same_as; + t.reserve( size_t {} ); + }; + template + node_callback_t expect_list_reserve_length(T& t, node_callback_t callback) { + return expect_list_and_length( + [&t](size_t size) -> size_t { + t.reserve(t.size() + size); + return size; + }, + callback + ); + } + template + node_callback_t expect_dictionary_reserve_length(T& t, key_value_callback_t callback) { + return expect_list_reserve_length(t, expect_assign(callback)); + } + + node_callback_t name_list_callback(std::vector& list); + + template + callback_t assign_variable_callback(T& var) { + return [&var](T val) -> bool { + var = val; + return true; + }; + } + + template + requires(std::integral) + callback_t assign_variable_callback_uint(const std::string_view name, T& var) { + return [&var, name](uint64_t val) -> bool { + if (val <= std::numeric_limits::max()) { + var = val; + return true; + } + Logger::error("Invalid ", name, ": ", val, " (valid range: [0, ", static_cast(std::numeric_limits::max()), "])"); + return false; + }; + } + + template + requires(std::integral) + callback_t assign_variable_callback_int(const std::string_view name, T& var) { + return [&var, name](int64_t val) -> bool { + if (std::numeric_limits::lowest() <= val && val <= std::numeric_limits::max()) { + var = val; + return true; + } + Logger::error("Invalid ", name, ": ", val, " (valid range: [", + static_cast(std::numeric_limits::lowest()), ", ", + static_cast(std::numeric_limits::max()), "])"); + return false; + }; + } + } +} diff --git a/src/openvic-simulation/economy/Good.cpp b/src/openvic-simulation/economy/Good.cpp new file mode 100644 index 0000000..e3dbd3e --- /dev/null +++ b/src/openvic-simulation/economy/Good.cpp @@ -0,0 +1,140 @@ +#include "Good.hpp" + +#include + +using namespace OpenVic; +using namespace OpenVic::NodeTools; + +GoodCategory::GoodCategory(const std::string_view new_identifier) : HasIdentifier { new_identifier } {} + +Good::Good(const std::string_view new_identifier, colour_t new_colour, GoodCategory const& new_category, price_t new_base_price, + bool new_available_from_start, bool new_tradeable, bool new_money, bool new_overseas_penalty) + : HasIdentifierAndColour { new_identifier, new_colour, true }, + category { new_category }, + base_price { new_base_price }, + available_from_start { new_available_from_start }, + tradeable { new_tradeable }, + money { new_money }, + overseas_penalty { new_overseas_penalty } { + assert(base_price > NULL_PRICE); +} + +GoodCategory const& Good::get_category() const { + return category; +} + +Good::price_t Good::get_base_price() const { + return base_price; +} + +Good::price_t Good::get_price() const { + return price; +} + +bool Good::get_available_from_start() const { + return available_from_start; +} + +bool Good::get_available() const { + return available; +} + +bool Good::get_tradeable() const { + return tradeable; +} + +bool Good::get_money() const { + return money; +} + +bool Good::get_overseas_penalty() { + return overseas_penalty; +} + +void Good::reset_to_defaults() { + available = available_from_start; + price = base_price; +} + +GoodManager::GoodManager() : good_categories { "good categories" }, goods { "goods" } {} + +bool GoodManager::add_good_category(const std::string_view identifier) { + if (identifier.empty()) { + Logger::error("Invalid good category identifier - empty!"); + return false; + } + return good_categories.add_item({ identifier }); +} + +bool GoodManager::add_good(const std::string_view identifier, colour_t colour, GoodCategory const* category, + Good::price_t base_price, bool available_from_start, bool tradeable, bool money, bool overseas_penalty) { + if (identifier.empty()) { + Logger::error("Invalid good identifier - empty!"); + return false; + } + if (colour > MAX_COLOUR_RGB) { + Logger::error("Invalid good colour for ", identifier, ": ", colour_to_hex_string(colour)); + return false; + } + if (category == nullptr) { + Logger::error("Invalid good category for ", identifier, ": null"); + return false; + } + if (base_price <= Good::NULL_PRICE) { + Logger::error("Invalid base price for ", identifier, ": ", base_price); + return false; + } + return goods.add_item({ identifier, colour, *category, base_price, available_from_start, tradeable, money, overseas_penalty }); +} + +void GoodManager::reset_to_defaults() { + for (Good& good : goods.get_items()) + good.reset_to_defaults(); +} + +bool GoodManager::load_good_file(ast::NodeCPtr root) { + size_t total_expected_goods = 0; + bool ret = expect_dictionary_reserve_length( + good_categories, + [this, &total_expected_goods](std::string_view key, ast::NodeCPtr value) -> bool { + bool ret = expect_list_and_length( + [&total_expected_goods](size_t size) -> size_t { + total_expected_goods += size; + return 0; + }, + success_callback + )(value); + ret &= add_good_category(key); + return ret; + } + )(root); + lock_good_categories(); + goods.reserve(goods.size() + total_expected_goods); + ret &= expect_dictionary( + [this](std::string_view good_category_key, ast::NodeCPtr good_category_value) -> bool { + GoodCategory const* good_category = get_good_category_by_identifier(good_category_key); + + return expect_dictionary( + [this, good_category](std::string_view key, ast::NodeCPtr value) -> bool { + colour_t colour = NULL_COLOUR; + Good::price_t base_price; + bool available_from_start, tradeable = true; + bool money, overseas_penalty = false; + + bool ret = expect_dictionary_keys( + "color", ONE_EXACTLY, expect_colour(assign_variable_callback(colour)), + "cost", ONE_EXACTLY, expect_fixed_point(assign_variable_callback(base_price)), + "available_from_start", ZERO_OR_ONE, expect_bool(assign_variable_callback(available_from_start)), + "tradeable", ZERO_OR_ONE, expect_bool(assign_variable_callback(tradeable)), + "money", ZERO_OR_ONE, expect_bool(assign_variable_callback(money)), + "overseas_penalty", ZERO_OR_ONE, expect_bool(assign_variable_callback(overseas_penalty)) + )(value); + ret &= add_good(key, colour, good_category, base_price, available_from_start, tradeable, money, overseas_penalty); + return ret; + } + )(good_category_value); + } + )(root); + lock_goods(); + return ret; +} diff --git a/src/openvic-simulation/economy/Good.hpp b/src/openvic-simulation/economy/Good.hpp new file mode 100644 index 0000000..d5cd532 --- /dev/null +++ b/src/openvic-simulation/economy/Good.hpp @@ -0,0 +1,79 @@ +#pragma once + +#include "openvic-simulation/types/IdentifierRegistry.hpp" +#include "openvic-simulation/dataloader/NodeTools.hpp" + +namespace OpenVic { + struct GoodManager; + + struct GoodCategory : HasIdentifier { + friend struct GoodManager; + + private: + GoodCategory(const std::string_view new_identifier); + + public: + GoodCategory(GoodCategory&&) = default; + }; + + /* REQUIREMENTS: + * + * ECON-3 , ECON-4 , ECON-5 , ECON-6 , ECON-7 , ECON-8 , ECON-9 , ECON-10, ECON-11, ECON-12, ECON-13, ECON-14, + * ECON-15, ECON-16, ECON-17, ECON-18, ECON-19, ECON-20, ECON-21, ECON-22, ECON-23, ECON-24, ECON-25, ECON-26, + * ECON-27, ECON-28, ECON-29, ECON-30, ECON-31, ECON-32, ECON-33, ECON-34, ECON-35, ECON-36, ECON-37, ECON-38, + * ECON-39, ECON-40, ECON-41, ECON-42, ECON-43, ECON-44, ECON-45, ECON-46, ECON-47, ECON-48, ECON-49, ECON-50 + * + * ECON-123, ECON-124, ECON-125, ECON-126, ECON-127, ECON-128, ECON-129, ECON-130, ECON-131, ECON-132, ECON-133, ECON-134, + * ECON-135, ECON-136, ECON-137, ECON-138, ECON-139, ECON-140, ECON-141, ECON-142, ECON-234, ECON-235, ECON-236, ECON-237, + * ECON-238, ECON-239, ECON-240, ECON-241, ECON-242, ECON-243, ECON-244, ECON-245, ECON-246, ECON-247, ECON-248, ECON-249, + * ECON-250, ECON-251, ECON-252, ECON-253, ECON-254, ECON-255, ECON-256, ECON-257, ECON-258, ECON-259, ECON-260, ECON-261 + */ + struct Good : HasIdentifierAndColour { + friend struct GoodManager; + + using price_t = fixed_point_t; + static constexpr price_t NULL_PRICE = fixed_point_t::_0(); + + private: + GoodCategory const& category; + const price_t base_price; + price_t price; + const bool available_from_start, tradeable, money, overseas_penalty; + bool available; + + Good(const std::string_view new_identifier, colour_t new_colour, GoodCategory const& new_category, price_t new_base_price, + bool new_available_from_start, bool new_tradeable, bool new_money, bool new_overseas_penalty); + + public: + Good(Good&&) = default; + + GoodCategory const& get_category() const; + price_t get_base_price() const; + price_t get_price() const; + bool get_available_from_start() const; + bool get_available() const; + bool get_tradeable() const; + bool get_money() const; + bool get_overseas_penalty(); + void reset_to_defaults(); + }; + + struct GoodManager { + private: + IdentifierRegistry good_categories; + IdentifierRegistry goods; + + public: + GoodManager(); + + bool add_good_category(const std::string_view identifier); + IDENTIFIER_REGISTRY_ACCESSORS_CUSTOM_PLURAL(GoodCategory, good_category, good_categories) + + bool add_good(const std::string_view identifier, colour_t colour, GoodCategory const* category, Good::price_t base_price, + bool available_from_start, bool tradeable, bool money, bool overseas_penalty); + IDENTIFIER_REGISTRY_ACCESSORS(Good, good) + + void reset_to_defaults(); + bool load_good_file(ast::NodeCPtr root); + }; +} diff --git a/src/openvic-simulation/map/Building.cpp b/src/openvic-simulation/map/Building.cpp new file mode 100644 index 0000000..6e5cf18 --- /dev/null +++ b/src/openvic-simulation/map/Building.cpp @@ -0,0 +1,127 @@ +#include "Building.hpp" + +#include + +#include "openvic-simulation/map/Province.hpp" +#include "openvic-simulation/utility/Logger.hpp" + +using namespace OpenVic; + +Building::Building(BuildingType const& new_type) + : HasIdentifier { new_type.get_identifier() }, + type { new_type } {} + +bool Building::_can_expand() const { + return level < type.get_max_level(); +} + +BuildingType const& Building::get_type() const { + return type; +} + +Building::level_t Building::get_level() const { + return level; +} + +Building::ExpansionState Building::get_expansion_state() const { + return expansion_state; +} + +Date const& Building::get_start_date() const { + return start; +} + +Date const& Building::get_end_date() const { + return end; +} + +float Building::get_expansion_progress() const { + return expansion_progress; +} + +bool Building::expand() { + if (expansion_state == ExpansionState::CanExpand) { + expansion_state = ExpansionState::Preparing; + expansion_progress = 0.0f; + return true; + } + return false; +} + +/* REQUIREMENTS: + * MAP-71, MAP-74, MAP-77 + */ +void Building::update_state(Date const& today) { + switch (expansion_state) { + case ExpansionState::Preparing: + start = today; + end = start + type.get_build_time(); + break; + case ExpansionState::Expanding: + expansion_progress = static_cast(today - start) / static_cast(end - start); + break; + default: expansion_state = _can_expand() ? ExpansionState::CanExpand : ExpansionState::CannotExpand; + } +} + +void Building::tick(Date const& today) { + if (expansion_state == ExpansionState::Preparing) { + expansion_state = ExpansionState::Expanding; + } + if (expansion_state == ExpansionState::Expanding) { + if (end <= today) { + level++; + expansion_state = ExpansionState::CannotExpand; + } + } +} + +BuildingType::BuildingType(const std::string_view new_identifier, Building::level_t new_max_level, Timespan new_build_time) + : HasIdentifier { new_identifier }, + max_level { new_max_level }, + build_time { new_build_time } { + assert(max_level >= 0); + assert(build_time >= 0); +} + +Building::level_t BuildingType::get_max_level() const { + return max_level; +} + +Timespan BuildingType::get_build_time() const { + return build_time; +} + +BuildingManager::BuildingManager() : building_types { "building types" } {} + +bool BuildingManager::add_building_type(const std::string_view identifier, Building::level_t max_level, Timespan build_time) { + if (identifier.empty()) { + Logger::error("Invalid building type identifier - empty!"); + return false; + } + if (max_level < 0) { + Logger::error("Invalid building type max level for ", identifier, ": ", max_level); + return false; + } + if (build_time < 0) { + Logger::error("Invalid building type build time for ", identifier, ": ", build_time); + return false; + } + return building_types.add_item({ identifier, max_level, build_time }); +} + +bool BuildingManager::generate_province_buildings(Province& province) const { + province.reset_buildings(); + if (!building_types.is_locked()) { + Logger::error("Cannot generate buildings until building types are locked!"); + return false; + } + bool ret = true; + if (!province.is_water()) { + for (BuildingType const& type : building_types.get_items()) { + ret &= province.add_building({ type }); + } + } + province.lock_buildings(); + return ret; +} diff --git a/src/openvic-simulation/map/Building.hpp b/src/openvic-simulation/map/Building.hpp new file mode 100644 index 0000000..d36dfd4 --- /dev/null +++ b/src/openvic-simulation/map/Building.hpp @@ -0,0 +1,85 @@ +#pragma once + +#include + +#include "openvic-simulation/types/Date.hpp" +#include "openvic-simulation/types/IdentifierRegistry.hpp" + +namespace OpenVic { + + struct BuildingManager; + struct BuildingType; + + /* REQUIREMENTS: + * MAP-11, MAP-72, MAP-73 + * MAP-12, MAP-75, MAP-76 + * MAP-13, MAP-78, MAP-79 + */ + struct Building : HasIdentifier { + friend struct BuildingManager; + + using level_t = int8_t; + + enum class ExpansionState { + CannotExpand, + CanExpand, + Preparing, + Expanding + }; + + private: + BuildingType const& type; + level_t level = 0; + ExpansionState expansion_state = ExpansionState::CannotExpand; + Date start, end; + float expansion_progress; + + Building(BuildingType const& new_type); + + bool _can_expand() const; + + public: + Building(Building&&) = default; + + BuildingType const& get_type() const; + level_t get_level() const; + ExpansionState get_expansion_state() const; + Date const& get_start_date() const; + Date const& get_end_date() const; + float get_expansion_progress() const; + + bool expand(); + void update_state(Date const& today); + void tick(Date const& today); + }; + + struct BuildingType : HasIdentifier { + friend struct BuildingManager; + + private: + const Building::level_t max_level; + const Timespan build_time; + + BuildingType(const std::string_view new_identifier, Building::level_t new_max_level, Timespan new_build_time); + + public: + BuildingType(BuildingType&&) = default; + + Building::level_t get_max_level() const; + Timespan get_build_time() const; + }; + + struct Province; + + struct BuildingManager { + private: + IdentifierRegistry building_types; + + public: + BuildingManager(); + + bool add_building_type(const std::string_view identifier, Building::level_t max_level, Timespan build_time); + IDENTIFIER_REGISTRY_ACCESSORS(BuildingType, building_type) + bool generate_province_buildings(Province& province) const; + }; +} diff --git a/src/openvic-simulation/map/Map.cpp b/src/openvic-simulation/map/Map.cpp new file mode 100644 index 0000000..3f86ccc --- /dev/null +++ b/src/openvic-simulation/map/Map.cpp @@ -0,0 +1,509 @@ +#include "Map.hpp" + +#include +#include + +#include "openvic-simulation/economy/Good.hpp" +#include "openvic-simulation/utility/Logger.hpp" + +using namespace OpenVic; + +Mapmode::Mapmode(const std::string_view new_identifier, index_t new_index, colour_func_t new_colour_func) + : HasIdentifier { new_identifier }, + index { new_index }, + colour_func { new_colour_func } { + assert(colour_func != nullptr); +} + +const Mapmode Mapmode::ERROR_MAPMODE { "mapmode_error", 0, + [](Map const& map, Province const& province) -> colour_t { return 0xFFFF0000; } }; + +Mapmode::index_t Mapmode::get_index() const { + return index; +} + +colour_t Mapmode::get_colour(Map const& map, Province const& province) const { + return colour_func ? colour_func(map, province) : NULL_COLOUR; +} + +Map::Map() : provinces { "provinces" }, + regions { "regions" }, + mapmodes { "mapmodes" } {} + +bool Map::add_province(const std::string_view identifier, colour_t colour) { + if (provinces.size() >= max_provinces) { + Logger::error("The map's province list is full - maximum number of provinces is ", max_provinces, " (this can be at most ", Province::MAX_INDEX, ")"); + return false; + } + if (identifier.empty()) { + Logger::error("Invalid province identifier - empty!"); + return false; + } + if (colour == NULL_COLOUR || colour > MAX_COLOUR_RGB) { + Logger::error("Invalid province colour for ", identifier, ": ", colour_to_hex_string(colour)); + return false; + } + Province new_province { identifier, colour, static_cast(provinces.size() + 1) }; + const Province::index_t index = get_index_from_colour(colour); + if (index != Province::NULL_INDEX) { + Logger::error("Duplicate province colours: ", get_province_by_index(index)->to_string(), " and ", new_province.to_string()); + return false; + } + colour_index_map[new_province.get_colour()] = new_province.get_index(); + return provinces.add_item(std::move(new_province)); +} + +void Map::lock_provinces() { + provinces.lock(); +} + +bool Map::set_water_province(const std::string_view identifier) { + if (water_provinces.is_locked()) { + Logger::error("The map's water provinces have already been locked!"); + return false; + } + Province* province = get_province_by_identifier(identifier); + if (province == nullptr) { + Logger::error("Unrecognised water province identifier: ", identifier); + return false; + } + if (province->is_water()) { + Logger::error("Province ", identifier, " is already a water province!"); + return false; + } + if (!water_provinces.add_province(province)) { + Logger::error("Failed to add province ", identifier, " to water province set!"); + return false; + } + province->water = true; + return true; +} + +bool Map::set_water_province_list(std::vector const& list) { + bool ret = true; + water_provinces.reserve(water_provinces.size() + list.size()); + for (std::string_view const& identifier : list) { + ret &= set_water_province(identifier); + } + return ret; +} + +void Map::lock_water_provinces() { + water_provinces.lock(); + Logger::info("Locked water provinces after registering ", water_provinces.size()); +} + +bool Map::add_region(const std::string_view identifier, std::vector const& province_identifiers) { + if (identifier.empty()) { + Logger::error("Invalid region identifier - empty!"); + return false; + } + Region new_region { identifier }; + bool ret = true; + for (const std::string_view province_identifier : province_identifiers) { + Province* province = get_province_by_identifier(province_identifier); + if (province != nullptr) { + if (new_region.contains_province(province)) { + Logger::error("Duplicate province identifier ", province_identifier, " in region ", identifier); + ret = false; + } else { + size_t other_region_index = reinterpret_cast(province->get_region()); + if (other_region_index != 0) { + other_region_index--; + if (other_region_index < regions.size()) + Logger::error("Cannot add province ", province_identifier, " to region ", identifier, " - it is already part of ", regions.get_item_by_index(other_region_index)->get_identifier()); + else + Logger::error("Cannot add province ", province_identifier, " to region ", identifier, " - it is already part of an unknown region with index ", other_region_index); + ret = false; + } else if (!new_region.add_province(province)) { + Logger::error("Failed to add province ", province_identifier, " to region ", identifier); + ret = false; + } + } + } else { + Logger::error("Invalid province identifier ", province_identifier, " for region ", identifier); + ret = false; + } + } + new_region.lock(); + if (new_region.empty()) { + Logger::error("No valid provinces in list for ", identifier); + return false; + } + + // Used to detect provinces listed in multiple regions, will + // be corrected once regions is stable (i.e. lock_regions). + Region* tmp_region_index = reinterpret_cast(regions.size()); + for (Province* province : new_region.get_provinces()) + province->region = tmp_region_index; + ret &= regions.add_item(std::move(new_region)); + return ret; +} + +void Map::lock_regions() { + regions.lock(); + for (Region& region : regions.get_items()) + for (Province* province : region.get_provinces()) + province->region = ®ion; +} + +size_t Map::get_province_count() const { + return provinces.size(); +} + +std::vector const& Map::get_provinces() const { + return provinces.get_items(); +} + +Province* Map::get_province_by_index(Province::index_t index) { + return index != Province::NULL_INDEX ? provinces.get_item_by_index(index - 1) : nullptr; +} + +Province const* Map::get_province_by_index(Province::index_t index) const { + return index != Province::NULL_INDEX ? provinces.get_item_by_index(index - 1) : nullptr; +} + +Province* Map::get_province_by_identifier(const std::string_view identifier) { + return provinces.get_item_by_identifier(identifier); +} + +Province const* Map::get_province_by_identifier(const std::string_view identifier) const { + return provinces.get_item_by_identifier(identifier); +} + +Province::index_t Map::get_index_from_colour(colour_t colour) const { + const colour_index_map_t::const_iterator it = colour_index_map.find(colour); + if (it != colour_index_map.end()) return it->second; + return Province::NULL_INDEX; +} + +Province::index_t Map::get_province_index_at(size_t x, size_t y) const { + if (x < width && y < height) return province_shape_image[x + y * width].index; + return Province::NULL_INDEX; +} + +bool Map::set_max_provinces(Province::index_t new_max_provinces) { + if (new_max_provinces <= Province::NULL_INDEX) { + Logger::error("Trying to set max province count to an invalid value ", new_max_provinces, " (must be greater than ", Province::NULL_INDEX, ")"); + return false; + } + if (!provinces.empty() || provinces.is_locked()) { + Logger::error("Trying to set max province count to ", new_max_provinces, " after provinces have already been added and/or locked"); + return false; + } + max_provinces = new_max_provinces; + return true; +} + +Province::index_t Map::get_max_provinces() const { + return max_provinces; +} + +void Map::set_selected_province(Province::index_t index) { + if (index > get_province_count()) { + Logger::error("Trying to set selected province to an invalid index ", index, " (max index is ", get_province_count(), ")"); + selected_province = Province::NULL_INDEX; + } else { + selected_province = index; + } +} + +Province::index_t Map::get_selected_province_index() const { + return selected_province; +} + +Province const* Map::get_selected_province() const { + return get_province_by_index(get_selected_province_index()); +} + +Region* Map::get_region_by_identifier(const std::string_view identifier) { + return regions.get_item_by_identifier(identifier); +} + +Region const* Map::get_region_by_identifier(const std::string_view identifier) const { + return regions.get_item_by_identifier(identifier); +} + +size_t Map::get_region_count() const { + return regions.size(); +} + +std::vector const& Map::get_regions() const { + return regions.get_items(); +} + +static colour_t colour_at(uint8_t const* colour_data, int32_t idx) { + idx *= 3; + return (colour_data[idx] << 16) | (colour_data[idx + 1] << 8) | colour_data[idx + 2]; +} + +bool Map::generate_province_shape_image(size_t new_width, size_t new_height, uint8_t const* colour_data, + uint8_t const* terrain_data, terrain_variant_map_t const& terrain_variant_map, bool detailed_errors) { + if (!province_shape_image.empty()) { + Logger::error("Province index image has already been generated!"); + return false; + } + if (!provinces.is_locked()) { + Logger::error("Province index image cannot be generated until after provinces are locked!"); + return false; + } + if (new_width < 1 || new_height < 1) { + Logger::error("Invalid province image dimensions: ", new_width, "x", new_height); + return false; + } + if (colour_data == nullptr) { + Logger::error("Province colour data pointer is null!"); + return false; + } + if (terrain_data == nullptr) { + Logger::error("Province terrain data pointer is null!"); + return false; + } + width = new_width; + height = new_height; + province_shape_image.resize(width * height); + + std::vector province_checklist(provinces.size()); + bool ret = true; + std::unordered_set unrecognised_province_colours, unrecognised_terrain_colours; + + for (int32_t y = 0; y < height; ++y) { + for (int32_t x = 0; x < width; ++x) { + const int32_t idx = x + y * width; + + const colour_t terrain_colour = colour_at(terrain_data, idx); + const terrain_variant_map_t::const_iterator it = terrain_variant_map.find(terrain_colour); + if (it != terrain_variant_map.end()) province_shape_image[idx].terrain = it->second; + else { + if (unrecognised_terrain_colours.find(terrain_colour) == unrecognised_terrain_colours.end()) { + unrecognised_terrain_colours.insert(terrain_colour); + if (detailed_errors) { + Logger::error("Unrecognised terrain colour ", colour_to_hex_string(terrain_colour), + " at (", x, ", ", y, ")"); + } + } + province_shape_image[idx].terrain = 0; + } + + const colour_t province_colour = colour_at(colour_data, idx); + if (x > 0) { + const int32_t jdx = idx - 1; + if (colour_at(colour_data, jdx) == province_colour) { + province_shape_image[idx].index = province_shape_image[jdx].index; + continue; + } + } + if (y > 0) { + const int32_t jdx = idx - width; + if (colour_at(colour_data, jdx) == province_colour) { + province_shape_image[idx].index = province_shape_image[jdx].index; + continue; + } + } + const Province::index_t index = get_index_from_colour(province_colour); + if (index != Province::NULL_INDEX) { + province_checklist[index - 1] = true; + province_shape_image[idx].index = index; + continue; + } + if (unrecognised_province_colours.find(province_colour) == unrecognised_province_colours.end()) { + unrecognised_province_colours.insert(province_colour); + if (detailed_errors) { + Logger::error("Unrecognised province colour ", colour_to_hex_string(province_colour), + " at (", x, ", ", y, ")"); + } + } + province_shape_image[idx].index = Province::NULL_INDEX; + } + } + if (!unrecognised_province_colours.empty()) { + Logger::error("Province image contains ", unrecognised_province_colours.size(), " unrecognised province colours"); + ret = false; + } + if (!unrecognised_terrain_colours.empty()) { + Logger::error("Terrain image contains ", unrecognised_terrain_colours.size(), " unrecognised terrain colours"); + ret = false; + } + + size_t missing = 0; + for (size_t idx = 0; idx < province_checklist.size(); ++idx) { + if (!province_checklist[idx]) { + if (detailed_errors) { + Logger::error("Province missing from shape image: ", provinces.get_item_by_index(idx)->to_string()); + } + missing++; + } + } + if (missing > 0) { + Logger::error("Province image is missing ", missing, " province colours"); + ret = false; + } + return ret; +} + +size_t Map::get_width() const { + return width; +} + +size_t Map::get_height() const { + return height; +} + +std::vector const& Map::get_province_shape_image() const { + return province_shape_image; +} + +bool Map::add_mapmode(const std::string_view identifier, Mapmode::colour_func_t colour_func) { + if (identifier.empty()) { + Logger::error("Invalid mapmode identifier - empty!"); + return false; + } + if (colour_func == nullptr) { + Logger::error("Mapmode colour function is null for identifier: ", identifier); + return false; + } + return mapmodes.add_item({ identifier, mapmodes.size(), colour_func }); +} + +bool Map::generate_mapmode_colours(Mapmode::index_t index, uint8_t* target) const { + if (target == nullptr) { + Logger::error("Mapmode colour target pointer is null!"); + return false; + } + bool ret = true; + Mapmode const* mapmode = mapmodes.get_item_by_index(index); + if (mapmode == nullptr) { + // Not an error if mapmodes haven't yet been loaded, + // e.g. if we want to allocate the province colour + // texture before mapmodes are loaded. + if (!(mapmodes.empty() && index == 0)) { + Logger::error("Invalid mapmode index: ", index); + ret = false; + } + mapmode = &Mapmode::ERROR_MAPMODE; + } + // Skip past Province::NULL_INDEX + for (size_t i = 0; i < MAPMODE_COLOUR_SIZE; ++i) + *target++ = 0; + for (Province const& province : provinces.get_items()) { + const colour_t colour = mapmode->get_colour(*this, province); + *target++ = (colour >> 16) & FULL_COLOUR; + *target++ = (colour >> 8) & FULL_COLOUR; + *target++ = colour & FULL_COLOUR; + *target++ = (colour >> 24) & FULL_COLOUR; + } + return ret; +} + +void Map::update_highest_province_population() { + highest_province_population = 0; + for (Province const& province : provinces.get_items()) { + highest_province_population = std::max(highest_province_population, province.get_total_population()); + } +} + +Pop::pop_size_t Map::get_highest_province_population() const { + return highest_province_population; +} + +void Map::update_total_map_population() { + total_map_population = 0; + for (Province const& province : provinces.get_items()) { + total_map_population += province.get_total_population(); + } +} + +Pop::pop_size_t Map::get_total_map_population() const { + return total_map_population; +} + +bool Map::setup(GoodManager const& good_manager, BuildingManager const& building_manager, PopManager const& pop_manager) { + bool ret = true; + for (Province& province : provinces.get_items()) { + province.clear_pops(); + // Set all land provinces to have an RGO based on their index to test them + if (!province.is_water() && good_manager.get_good_count() > 0) + province.rgo = good_manager.get_good_by_index(province.get_index() % good_manager.get_good_count()); + ret &= building_manager.generate_province_buildings(province); + } + return ret; +} + +void Map::update_state(Date const& today) { + for (Province& province : provinces.get_items()) + province.update_state(today); + update_highest_province_population(); + update_total_map_population(); +} + +void Map::tick(Date const& today) { + for (Province& province : provinces.get_items()) + province.tick(today); +} + +using namespace ovdl::csv; + +static bool validate_province_definitions_header(LineObject const& header) { + static const std::vector standard_header { "province", "red", "green", "blue" }; + for (size_t i = 0; i < standard_header.size(); ++i) { + const std::string_view val = header.get_value_for(i); + if (i == 0 && val.empty()) break; + if (val != standard_header[i]) return false; + } + return true; +} + +static bool parse_province_colour(colour_t& colour, std::array components) { + bool ret = true; + colour = NULL_COLOUR; + for (std::string_view& c : components) { + colour <<= 8; + if (c.ends_with('.')) c.remove_suffix(1); + bool successful = false; + uint64_t val = StringUtils::string_to_uint64(c, &successful, 10); + if (successful && val <= 255) { + colour |= val; + } else { + ret = false; + } + } + return ret; +} + +bool Map::load_province_definitions(std::vector const& lines) { + if (lines.empty()) { + Logger::error("No header or entries in province definition file!"); + return false; + } + { + LineObject const& header = lines.front(); + if (!validate_province_definitions_header(header)) { + Logger::error("Non-standard province definition file header - make sure this is not a province definition: ", header); + } + } + if (lines.size() <= 1) { + Logger::error("No entries in province definition file!"); + return false; + } + provinces.reserve(lines.size() - 1); + bool ret = true; + std::for_each(lines.begin() + 1, lines.end(), + [this, &ret](LineObject const& line) -> void { + const std::string_view identifier = line.get_value_for(0); + if (!identifier.empty()) { + colour_t colour; + if (!parse_province_colour(colour, { + line.get_value_for(1), + line.get_value_for(2), + line.get_value_for(3) + })) { + Logger::error("Error reading colour in province definition: ", line); + ret = false; + } + ret &= add_province(identifier, colour); + } + } + ); + lock_provinces(); + return ret; +} diff --git a/src/openvic-simulation/map/Map.hpp b/src/openvic-simulation/map/Map.hpp new file mode 100644 index 0000000..71719e2 --- /dev/null +++ b/src/openvic-simulation/map/Map.hpp @@ -0,0 +1,117 @@ +#pragma once + +#include + +#include + +#include "openvic-simulation/map/Region.hpp" + +namespace OpenVic { + + struct Mapmode : HasIdentifier { + friend struct Map; + + using colour_func_t = std::function; + using index_t = size_t; + + private: + const index_t index; + const colour_func_t colour_func; + + Mapmode(const std::string_view new_identifier, index_t new_index, colour_func_t new_colour_func); + + public: + static const Mapmode ERROR_MAPMODE; + + Mapmode(Mapmode&&) = default; + + index_t get_index() const; + colour_t get_colour(Map const& map, Province const& province) const; + }; + + struct GoodManager; + + /* REQUIREMENTS: + * MAP-4 + */ + struct Map { + using terrain_t = uint8_t; + using terrain_variant_map_t = std::map; + +#pragma pack(push, 1) + struct shape_pixel_t { + Province::index_t index; + terrain_t terrain; + }; +#pragma pack(pop) + private: + using colour_index_map_t = std::map; + + IdentifierRegistry provinces; + IdentifierRegistry regions; + IdentifierRegistry mapmodes; + ProvinceSet water_provinces; + + size_t width = 0, height = 0; + std::vector province_shape_image; + colour_index_map_t colour_index_map; + Province::index_t max_provinces = Province::MAX_INDEX; + Province::index_t selected_province = Province::NULL_INDEX; + + Pop::pop_size_t highest_province_population, total_map_population; + + Province::index_t get_index_from_colour(colour_t colour) const; + + public: + Map(); + + bool add_province(const std::string_view identifier, colour_t colour); + void lock_provinces(); + bool set_water_province(const std::string_view identifier); + bool set_water_province_list(std::vector const& list); + void lock_water_provinces(); + bool add_region(const std::string_view identifier, std::vector const& province_identifiers); + void lock_regions(); + + size_t get_province_count() const; + std::vector const& get_provinces() const; + Province* get_province_by_index(Province::index_t index); + Province const* get_province_by_index(Province::index_t index) const; + Province* get_province_by_identifier(const std::string_view identifier); + Province const* get_province_by_identifier(const std::string_view identifier) const; + Province::index_t get_province_index_at(size_t x, size_t y) const; + bool set_max_provinces(Province::index_t new_max_provinces); + Province::index_t get_max_provinces() const; + void set_selected_province(Province::index_t index); + Province::index_t get_selected_province_index() const; + Province const* get_selected_province() const; + + Region* get_region_by_identifier(const std::string_view identifier); + Region const* get_region_by_identifier(const std::string_view identifier) const; + size_t get_region_count() const; + std::vector const& get_regions() const; + + bool generate_province_shape_image(size_t new_width, size_t new_height, uint8_t const* colour_data, + uint8_t const* terrain_data, terrain_variant_map_t const& terrain_variant_map, bool detailed_errors); + size_t get_width() const; + size_t get_height() const; + std::vector const& get_province_shape_image() const; + + bool add_mapmode(const std::string_view identifier, Mapmode::colour_func_t colour_func); + IDENTIFIER_REGISTRY_ACCESSORS(Mapmode, mapmode) + static constexpr size_t MAPMODE_COLOUR_SIZE = 4; + bool generate_mapmode_colours(Mapmode::index_t index, uint8_t* target) const; + + bool setup(GoodManager const& good_manager, BuildingManager const& building_manager, PopManager const& pop_manager); + + void update_highest_province_population(); + Pop::pop_size_t get_highest_province_population() const; + void update_total_map_population(); + Pop::pop_size_t get_total_map_population() const; + + void update_state(Date const& today); + void tick(Date const& today); + + bool load_province_definitions(std::vector const& lines); + }; +} diff --git a/src/openvic-simulation/map/Province.cpp b/src/openvic-simulation/map/Province.cpp new file mode 100644 index 0000000..43eddd1 --- /dev/null +++ b/src/openvic-simulation/map/Province.cpp @@ -0,0 +1,129 @@ +#include "Province.hpp" + +#include +#include +#include + +using namespace OpenVic; +using namespace OpenVic::NodeTools; + +Province::Province(const std::string_view new_identifier, colour_t new_colour, index_t new_index) + : HasIdentifierAndColour { new_identifier, new_colour, false }, + index { new_index }, + buildings { "buildings", false } { + assert(index != NULL_INDEX); +} + +Province::index_t Province::get_index() const { + return index; +} + +Region* Province::get_region() const { + return region; +} + +bool Province::is_water() const { + return water; +} + +Province::life_rating_t Province::get_life_rating() const { + return life_rating; +} + +bool Province::add_building(Building&& building) { + return buildings.add_item(std::move(building)); +} + +void Province::reset_buildings() { + buildings.reset(); +} + +bool Province::expand_building(const std::string_view building_type_identifier) { + Building* building = buildings.get_item_by_identifier(building_type_identifier); + if (building == nullptr) return false; + return building->expand(); +} + +Good const* Province::get_rgo() const { + return rgo; +} + +std::string Province::to_string() const { + std::stringstream stream; + stream << "(#" << std::to_string(index) << ", " << get_identifier() << ", 0x" << colour_to_hex_string() << ")"; + return stream.str(); +} + +bool Province::load_pop_list(PopManager const& pop_manager, ast::NodeCPtr root) { + return expect_list_reserve_length( + pops, + [this, &pop_manager](ast::NodeCPtr pop_node) -> bool { + return pop_manager.load_pop_into_province(*this, pop_node); + } + )(root); +} + +bool Province::add_pop(Pop&& pop) { + if (!is_water()) { + pops.push_back(std::move(pop)); + return true; + } else { + Logger::error("Trying to add pop to water province ", get_identifier()); + return false; + } +} + +void Province::clear_pops() { + pops.clear(); +} + +size_t Province::get_pop_count() const { + return pops.size(); +} + +std::vector const& Province::get_pops() const { + return pops; +} + +Pop::pop_size_t Province::get_total_population() const { + return total_population; +} + +distribution_t const& Province::get_pop_type_distribution() const { + return pop_types; +} + +distribution_t const& Province::get_culture_distribution() const { + return cultures; +} + +distribution_t const& Province::get_religion_distribution() const { + return religions; +} + +/* REQUIREMENTS: + * MAP-65 + */ +void Province::update_pops() { + total_population = 0; + pop_types.clear(); + cultures.clear(); + religions.clear(); + for (Pop const& pop : pops) { + total_population += pop.get_size(); + pop_types[&pop.get_type()] += pop.get_size(); + cultures[&pop.get_culture()] += pop.get_size(); + religions[&pop.get_religion()] += pop.get_size(); + } +} + +void Province::update_state(Date const& today) { + for (Building& building : buildings.get_items()) + building.update_state(today); + update_pops(); +} + +void Province::tick(Date const& today) { + for (Building& building : buildings.get_items()) + building.tick(today); +} diff --git a/src/openvic-simulation/map/Province.hpp b/src/openvic-simulation/map/Province.hpp new file mode 100644 index 0000000..682fc16 --- /dev/null +++ b/src/openvic-simulation/map/Province.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include "openvic-simulation/map/Building.hpp" +#include "openvic-simulation/pop/Pop.hpp" + +namespace OpenVic { + struct Map; + struct Region; + struct Good; + + /* REQUIREMENTS: + * MAP-5, MAP-7, MAP-8, MAP-43, MAP-47 + */ + struct Province : HasIdentifierAndColour { + friend struct Map; + + using index_t = uint16_t; + using life_rating_t = int8_t; + + static constexpr index_t NULL_INDEX = 0, MAX_INDEX = (1 << (8 * sizeof(index_t))) - 1; + + private: + const index_t index; + Region* region = nullptr; + bool water = false; + life_rating_t life_rating = 0; + IdentifierRegistry buildings; + // TODO - change this into a factory-like structure + Good const* rgo = nullptr; + + std::vector pops; + Pop::pop_size_t total_population; + distribution_t pop_types, cultures, religions; + + Province(const std::string_view new_identifier, colour_t new_colour, index_t new_index); + + public: + Province(Province&&) = default; + + index_t get_index() const; + Region* get_region() const; + bool is_water() const; + life_rating_t get_life_rating() const; + bool add_building(Building&& building); + IDENTIFIER_REGISTRY_ACCESSORS(Building, building) + void reset_buildings(); + bool expand_building(const std::string_view building_type_identifier); + Good const* get_rgo() const; + std::string to_string() const; + + bool load_pop_list(PopManager const& pop_manager, ast::NodeCPtr root); + bool add_pop(Pop&& pop); + void clear_pops(); + size_t get_pop_count() const; + std::vector const& get_pops() const; + Pop::pop_size_t get_total_population() const; + distribution_t const& get_pop_type_distribution() const; + distribution_t const& get_culture_distribution() const; + distribution_t const& get_religion_distribution() const; + void update_pops(); + + void update_state(Date const& today); + void tick(Date const& today); + }; +} diff --git a/src/openvic-simulation/map/Region.cpp b/src/openvic-simulation/map/Region.cpp new file mode 100644 index 0000000..33092c5 --- /dev/null +++ b/src/openvic-simulation/map/Region.cpp @@ -0,0 +1,69 @@ +#include "Region.hpp" + +using namespace OpenVic; + +bool ProvinceSet::add_province(Province* province) { + if (locked) { + Logger::error("Cannot add province to province set - locked!"); + return false; + } + if (province == nullptr) { + Logger::error("Cannot add province to province set - null province!"); + return false; + } + if (contains_province(province)) { + Logger::error("Cannot add province ", province->get_identifier(), " to province set - already in the set!"); + return false; + } + provinces.push_back(province); + return true; +} + +void ProvinceSet::lock(bool log) { + if (locked) { + Logger::error("Failed to lock province set - already locked!"); + } else { + locked = true; + if (log) Logger::info("Locked province set with ", size(), " provinces"); + } +} + +bool ProvinceSet::is_locked() const { + return locked; +} + +void ProvinceSet::reset() { + provinces.clear(); + locked = false; +} + +bool ProvinceSet::empty() const { + return provinces.empty(); +} + +size_t ProvinceSet::size() const { + return provinces.size(); +} + +void ProvinceSet::reserve(size_t size) { + if (locked) { + Logger::error("Failed to reserve space for ", size, " items in province set - already locked!"); + } else { + provinces.reserve(size); + } +} + +bool ProvinceSet::contains_province(Province const* province) const { + return province && std::find(provinces.begin(), provinces.end(), province) != provinces.end(); +} + +std::vector const& ProvinceSet::get_provinces() const { + return provinces; +} + +Region::Region(const std::string_view new_identifier) : HasIdentifier { new_identifier } {} + +colour_t Region::get_colour() const { + if (provinces.empty()) return FULL_COLOUR << 16; + return provinces.front()->get_colour(); +} diff --git a/src/openvic-simulation/map/Region.hpp b/src/openvic-simulation/map/Region.hpp new file mode 100644 index 0000000..2fccf06 --- /dev/null +++ b/src/openvic-simulation/map/Region.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include "openvic-simulation/map/Province.hpp" + +namespace OpenVic { + + struct ProvinceSet { + protected: + std::vector provinces; + bool locked = false; + + public: + bool add_province(Province* province); + void lock(bool log = false); + bool is_locked() const; + void reset(); + bool empty() const; + size_t size() const; + void reserve(size_t size); + bool contains_province(Province const* province) const; + std::vector const& get_provinces() const; + }; + + /* REQUIREMENTS: + * MAP-6, MAP-44, MAP-48 + */ + struct Region : HasIdentifier, ProvinceSet { + friend struct Map; + + private: + Region(const std::string_view new_identifier); + + public: + Region(Region&&) = default; + + colour_t get_colour() const; + }; +} diff --git a/src/openvic-simulation/pop/Culture.cpp b/src/openvic-simulation/pop/Culture.cpp new file mode 100644 index 0000000..7b595fb --- /dev/null +++ b/src/openvic-simulation/pop/Culture.cpp @@ -0,0 +1,199 @@ +#include "Culture.hpp" + +#include "openvic-simulation/dataloader/NodeTools.hpp" + +using namespace OpenVic; +using namespace OpenVic::NodeTools; + +GraphicalCultureType::GraphicalCultureType(const std::string_view new_identifier) : HasIdentifier { new_identifier } {} + +CultureGroup::CultureGroup(const std::string_view new_identifier, const std::string_view new_leader, + GraphicalCultureType const& new_unit_graphical_culture_type, bool new_is_overseas) + : HasIdentifier { new_identifier }, leader { new_leader }, + unit_graphical_culture_type { new_unit_graphical_culture_type }, + is_overseas { new_is_overseas } {} + + +std::string const& CultureGroup::get_leader() const { + return leader; +} + +GraphicalCultureType const& CultureGroup::get_unit_graphical_culture_type() const { + return unit_graphical_culture_type; +} + +bool CultureGroup::get_is_overseas() const { + return is_overseas; +} + +Culture::Culture(const std::string_view new_identifier, colour_t new_colour, CultureGroup const& new_group, + std::vector const& new_first_names, std::vector const& new_last_names) + : HasIdentifierAndColour { new_identifier, new_colour, true }, + group { new_group }, + first_names { new_first_names }, + last_names { new_last_names } {} + +CultureGroup const& Culture::get_group() const { + return group; +} + +std::vector const& Culture::get_first_names() const { + return first_names; +} + +std::vector const& Culture::get_last_names() const { + return last_names; +} + +CultureManager::CultureManager() + : graphical_culture_types { "graphical culture types" }, + culture_groups { "culture groups" }, + cultures { "cultures" } {} + +bool CultureManager::add_graphical_culture_type(const std::string_view identifier) { + if (identifier.empty()) { + Logger::error("Invalid culture group identifier - empty!"); + return false; + } + return graphical_culture_types.add_item({ identifier }); +} + +bool CultureManager::add_culture_group(const std::string_view identifier, const std::string_view leader, GraphicalCultureType const* graphical_culture_type, bool is_overseas) { + if (!graphical_culture_types.is_locked()) { + Logger::error("Cannot register culture groups until graphical culture types are locked!"); + return false; + } + if (identifier.empty()) { + Logger::error("Invalid culture group identifier - empty!"); + return false; + } + if (leader.empty()) { + Logger::error("Invalid culture group leader - empty!"); + return false; + } + if (graphical_culture_type == nullptr) { + Logger::error("Null graphical culture type for ", identifier); + return false; + } + return culture_groups.add_item({ identifier, leader, *graphical_culture_type, is_overseas }); +} + +bool CultureManager::add_culture(const std::string_view identifier, colour_t colour, CultureGroup const* group, std::vector const& first_names, std::vector const& last_names) { + if (!culture_groups.is_locked()) { + Logger::error("Cannot register cultures until culture groups are locked!"); + return false; + } + if (identifier.empty()) { + Logger::error("Invalid culture identifier - empty!"); + return false; + } + if (group == nullptr) { + Logger::error("Null culture group for ", identifier); + return false; + } + if (colour > MAX_COLOUR_RGB) { + Logger::error("Invalid culture colour for ", identifier, ": ", colour_to_hex_string(colour)); + return false; + } + return cultures.add_item({ identifier, colour, *group, first_names, last_names }); +} + +bool CultureManager::load_graphical_culture_type_file(ast::NodeCPtr root) { + const bool ret = expect_list_reserve_length( + graphical_culture_types, + expect_identifier( + std::bind(&CultureManager::add_graphical_culture_type, this, std::placeholders::_1) + ) + )(root); + lock_graphical_culture_types(); + return ret; +} + +bool CultureManager::_load_culture_group(size_t& total_expected_cultures, + GraphicalCultureType const* default_unit_graphical_culture_type, + const std::string_view culture_group_key, ast::NodeCPtr culture_group_node) { + + std::string_view leader; + GraphicalCultureType const* unit_graphical_culture_type = default_unit_graphical_culture_type; + bool is_overseas = true; + + bool ret = expect_dictionary_keys_and_length( + [&total_expected_cultures](size_t size) -> size_t { + total_expected_cultures += size; + return size; + }, + ALLOW_OTHER_KEYS, + "leader", ONE_EXACTLY, [&total_expected_cultures, &leader](ast::NodeCPtr node) -> bool { + total_expected_cultures--; + return expect_identifier(assign_variable_callback(leader))(node); + }, + "unit", ZERO_OR_ONE, [this, &total_expected_cultures, &unit_graphical_culture_type](ast::NodeCPtr node) -> bool { + total_expected_cultures--; + return expect_graphical_culture_type(unit_graphical_culture_type)(node); + }, + "union", ZERO_OR_ONE, [&total_expected_cultures](ast::NodeCPtr) -> bool { + total_expected_cultures--; + return true; + }, + "is_overseas", ZERO_OR_ONE, [&total_expected_cultures, &is_overseas](ast::NodeCPtr node) -> bool { + total_expected_cultures--; + return expect_bool(assign_variable_callback(is_overseas))(node); + } + )(culture_group_node); + ret &= add_culture_group(culture_group_key, leader, unit_graphical_culture_type, is_overseas); + return ret; +} + +bool CultureManager::_load_culture(CultureGroup const* culture_group, + const std::string_view culture_key, ast::NodeCPtr culture_node) { + + colour_t colour = NULL_COLOUR; + std::vector first_names, last_names; + + bool ret = expect_dictionary_keys( + "color", ONE_EXACTLY, expect_colour(assign_variable_callback(colour)), + "first_names", ONE_EXACTLY, name_list_callback(first_names), + "last_names", ONE_EXACTLY, name_list_callback(last_names), + "radicalism", ZERO_OR_ONE, success_callback, + "primary", ZERO_OR_ONE, success_callback + )(culture_node); + ret &= add_culture(culture_key, colour, culture_group, first_names, last_names); + return ret; +} + +bool CultureManager::load_culture_file(ast::NodeCPtr root) { + if (!graphical_culture_types.is_locked()) { + Logger::error("Cannot load culture groups until graphical culture types are locked!"); + return false; + } + + static const std::string default_unit_graphical_culture_type_identifier = "Generic"; + GraphicalCultureType const* const default_unit_graphical_culture_type = get_graphical_culture_type_by_identifier(default_unit_graphical_culture_type_identifier); + if (default_unit_graphical_culture_type == nullptr) { + Logger::error("Failed to find default unit graphical culture type: ", default_unit_graphical_culture_type_identifier); + } + + size_t total_expected_cultures = 0; + bool ret = expect_dictionary_reserve_length( + culture_groups, + [this, default_unit_graphical_culture_type, &total_expected_cultures](std::string_view key, ast::NodeCPtr value) -> bool { + return _load_culture_group(total_expected_cultures, default_unit_graphical_culture_type, key, value); + } + )(root); + lock_culture_groups(); + cultures.reserve(cultures.size() + total_expected_cultures); + + ret &= expect_dictionary( + [this](std::string_view culture_group_key, ast::NodeCPtr culture_group_value) -> bool { + CultureGroup const* culture_group = get_culture_group_by_identifier(culture_group_key); + return expect_dictionary( + [this, culture_group](std::string_view key, ast::NodeCPtr value) -> bool { + if (key == "leader" || key == "unit" || key == "union" || key == "is_overseas") return true; + return _load_culture(culture_group, key, value); + } + )(culture_group_value); + } + )(root); + lock_cultures(); + return ret; +} diff --git a/src/openvic-simulation/pop/Culture.hpp b/src/openvic-simulation/pop/Culture.hpp new file mode 100644 index 0000000..d27c7c9 --- /dev/null +++ b/src/openvic-simulation/pop/Culture.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include "openvic-simulation/types/IdentifierRegistry.hpp" + +namespace OpenVic { + + struct CultureManager; + + struct GraphicalCultureType : HasIdentifier { + friend struct CultureManager; + + private: + GraphicalCultureType(const std::string_view new_identifier); + + public: + GraphicalCultureType(GraphicalCultureType&&) = default; + }; + + struct CultureGroup : HasIdentifier { + friend struct CultureManager; + + private: + const std::string leader; + GraphicalCultureType const& unit_graphical_culture_type; + const bool is_overseas; + + // TODO - union tag + + CultureGroup(const std::string_view new_identifier, const std::string_view new_leader, GraphicalCultureType const& new_unit_graphical_culture_type, bool new_is_overseas); + + public: + CultureGroup(CultureGroup&&) = default; + + std::string const& get_leader() const; + GraphicalCultureType const& get_unit_graphical_culture_type() const; + bool get_is_overseas() const; + }; + + struct Culture : HasIdentifierAndColour { + friend struct CultureManager; + + private: + CultureGroup const& group; + const std::vector first_names, last_names; + + // TODO - radicalism, primary tag + + Culture(const std::string_view new_identifier, colour_t new_colour, CultureGroup const& new_group, std::vector const& new_first_names, std::vector const& new_last_names); + + public: + Culture(Culture&&) = default; + + CultureGroup const& get_group() const; + std::vector const& get_first_names() const; + std::vector const& get_last_names() const; + }; + + struct CultureManager { + private: + IdentifierRegistry graphical_culture_types; + IdentifierRegistry culture_groups; + IdentifierRegistry cultures; + + bool _load_culture_group(size_t& total_expected_cultures, GraphicalCultureType const* default_unit_graphical_culture_type, + const std::string_view culture_group_key, ast::NodeCPtr culture_group_node); + bool _load_culture(CultureGroup const* culture_group, const std::string_view culture_key, ast::NodeCPtr node); + + public: + CultureManager(); + + bool add_graphical_culture_type(const std::string_view identifier); + IDENTIFIER_REGISTRY_ACCESSORS(GraphicalCultureType, graphical_culture_type) + + bool add_culture_group(const std::string_view identifier, const std::string_view leader, GraphicalCultureType const* new_graphical_culture_type, bool is_overseas); + IDENTIFIER_REGISTRY_ACCESSORS(CultureGroup, culture_group) + + bool add_culture(const std::string_view identifier, colour_t colour, CultureGroup const* group, std::vector const& first_names, std::vector const& last_names); + IDENTIFIER_REGISTRY_ACCESSORS(Culture, culture) + + bool load_graphical_culture_type_file(ast::NodeCPtr root); + bool load_culture_file(ast::NodeCPtr root); + }; +} diff --git a/src/openvic-simulation/pop/Pop.cpp b/src/openvic-simulation/pop/Pop.cpp new file mode 100644 index 0000000..7b4dd60 --- /dev/null +++ b/src/openvic-simulation/pop/Pop.cpp @@ -0,0 +1,163 @@ +#include "Pop.hpp" + +#include + +#include "openvic-simulation/dataloader/NodeTools.hpp" +#include "openvic-simulation/map/Province.hpp" +#include "openvic-simulation/utility/Logger.hpp" + +using namespace OpenVic; +using namespace OpenVic::NodeTools; + +Pop::Pop(PopType const& new_type, Culture const& new_culture, Religion const& new_religion, pop_size_t new_size) + : type { new_type }, + culture { new_culture }, + religion { new_religion }, + size { new_size } { + assert(size > 0); +} + +PopType const& Pop::get_type() const { + return type; +} + +Culture const& Pop::get_culture() const { + return culture; +} + +Religion const& Pop::get_religion() const { + return religion; +} + +Pop::pop_size_t Pop::get_size() const { + return size; +} + +Pop::pop_size_t Pop::get_num_promoted() const { + return num_promoted; +} + +Pop::pop_size_t Pop::get_num_demoted() const { + return num_demoted; +} + +Pop::pop_size_t Pop::get_num_migrated() const { + return num_migrated; +} + +Pop::pop_size_t Pop::get_pop_daily_change() const { + return Pop::get_num_promoted() - (Pop::get_num_demoted() + Pop::get_num_migrated()); +} + +PopType::PopType(const std::string_view new_identifier, colour_t new_colour, + strata_t new_strata, sprite_t new_sprite, + Pop::pop_size_t new_max_size, Pop::pop_size_t new_merge_max_size, + bool new_state_capital_only, bool new_demote_migrant, bool new_is_artisan, bool new_is_slave) + : HasIdentifierAndColour { new_identifier, new_colour, true }, + strata { new_strata }, + sprite { new_sprite }, + max_size { new_max_size }, + merge_max_size { new_merge_max_size }, + state_capital_only { new_state_capital_only }, + demote_migrant { new_demote_migrant }, + is_artisan { new_is_artisan }, + is_slave { new_is_slave } { + assert(sprite > 0); + assert(max_size > 0); + assert(merge_max_size > 0); +} + +PopType::sprite_t PopType::get_sprite() const { + return sprite; +} + +PopType::strata_t PopType::get_strata() const { + return strata; +} + +Pop::pop_size_t PopType::get_max_size() const { + return max_size; +} + +Pop::pop_size_t PopType::get_merge_max_size() const { + return merge_max_size; +} + +bool PopType::get_state_capital_only() const { + return state_capital_only; +} + +bool PopType::get_demote_migrant() const { + return demote_migrant; +} + +bool PopType::get_is_artisan() const { + return is_artisan; +} + +bool PopType::get_is_slave() const { + return is_slave; +} + +PopManager::PopManager() : pop_types { "pop types" } {} + +bool PopManager::add_pop_type(const std::string_view identifier, colour_t colour, PopType::strata_t strata, PopType::sprite_t sprite, + Pop::pop_size_t max_size, Pop::pop_size_t merge_max_size, bool state_capital_only, bool demote_migrant, bool is_artisan, bool is_slave) { + if (identifier.empty()) { + Logger::error("Invalid pop type identifier - empty!"); + return false; + } + if (colour > MAX_COLOUR_RGB) { + Logger::error("Invalid pop type colour for ", identifier, ": ", colour_to_hex_string(colour)); + return false; + } + if (sprite <= 0) { + Logger::error("Invalid pop type sprite index for ", identifier, ": ", sprite); + return false; + } + if (max_size <= 0) { + Logger::error("Invalid pop type max size for ", identifier, ": ", max_size); + return false; + } + if (merge_max_size <= 0) { + Logger::error("Invalid pop type merge max size for ", identifier, ": ", merge_max_size); + return false; + } + return pop_types.add_item({ identifier, colour, strata, sprite, max_size, merge_max_size, state_capital_only, demote_migrant, is_artisan, is_slave }); +} + +bool PopManager::load_pop_type_file(std::filesystem::path const& path, ast::NodeCPtr root) { + + // TODO - pop type loading + + if (pop_types.empty()) + return add_pop_type("test_pop_type", 0xFF0000, PopType::strata_t::POOR, 1, 1, 1, false, false, false, false); + return true; +} + +bool PopManager::load_pop_into_province(Province& province, ast::NodeCPtr root) const { + static PopType const* type = get_pop_type_by_identifier("test_pop_type"); + Culture const* culture = nullptr; + Religion const* religion = nullptr; + Pop::pop_size_t size = 0; + + bool ret = expect_assign( + [this, &culture, &religion, &size](std::string_view, ast::NodeCPtr pop_node) -> bool { + return expect_dictionary_keys( + "culture", ONE_EXACTLY, culture_manager.expect_culture(culture), + "religion", ONE_EXACTLY, religion_manager.expect_religion(religion), + "size", ONE_EXACTLY, expect_uint(assign_variable_callback_uint("pop size", size)), + "militancy", ZERO_OR_ONE, success_callback, + "rebel_type", ZERO_OR_ONE, success_callback + )(pop_node); + } + )(root); + + if (type != nullptr && culture != nullptr && religion != nullptr && size > 0) { + ret &= province.add_pop({ *type, *culture, *religion, size }); + } else { + Logger::error("Some pop arguments are invalid: type = ", type, ", culture = ", culture, ", religion = ", religion, ", size = ", size); + ret = false; + } + return ret; +} diff --git a/src/openvic-simulation/pop/Pop.hpp b/src/openvic-simulation/pop/Pop.hpp new file mode 100644 index 0000000..3c635a4 --- /dev/null +++ b/src/openvic-simulation/pop/Pop.hpp @@ -0,0 +1,101 @@ +#pragma once + +#include + +#include "openvic-simulation/pop/Culture.hpp" +#include "openvic-simulation/pop/Religion.hpp" + +namespace OpenVic { + + struct PopManager; + struct PopType; + + /* REQUIREMENTS: + * POP-18, POP-19, POP-20, POP-21 + */ + struct Pop { + friend struct PopManager; + + using pop_size_t = int64_t; + + private: + PopType const& type; + Culture const& culture; + Religion const& religion; + pop_size_t size, num_promoted, num_demoted, num_migrated; + + Pop(PopType const& new_type, Culture const& new_culture, Religion const& new_religion, pop_size_t new_size); + + public: + Pop(Pop const&) = delete; + Pop(Pop&&) = default; + Pop& operator=(Pop const&) = delete; + Pop& operator=(Pop&&) = delete; + + PopType const& get_type() const; + Culture const& get_culture() const; + Religion const& get_religion() const; + pop_size_t get_size() const; + pop_size_t get_num_promoted() const; + pop_size_t get_num_demoted() const; + pop_size_t get_num_migrated() const; + pop_size_t get_pop_daily_change() const; + }; + + /* REQUIREMENTS: + * POP-26 + */ + struct PopType : HasIdentifierAndColour { + friend struct PopManager; + + using sprite_t = uint8_t; + + private: + const enum class strata_t { + POOR, + MIDDLE, + RICH + } strata; + const sprite_t sprite; + const Pop::pop_size_t max_size, merge_max_size; + const bool state_capital_only, demote_migrant, is_artisan, is_slave; + + // TODO - rebel composition, life/everyday/luxury needs, country and province migration targets, promote_to targets, ideologies and issues + + PopType(const std::string_view new_identifier, colour_t new_colour, strata_t new_strata, sprite_t new_sprite, Pop::pop_size_t new_max_size, Pop::pop_size_t new_merge_max_size, + bool new_state_capital_only, bool new_demote_migrant, bool new_is_artisan, bool new_is_slave); + + public: + PopType(PopType&&) = default; + + strata_t get_strata() const; + sprite_t get_sprite() const; + Pop::pop_size_t get_max_size() const; + Pop::pop_size_t get_merge_max_size() const; + bool get_state_capital_only() const; + bool get_demote_migrant() const; + bool get_is_artisan() const; + bool get_is_slave() const; + }; + + struct Province; + + struct PopManager { + private: + IdentifierRegistry pop_types; + + public: + CultureManager culture_manager; + ReligionManager religion_manager; + + PopManager(); + + bool add_pop_type(const std::string_view identifier, colour_t new_colour, PopType::strata_t strata, PopType::sprite_t sprite, + Pop::pop_size_t max_size, Pop::pop_size_t merge_max_size, bool state_capital_only, bool demote_migrant, + bool is_artisan, bool is_slave); + IDENTIFIER_REGISTRY_ACCESSORS(PopType, pop_type) + + bool load_pop_type_file(std::filesystem::path const& path, ast::NodeCPtr root); + bool load_pop_into_province(Province& province, ast::NodeCPtr root) const; + }; +} diff --git a/src/openvic-simulation/pop/Religion.cpp b/src/openvic-simulation/pop/Religion.cpp new file mode 100644 index 0000000..b336ae1 --- /dev/null +++ b/src/openvic-simulation/pop/Religion.cpp @@ -0,0 +1,110 @@ +#include "Religion.hpp" + +#include + +using namespace OpenVic; +using namespace OpenVic::NodeTools; + +ReligionGroup::ReligionGroup(const std::string_view new_identifier) : HasIdentifier { new_identifier } {} + +Religion::Religion(const std::string_view new_identifier, colour_t new_colour, + ReligionGroup const& new_group, icon_t new_icon, bool new_pagan) + : HasIdentifierAndColour { new_identifier, new_colour, true }, + group { new_group }, + icon { new_icon }, + pagan { new_pagan } { + assert(icon > 0); +} + +ReligionGroup const& Religion::get_group() const { + return group; +} + +Religion::icon_t Religion::get_icon() const { + return icon; +} + +bool Religion::get_pagan() const { + return pagan; +} + +ReligionManager::ReligionManager() + : religion_groups { "religion groups" }, + religions { "religions" } {} + +bool ReligionManager::add_religion_group(const std::string_view identifier) { + if (identifier.empty()) { + Logger::error("Invalid religion group identifier - empty!"); + return false; + } + return religion_groups.add_item({ identifier }); +} + +bool ReligionManager::add_religion(const std::string_view identifier, colour_t colour, ReligionGroup const* group, Religion::icon_t icon, bool pagan) { + if (!religion_groups.is_locked()) { + Logger::error("Cannot register religions until religion groups are locked!"); + return false; + } + if (identifier.empty()) { + Logger::error("Invalid religion identifier - empty!"); + return false; + } + if (group == nullptr) { + Logger::error("Null religion group for ", identifier); + return false; + } + if (colour > MAX_COLOUR_RGB) { + Logger::error("Invalid religion colour for ", identifier, ": ", colour_to_hex_string(colour)); + return false; + } + if (icon <= 0) { + Logger::error("Invalid religion icon for ", identifier, ": ", icon); + return false; + } + return religions.add_item({ identifier, colour, *group, icon, pagan }); +} + +bool ReligionManager::load_religion_file(ast::NodeCPtr root) { + + size_t total_expected_religions = 0; + bool ret = expect_dictionary_reserve_length( + religion_groups, + [this, &total_expected_religions](std::string_view key, ast::NodeCPtr value) -> bool { + bool ret = expect_list_and_length( + [&total_expected_religions](size_t size) -> size_t { + total_expected_religions += size; + return 0; + }, + success_callback + )(value); + ret &= add_religion_group(key); + return ret; + } + )(root); + lock_religion_groups(); + religions.reserve(religions.size() + total_expected_religions); + ret &= expect_dictionary( + [this](std::string_view religion_group_key, ast::NodeCPtr religion_group_value) -> bool { + + ReligionGroup const* religion_group = get_religion_group_by_identifier(religion_group_key); + + return expect_dictionary( + [this, religion_group](std::string_view key, ast::NodeCPtr value) -> bool { + colour_t colour = NULL_COLOUR; + Religion::icon_t icon = 0; + bool pagan = false; + + bool ret = expect_dictionary_keys( + "icon", ONE_EXACTLY, expect_uint(assign_variable_callback_uint("religion icon", icon)), + "color", ONE_EXACTLY, expect_colour(assign_variable_callback(colour)), + "pagan", ZERO_OR_ONE, expect_bool(assign_variable_callback(pagan)) + )(value); + ret &= add_religion(key, colour, religion_group, icon, pagan); + return ret; + } + )(religion_group_value); + } + )(root); + lock_religions(); + return ret; +} diff --git a/src/openvic-simulation/pop/Religion.hpp b/src/openvic-simulation/pop/Religion.hpp new file mode 100644 index 0000000..bd65321 --- /dev/null +++ b/src/openvic-simulation/pop/Religion.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include "openvic-simulation/types/IdentifierRegistry.hpp" +#include "openvic-simulation/dataloader/NodeTools.hpp" + +namespace OpenVic { + + struct ReligionManager; + + struct ReligionGroup : HasIdentifier { + friend struct ReligionManager; + + private: + ReligionGroup(const std::string_view new_identifier); + + public: + ReligionGroup(ReligionGroup&&) = default; + }; + + struct Religion : HasIdentifierAndColour { + friend struct ReligionManager; + + using icon_t = uint8_t; + + private: + ReligionGroup const& group; + const icon_t icon; + const bool pagan; + + Religion(const std::string_view new_identifier, colour_t new_colour, ReligionGroup const& new_group, icon_t new_icon, bool new_pagan); + + public: + Religion(Religion&&) = default; + + ReligionGroup const& get_group() const; + icon_t get_icon() const; + bool get_pagan() const; + }; + + struct ReligionManager { + private: + IdentifierRegistry religion_groups; + IdentifierRegistry religions; + + public: + ReligionManager(); + + bool add_religion_group(const std::string_view identifier); + IDENTIFIER_REGISTRY_ACCESSORS(ReligionGroup, religion_group) + + bool add_religion(const std::string_view identifier, colour_t colour, ReligionGroup const* group, Religion::icon_t icon, bool pagan); + IDENTIFIER_REGISTRY_ACCESSORS(Religion, religion) + + bool load_religion_file(ast::NodeCPtr root); + }; +} diff --git a/src/openvic-simulation/types/Colour.hpp b/src/openvic-simulation/types/Colour.hpp new file mode 100644 index 0000000..01f3852 --- /dev/null +++ b/src/openvic-simulation/types/Colour.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace OpenVic { + // Represents a 24-bit RGB integer OR a 32-bit ARGB integer + using colour_t = uint32_t; + /* When colour_t is used as an identifier, NULL_COLOUR is disallowed + * and should be reserved as an error value. + * When colour_t is used in a purely graphical context, NULL_COLOUR + * should be allowed. + */ + static constexpr colour_t NULL_COLOUR = 0, FULL_COLOUR = 0xFF, MAX_COLOUR_RGB = 0xFFFFFF; + constexpr colour_t float_to_colour_byte(float f, float min = 0.0f, float max = 1.0f) { + return static_cast(std::clamp(min + f * (max - min), min, max) * 255.0f); + } + constexpr colour_t fraction_to_colour_byte(int n, int d, float min = 0.0f, float max = 1.0f) { + return float_to_colour_byte(static_cast(n) / static_cast(d), min, max); + } + constexpr colour_t float_to_alpha_value(float a) { + return float_to_colour_byte(a) << 24; + } + constexpr float colour_byte_to_float(colour_t colour) { + return std::clamp(static_cast(colour) / 255.0f, 0.0f, 1.0f); + } + + inline std::string colour_to_hex_string(colour_t colour) { + std::ostringstream stream; + stream << std::hex << std::setfill('0') << std::setw(6) << colour; + return stream.str(); + } +} diff --git a/src/openvic-simulation/types/Date.cpp b/src/openvic-simulation/types/Date.cpp new file mode 100644 index 0000000..3449591 --- /dev/null +++ b/src/openvic-simulation/types/Date.cpp @@ -0,0 +1,262 @@ +#include "Date.hpp" + +#include +#include +#include +#include + +#include "openvic-simulation/utility/Logger.hpp" +#include "openvic-simulation/utility/StringUtils.hpp" + +using namespace OpenVic; + +Timespan::Timespan(day_t value) : days { value } {} + +bool Timespan::operator<(Timespan other) const { return days < other.days; }; +bool Timespan::operator>(Timespan other) const { return days > other.days; }; +bool Timespan::operator<=(Timespan other) const { return days <= other.days; }; +bool Timespan::operator>=(Timespan other) const { return days >= other.days; }; +bool Timespan::operator==(Timespan other) const { return days == other.days; }; +bool Timespan::operator!=(Timespan other) const { return days != other.days; }; + +Timespan Timespan::operator+(Timespan other) const { return days + other.days; } + +Timespan Timespan::operator-(Timespan other) const { return days - other.days; } + +Timespan Timespan::operator*(day_t factor) const { return days * factor; } + +Timespan Timespan::operator/(day_t factor) const { return days / factor; } + +Timespan& Timespan::operator+=(Timespan other) { + days += other.days; + return *this; +} + +Timespan& Timespan::operator-=(Timespan other) { + days -= other.days; + return *this; +} + +Timespan& Timespan::operator++() { + days++; + return *this; +} + +Timespan Timespan::operator++(int) { + Timespan old = *this; + ++(*this); + return old; +} + +Timespan::operator day_t() const { + return days; +} + +Timespan::operator double() const { + return days; +} + +std::string Timespan::to_string() const { + return std::to_string(days); +} + +Timespan::operator std::string() const { + return to_string(); +} + +std::ostream& OpenVic::operator<<(std::ostream& out, Timespan const& timespan) { + return out << timespan.to_string(); +} + +Timespan Date::_dateToTimespan(year_t year, month_t month, day_t day) { + month = std::clamp(month, 1, MONTHS_IN_YEAR); + day = std::clamp(day, 1, DAYS_IN_MONTH[month - 1]); + return year * DAYS_IN_YEAR + DAYS_UP_TO_MONTH[month - 1] + day - 1; +} + +Timespan::day_t const* Date::DAYS_UP_TO_MONTH = generate_days_up_to_month(); + +Timespan::day_t const* Date::generate_days_up_to_month() { + static Timespan::day_t days_up_to_month[MONTHS_IN_YEAR]; + Timespan::day_t days = 0; + for (int month = 0; month < MONTHS_IN_YEAR; + days_up_to_month[month] = days, days += DAYS_IN_MONTH[month++]); + assert(days == DAYS_IN_YEAR); + return days_up_to_month; +} + +Date::month_t const* Date::MONTH_FROM_DAY_IN_YEAR = generate_month_from_day_in_year(); + +Date::month_t const* Date::generate_month_from_day_in_year() { + static month_t month_from_day_in_year[DAYS_IN_YEAR]; + Timespan::day_t days_left = 0; + for (int day = 0, month = 0; day < DAYS_IN_YEAR; + days_left = (days_left > 0 ? days_left : DAYS_IN_MONTH[month++]) - 1, + month_from_day_in_year[day++] = month); + assert(days_left == 0); + assert(month_from_day_in_year[DAYS_IN_YEAR - 1] == MONTHS_IN_YEAR); + return month_from_day_in_year; +} + +Date::Date(Timespan total_days) : timespan { total_days } { + if (timespan < 0) { + Logger::error("Invalid timespan for date: ", timespan, " (cannot be negative)"); + timespan = 0; + } +} + +Date::Date(year_t year, month_t month, day_t day) : timespan { _dateToTimespan(year, month, day) } {} + +Date::year_t Date::getYear() const { + return static_cast(timespan) / DAYS_IN_YEAR; +} + +Date::month_t Date::getMonth() const { + return MONTH_FROM_DAY_IN_YEAR[static_cast(timespan) % DAYS_IN_YEAR]; +} + +Date::day_t Date::getDay() const { + return (static_cast(timespan) % DAYS_IN_YEAR) - DAYS_UP_TO_MONTH[getMonth() - 1] + 1; +} + +bool Date::operator<(Date other) const { return timespan < other.timespan; }; +bool Date::operator>(Date other) const { return timespan > other.timespan; }; +bool Date::operator<=(Date other) const { return timespan <= other.timespan; }; +bool Date::operator>=(Date other) const { return timespan >= other.timespan; }; +bool Date::operator==(Date other) const { return timespan == other.timespan; }; +bool Date::operator!=(Date other) const { return timespan != other.timespan; }; + +Date Date::operator+(Timespan other) const { return timespan + other; } + +Timespan Date::operator-(Date other) const { return timespan - other.timespan; } + +Date& Date::operator+=(Timespan other) { + timespan += other; + return *this; +} + +Date& Date::operator-=(Timespan other) { + timespan -= other; + return *this; +} + +Date& Date::operator++() { + timespan++; + return *this; +} + +Date Date::operator++(int) { + Date old = *this; + ++(*this); + return old; +} + +std::string Date::to_string() const { + std::stringstream ss; + ss << *this; + return ss.str(); +} + +Date::operator std::string() const { + return to_string(); +} + +std::ostream& OpenVic::operator<<(std::ostream& out, Date const& date) { + return out << static_cast(date.getYear()) << Date::SEPARATOR_CHARACTER << static_cast(date.getMonth()) << Date::SEPARATOR_CHARACTER << static_cast(date.getDay()); +} + +// Parsed from string of the form YYYY.MM.DD +Date Date::from_string(char const* const str, char const* const end, bool* successful) { + if (successful != nullptr) *successful = true; + + year_t year = 0; + month_t month = 1; + day_t day = 1; + + if (str == nullptr || end <= str) { + Logger::error("Invalid string start/end pointers: ", static_cast(str), " - ", static_cast(end)); + if (successful != nullptr) *successful = false; + return { year, month, day }; + } + + char const* year_end = str; + while (std::isdigit(*year_end) && ++year_end < end); + + if (year_end <= str) { + Logger::error("Failed to find year digits in date: ", std::string_view { str, static_cast(end - str) }); + if (successful != nullptr) *successful = false; + return { year, month, day }; + } + + bool sub_successful = false; + uint64_t val = StringUtils::string_to_uint64(str, year_end, &sub_successful, 10); + if (!sub_successful || val >= 1 << (8 * sizeof(year_t))) { + Logger::error("Failed to read year: ", std::string_view { str, static_cast(end - str) }); + if (successful != nullptr) *successful = false; + return { year, month, day }; + } + year = val; + if (year_end < end) { + if (*year_end == SEPARATOR_CHARACTER) { + char const* const month_start = year_end + 1; + char const* month_end = month_start; + if (month_start < end) { + while (std::isdigit(*month_end) && ++month_end < end); + } + if (month_start >= month_end) { + Logger::error("Failed to find month digits in date: ", std::string_view { str, static_cast(end - str) }); + if (successful != nullptr) *successful = false; + } else { + sub_successful = false; + val = StringUtils::string_to_uint64(month_start, month_end, &sub_successful, 10); + if (!sub_successful || val < 1 || val > MONTHS_IN_YEAR) { + Logger::error("Failed to read month: ", std::string_view { str, static_cast(end - str) }); + if (successful != nullptr) *successful = false; + } else { + month = val; + if (month_end < end) { + if (*month_end == SEPARATOR_CHARACTER) { + char const* const day_start = month_end + 1; + char const* day_end = day_start; + if (day_start < end) { + while (std::isdigit(*day_end) && ++day_end < end); + } + if (day_start >= day_end) { + Logger::error("Failed to find day digits in date: ", std::string_view { str, static_cast(end - str) }); + if (successful != nullptr) *successful = false; + } else { + sub_successful = false; + val = StringUtils::string_to_uint64(day_start, day_end, &sub_successful); + if (!sub_successful || val < 1 || val > DAYS_IN_MONTH[month - 1]) { + Logger::error("Failed to read day: ", std::string_view { str, static_cast(end - str) }); + if (successful != nullptr) *successful = false; + } else { + day = val; + if (day_end < end) { + Logger::error("Unexpected string \"", std::string_view { day_end, static_cast(end - day_end) }, "\" at the end of date ", std::string_view { str, static_cast(end - str) }); + if (successful != nullptr) *successful = false; + } + } + } + } else { + Logger::error("Unexpected character \"", *month_end, "\" in month of date ", std::string_view { str, static_cast(end - str) }); + if (successful != nullptr) *successful = false; + } + } + } + } + } else { + Logger::error("Unexpected character \"", *year_end, "\" in year of date ", std::string_view { str, static_cast(end - str) }); + if (successful != nullptr) *successful = false; + } + } + return { year, month, day }; +}; + +Date Date::from_string(char const* str, size_t length, bool* successful) { + return from_string(str, str + length, successful); +} + +Date Date::from_string(const std::string_view str, bool* successful) { + return from_string(str.data(), str.length(), successful); +} diff --git a/src/openvic-simulation/types/Date.hpp b/src/openvic-simulation/types/Date.hpp new file mode 100644 index 0000000..601f9dc --- /dev/null +++ b/src/openvic-simulation/types/Date.hpp @@ -0,0 +1,96 @@ +#pragma once + +#include +#include +#include + +namespace OpenVic { + // A relative period between points in time, measured in days + struct Timespan { + using day_t = int64_t; + + private: + day_t days; + + public: + Timespan(day_t value = 0); + + bool operator<(Timespan other) const; + bool operator>(Timespan other) const; + bool operator<=(Timespan other) const; + bool operator>=(Timespan other) const; + bool operator==(Timespan other) const; + bool operator!=(Timespan other) const; + + Timespan operator+(Timespan other) const; + Timespan operator-(Timespan other) const; + Timespan operator*(day_t factor) const; + Timespan operator/(day_t factor) const; + Timespan& operator+=(Timespan other); + Timespan& operator-=(Timespan other); + Timespan& operator++(); + Timespan operator++(int); + + explicit operator day_t() const; + explicit operator double() const; + std::string to_string() const; + explicit operator std::string() const; + }; + std::ostream& operator<<(std::ostream& out, Timespan const& timespan); + + // Represents an in-game date + // Note: Current implementation does not account for leap-years, or dates before Year 0 + struct Date { + using year_t = uint16_t; + using month_t = uint8_t; + using day_t = uint8_t; + + static constexpr Timespan::day_t MONTHS_IN_YEAR = 12; + static constexpr Timespan::day_t DAYS_IN_YEAR = 365; + static constexpr Timespan::day_t DAYS_IN_MONTH[MONTHS_IN_YEAR] { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + static Timespan::day_t const* DAYS_UP_TO_MONTH; + static month_t const* MONTH_FROM_DAY_IN_YEAR; + + static constexpr char SEPARATOR_CHARACTER = '.'; + + private: + // Number of days since Jan 1st, Year 0 + Timespan timespan; + + static Timespan _dateToTimespan(year_t year, month_t month, day_t day); + static Timespan::day_t const* generate_days_up_to_month(); + static month_t const* generate_month_from_day_in_year(); + + public: + // The Timespan is considered to be the number of days since Jan 1st, Year 0 + Date(Timespan total_days); + // Year month day specification + Date(year_t year = 0, month_t month = 1, day_t day = 1); + + year_t getYear() const; + month_t getMonth() const; + day_t getDay() const; + + bool operator<(Date other) const; + bool operator>(Date other) const; + bool operator<=(Date other) const; + bool operator>=(Date other) const; + bool operator==(Date other) const; + bool operator!=(Date other) const; + + Date operator+(Timespan other) const; + Timespan operator-(Date other) const; + Date& operator+=(Timespan other); + Date& operator-=(Timespan other); + Date& operator++(); + Date operator++(int); + + std::string to_string() const; + explicit operator std::string() const; + // Parsed from string of the form YYYY.MM.DD + static Date from_string(char const* str, char const* end, bool* successful = nullptr); + static Date from_string(char const* str, size_t length, bool* successful = nullptr); + static Date from_string(const std::string_view str, bool* successful = nullptr); + }; + std::ostream& operator<<(std::ostream& out, Date const& date); +} diff --git a/src/openvic-simulation/types/IdentifierRegistry.cpp b/src/openvic-simulation/types/IdentifierRegistry.cpp new file mode 100644 index 0000000..6d5221b --- /dev/null +++ b/src/openvic-simulation/types/IdentifierRegistry.cpp @@ -0,0 +1,37 @@ +#include "IdentifierRegistry.hpp" + +#include + +using namespace OpenVic; + +HasIdentifier::HasIdentifier(const std::string_view new_identifier) + : identifier { new_identifier } { + assert(!identifier.empty()); +} + +std::string const& HasIdentifier::get_identifier() const { + return identifier; +} + +HasColour::HasColour(colour_t const new_colour, bool can_be_null) : colour(new_colour) { + assert((can_be_null || colour != NULL_COLOUR) && colour <= MAX_COLOUR_RGB); +} + +colour_t HasColour::get_colour() const { return colour; } + +std::string HasColour::colour_to_hex_string() const { + return OpenVic::colour_to_hex_string(colour); +} + +HasIdentifierAndColour::HasIdentifierAndColour(const std::string_view new_identifier, + const colour_t new_colour, bool can_be_null) + : HasIdentifier { new_identifier }, + HasColour { new_colour, can_be_null } {} + +distribution_t::value_type OpenVic::get_largest_item(distribution_t const& dist) { + const distribution_t::const_iterator result = std::max_element(dist.begin(), dist.end(), + [](distribution_t::value_type a, distribution_t::value_type b) -> bool { + return a.second < b.second; + }); + return result != dist.end() ? *result : distribution_t::value_type { nullptr, -1.0f }; +} diff --git a/src/openvic-simulation/types/IdentifierRegistry.hpp b/src/openvic-simulation/types/IdentifierRegistry.hpp new file mode 100644 index 0000000..494ff3e --- /dev/null +++ b/src/openvic-simulation/types/IdentifierRegistry.hpp @@ -0,0 +1,197 @@ +#pragma once + +#include +#include + +#include "openvic-simulation/dataloader/NodeTools.hpp" +#include "openvic-simulation/utility/Logger.hpp" + +namespace OpenVic { + /* + * Base class for objects with a non-empty string identifier, + * uniquely named instances of which can be entered into an + * IdentifierRegistry instance. + */ + class HasIdentifier { + const std::string identifier; + + protected: + HasIdentifier(const std::string_view new_identifier); + + public: + HasIdentifier(HasIdentifier const&) = delete; + HasIdentifier(HasIdentifier&&) = default; + HasIdentifier& operator=(HasIdentifier const&) = delete; + HasIdentifier& operator=(HasIdentifier&&) = delete; + + std::string const& get_identifier() const; + }; + + /* + * Base class for objects with associated colour information. + */ + class HasColour { + const colour_t colour; + + protected: + HasColour(const colour_t new_colour, bool can_be_null); + + public: + HasColour(HasColour const&) = delete; + HasColour(HasColour&&) = default; + HasColour& operator=(HasColour const&) = delete; + HasColour& operator=(HasColour&&) = delete; + + colour_t get_colour() const; + std::string colour_to_hex_string() const; + }; + + /* + * Base class for objects with a unique string identifier + * and associated colour information. + */ + class HasIdentifierAndColour : public HasIdentifier, public HasColour { + protected: + HasIdentifierAndColour(const std::string_view new_identifier, const colour_t new_colour, bool can_be_null); + + public: + HasIdentifierAndColour(HasIdentifierAndColour const&) = delete; + HasIdentifierAndColour(HasIdentifierAndColour&&) = default; + HasIdentifierAndColour& operator=(HasIdentifierAndColour const&) = delete; + HasIdentifierAndColour& operator=(HasIdentifierAndColour&&) = delete; + }; + + using distribution_t = std::map; + + distribution_t::value_type get_largest_item(distribution_t const& dist); + + /* + * Template for a list of objects with unique string identifiers that can + * be locked to prevent any further additions. The template argument T is + * the type of object that the registry will store, and the second part ensures + * that HasIdentifier is a base class of T. + */ + template + requires(std::derived_from) + class IdentifierRegistry { + using identifier_index_map_t = std::map>; + + const std::string name; + const bool log_lock; + std::vector items; + bool locked = false; + identifier_index_map_t identifier_index_map; + + public: + IdentifierRegistry(const std::string_view new_name, bool new_log_lock = true) + : name { new_name }, log_lock { new_log_lock } {} + + std::string const& get_name() const { + return name; + } + + bool add_item(T&& item) { + if (locked) { + Logger::error("Cannot add item to the ", name, " registry - locked!"); + return false; + } + T const* old_item = get_item_by_identifier(item.get_identifier()); + if (old_item != nullptr) { + Logger::error("Cannot add item to the ", name, " registry - an item with the identifier \"", item.get_identifier(), "\" already exists!"); + return false; + } + identifier_index_map[item.get_identifier()] = items.size(); + items.push_back(std::move(item)); + return true; + } + + void lock() { + if (locked) { + Logger::error("Failed to lock ", name, " registry - already locked!"); + } else { + locked = true; + if (log_lock) Logger::info("Locked ", name, " registry after registering ", size(), " items"); + } + } + + bool is_locked() const { + return locked; + } + + void reset() { + identifier_index_map.clear(); + items.clear(); + locked = false; + } + + size_t size() const { + return items.size(); + } + + bool empty() const { + return items.empty(); + } + + void reserve(size_t size) { + if (locked) { + Logger::error("Failed to reserve space for ", size, " items in ", name, " registry - already locked!"); + } else { + items.reserve(size); + } + } + + T* get_item_by_identifier(const std::string_view identifier) { + const identifier_index_map_t::const_iterator it = identifier_index_map.find(identifier); + if (it != identifier_index_map.end()) return &items[it->second]; + return nullptr; + } + + T const* get_item_by_identifier(const std::string_view identifier) const { + const identifier_index_map_t::const_iterator it = identifier_index_map.find(identifier); + if (it != identifier_index_map.end()) return &items[it->second]; + return nullptr; + } + + T* get_item_by_index(size_t index) { + return index < items.size() ? &items[index] : nullptr; + } + + T const* get_item_by_index(size_t index) const { + return index < items.size() ? &items[index] : nullptr; + } + + std::vector& get_items() { + return items; + } + + std::vector const& get_items() const { + return items; + } + + NodeTools::node_callback_t expect_item(T const*& ret) const { + return NodeTools::expect_identifier( + [this, &ret](std::string_view identifier) -> bool { + ret = get_item_by_identifier(identifier); + if (ret != nullptr) return true; + Logger::error("Invalid ", name, ": ", identifier); + return false; + } + ); + } + }; + +#define IDENTIFIER_REGISTRY_ACCESSORS_CUSTOM_PLURAL(type, singular, plural) \ + void lock_##plural() { plural.lock(); } \ + type const* get_##singular##_by_index(size_t index) const { \ + return plural.get_item_by_index(index); } \ + type const* get_##singular##_by_identifier(const std::string_view identifier) const { \ + return plural.get_item_by_identifier(identifier); } \ + size_t get_##singular##_count() const { \ + return plural.size(); } \ + std::vector const& get_##plural() const { \ + return plural.get_items(); } \ + NodeTools::node_callback_t expect_##singular(type const*& ret) const { \ + return plural.expect_item(ret); } + +#define IDENTIFIER_REGISTRY_ACCESSORS(type, name) IDENTIFIER_REGISTRY_ACCESSORS_CUSTOM_PLURAL(type, name, name##s) +} diff --git a/src/openvic-simulation/types/fixed_point/FixedPoint.hpp b/src/openvic-simulation/types/fixed_point/FixedPoint.hpp new file mode 100644 index 0000000..913f237 --- /dev/null +++ b/src/openvic-simulation/types/fixed_point/FixedPoint.hpp @@ -0,0 +1,584 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "openvic-simulation/utility/Logger.hpp" +#include "openvic-simulation/utility/NumberUtils.hpp" +#include "openvic-simulation/utility/StringUtils.hpp" + +#include "FixedPointLUT.hpp" + +namespace OpenVic { + struct fixed_point_t { + static constexpr size_t SIZE = 8; + + static constexpr int32_t PRECISION = FPLUT::SIN_LUT_PRECISION; + static constexpr int64_t ONE = 1 << PRECISION; + + constexpr fixed_point_t() : value { 0 } {} + constexpr fixed_point_t(int64_t new_value) : value { new_value } {} + constexpr fixed_point_t(int32_t new_value) : value { static_cast(new_value) << PRECISION } {} + + // Trivial destructor + ~fixed_point_t() = default; + + static constexpr fixed_point_t max() { + return std::numeric_limits::max(); + } + + static constexpr fixed_point_t min() { + return std::numeric_limits::min(); + } + + static constexpr fixed_point_t usable_max() { + return static_cast(2147483648LL); + } + + static constexpr fixed_point_t usable_min() { + return -usable_max(); + } + + static constexpr fixed_point_t _0() { + return 0; + } + + static constexpr fixed_point_t _1() { + return 1; + } + + static constexpr fixed_point_t _2() { + return 2; + } + + static constexpr fixed_point_t _3() { + return 3; + } + + static constexpr fixed_point_t _4() { + return 4; + } + + static constexpr fixed_point_t _5() { + return 5; + } + + static constexpr fixed_point_t _6() { + return 6; + } + + static constexpr fixed_point_t _7() { + return 7; + } + + static constexpr fixed_point_t _8() { + return 8; + } + + static constexpr fixed_point_t _9() { + return 9; + } + + static constexpr fixed_point_t _10() { + return 10; + } + + static constexpr fixed_point_t _50() { + return 50; + } + + static constexpr fixed_point_t _100() { + return 100; + } + + static constexpr fixed_point_t _200() { + return 200; + } + + static constexpr fixed_point_t _0_01() { + return _1() / _100(); + } + + static constexpr fixed_point_t _0_02() { + return _0_01() * 2; + } + + static constexpr fixed_point_t _0_03() { + return _0_01() * 3; + } + + static constexpr fixed_point_t _0_04() { + return _0_01() * 4; + } + + static constexpr fixed_point_t _0_05() { + return _0_01() * 5; + } + + static constexpr fixed_point_t _0_10() { + return _1() / 10; + } + + static constexpr fixed_point_t _0_20() { + return _0_10() * 2; + } + + static constexpr fixed_point_t _0_25() { + return _1() / 4; + } + + static constexpr fixed_point_t _0_33() { + return _1() / 3; + } + + static constexpr fixed_point_t _0_50() { + return _1() / 2; + } + + static constexpr fixed_point_t _0_75() { + return _1() - _0_25(); + } + + static constexpr fixed_point_t _0_95() { + return _1() - _0_05(); + } + + static constexpr fixed_point_t _0_99() { + return _1() - _0_01(); + } + + static constexpr fixed_point_t _1_01() { + return _1() + _0_01(); + } + + static constexpr fixed_point_t _1_10() { + return _1() + _0_10(); + } + + static constexpr fixed_point_t _1_50() { + return _1() + _0_50(); + } + + static constexpr fixed_point_t minus_one() { + return -1; + } + + static constexpr fixed_point_t pi() { + return static_cast(205887LL); + } + + static constexpr fixed_point_t pi2() { + return pi() * 2; + } + + static constexpr fixed_point_t pi_quarter() { + return pi() / 4; + } + + static constexpr fixed_point_t pi_half() { + return pi() / 2; + } + + static constexpr fixed_point_t one_div_pi2() { + return 1 / pi2(); + } + + static constexpr fixed_point_t deg2rad() { + return static_cast(1143LL); + } + + static constexpr fixed_point_t rad2deg() { + return static_cast(3754936LL); + } + + static constexpr fixed_point_t e() { + return static_cast(178145LL); + } + + constexpr bool is_negative() const { + return value < 0; + } + + constexpr fixed_point_t abs() const { + return !is_negative() ? value : -value; + } + + // Doesn't account for sign, so -n.abc -> 1 - 0.abc + constexpr fixed_point_t get_frac() const { + return value & (ONE - 1); + } + + constexpr int64_t to_int64_t() const { + return value >> PRECISION; + } + + constexpr void set_raw_value(int64_t value) { + this->value = value; + } + + constexpr int64_t get_raw_value() const { + return value; + } + + constexpr int32_t to_int32_t() const { + return static_cast(to_int64_t()); + } + + constexpr float to_float() const { + return value / static_cast(ONE); + } + + constexpr float to_float_rounded() const { + return static_cast(NumberUtils::round_to_int64((value / static_cast(ONE)) * 100000.0f)) / 100000.0f; + } + + constexpr double to_double() const { + return value / static_cast(ONE); + } + + constexpr float to_double_rounded() const { + return NumberUtils::round_to_int64((value / static_cast(ONE)) * 100000.0) / 100000.0; + } + + std::string to_string() const { + fixed_point_t val = abs(); + std::string str = std::to_string(val.to_int64_t()) + "."; + if (is_negative()) str = "-" + str; + val = val.get_frac(); + do { + val *= 10; + str.push_back('0' + static_cast(val.to_int64_t())); + val = val.get_frac(); + } while (val > 0); + return str; + } + + // Deterministic + static constexpr fixed_point_t parse_raw(int64_t value) { + return value; + } + + // Deterministic + static constexpr fixed_point_t parse(int64_t value) { + return value << PRECISION; + } + + // Deterministic + static constexpr fixed_point_t parse(char const* str, char const* const end, bool* successful = nullptr) { + if (successful != nullptr) *successful = false; + + if (str == nullptr || str >= end) { + return _0(); + } + + bool negative = false; + + if (*str == '-') { + negative = true; + ++str; + if (str == end) return _0(); + } + + char const* dot_pointer = str; + while (*dot_pointer != '.' && ++dot_pointer != end); + + if (dot_pointer == str && dot_pointer + 1 == end) { + // Invalid: ".", "+." or "-." + return _0(); + } + + fixed_point_t result = _0(); + if (successful != nullptr) *successful = true; + + if (dot_pointer != str) { + // Non-empty integer part + bool int_successful = false; + result += parse_integer(str, dot_pointer, &int_successful); + if (!int_successful && successful != nullptr) *successful = false; + } + + if (dot_pointer + 1 < end) { + // Non-empty fractional part + bool frac_successful = false; + result += parse_fraction(dot_pointer + 1, end, &frac_successful); + if (!frac_successful && successful != nullptr) *successful = false; + } + + return negative ? -result : result; + } + + static constexpr fixed_point_t parse(char const* str, size_t length, bool* successful = nullptr) { + return parse(str, str + length, successful); + } + + static fixed_point_t parse(const std::string_view str, bool* successful = nullptr) { + return parse(str.data(), str.length(), successful); + } + + // Not Deterministic + static constexpr fixed_point_t parse_unsafe(float value) { + return static_cast(value * ONE + 0.5f * (value < 0 ? -1 : 1)); + } + + // Not Deterministic + static fixed_point_t parse_unsafe(char const* value) { + char* endpointer; + double double_value = std::strtod(value, &endpointer); + + if (*endpointer != '\0') { + Logger::error("Unsafe fixed point parse failed to parse the end of a string: \"", endpointer, "\""); + } + + int64_t integer_value = static_cast(double_value * ONE + 0.5 * (double_value < 0 ? -1 : 1)); + + return integer_value; + } + + constexpr operator int32_t() const { + return to_int32_t(); + } + + constexpr operator int64_t() const { + return to_int64_t(); + } + + constexpr operator float() const { + return to_float(); + } + + constexpr operator double() const { + return to_double(); + } + + operator std::string() const { + return to_string(); + } + + friend std::ostream& operator<<(std::ostream& stream, fixed_point_t const& obj) { + return stream << obj.to_string(); + } + + constexpr friend fixed_point_t operator-(fixed_point_t const& obj) { + return -obj.value; + } + + constexpr friend fixed_point_t operator+(fixed_point_t const& obj) { + return +obj.value; + } + + constexpr friend fixed_point_t operator+(fixed_point_t const& lhs, fixed_point_t const& rhs) { + return lhs.value + rhs.value; + } + + constexpr friend fixed_point_t operator+(fixed_point_t const& lhs, int32_t const& rhs) { + return lhs.value + (static_cast(rhs) << PRECISION); + } + + constexpr friend fixed_point_t operator+(int32_t const& lhs, fixed_point_t const& rhs) { + return (static_cast(lhs) << PRECISION) + rhs.value; + } + + constexpr fixed_point_t operator+=(fixed_point_t const& obj) { + value += obj.value; + return *this; + } + + constexpr fixed_point_t operator+=(int32_t const& obj) { + value += (static_cast(obj) << PRECISION); + return *this; + } + + constexpr friend fixed_point_t operator-(fixed_point_t const& lhs, fixed_point_t const& rhs) { + return lhs.value - rhs.value; + } + + constexpr friend fixed_point_t operator-(fixed_point_t const& lhs, int32_t const& rhs) { + return lhs.value - (static_cast(rhs) << PRECISION); + } + + constexpr friend fixed_point_t operator-(int32_t const& lhs, fixed_point_t const& rhs) { + return (static_cast(lhs) << PRECISION) - rhs.value; + } + + constexpr fixed_point_t operator-=(fixed_point_t const& obj) { + value -= obj.value; + return *this; + } + + constexpr fixed_point_t operator-=(int32_t const& obj) { + value -= (static_cast(obj) << PRECISION); + return *this; + } + + constexpr friend fixed_point_t operator*(fixed_point_t const& lhs, fixed_point_t const& rhs) { + return lhs.value * rhs.value >> PRECISION; + } + + constexpr friend fixed_point_t operator*(fixed_point_t const& lhs, int32_t const& rhs) { + return lhs.value * rhs; + } + + constexpr friend fixed_point_t operator*(int32_t const& lhs, fixed_point_t const& rhs) { + return lhs * rhs.value; + } + + constexpr fixed_point_t operator*=(fixed_point_t const& obj) { + value *= obj.value >> PRECISION; + return *this; + } + + constexpr fixed_point_t operator*=(int32_t const& obj) { + value *= obj; + return *this; + } + + constexpr friend fixed_point_t operator/(fixed_point_t const& lhs, fixed_point_t const& rhs) { + return (lhs.value << PRECISION) / rhs.value; + } + + constexpr friend fixed_point_t operator/(fixed_point_t const& lhs, int32_t const& rhs) { + return lhs.value / rhs; + } + + constexpr friend fixed_point_t operator/(int32_t const& lhs, fixed_point_t const& rhs) { + return (static_cast(lhs) << (2 * PRECISION)) / rhs.value; + } + + constexpr fixed_point_t operator/=(fixed_point_t const& obj) { + value = (value << PRECISION) / obj.value; + return *this; + } + + constexpr fixed_point_t operator/=(int32_t const& obj) { + value /= obj; + return *this; + } + + constexpr friend fixed_point_t operator%(fixed_point_t const& lhs, fixed_point_t const& rhs) { + return lhs.value % rhs.value; + } + + constexpr friend fixed_point_t operator%(fixed_point_t const& lhs, int32_t const& rhs) { + return lhs.value % (static_cast(rhs) << PRECISION); + } + + constexpr friend fixed_point_t operator%(int32_t const& lhs, fixed_point_t const& rhs) { + return (static_cast(lhs) << PRECISION) % rhs.value; + } + + constexpr fixed_point_t operator%=(fixed_point_t const& obj) { + value %= obj.value; + return *this; + } + + constexpr fixed_point_t operator%=(int32_t const& obj) { + value %= (static_cast(obj) << PRECISION); + return *this; + } + + constexpr friend bool operator<(fixed_point_t const& lhs, fixed_point_t const& rhs) { + return lhs.value < rhs.value; + } + + constexpr friend bool operator<(fixed_point_t const& lhs, int32_t const& rhs) { + return lhs.value < static_cast(rhs) << PRECISION; + } + + constexpr friend bool operator<(int32_t const& lhs, fixed_point_t const& rhs) { + return static_cast(lhs) << PRECISION < rhs.value; + } + + constexpr friend bool operator<=(fixed_point_t const& lhs, fixed_point_t const& rhs) { + return lhs.value <= rhs.value; + } + + constexpr friend bool operator<=(fixed_point_t const& lhs, int32_t const& rhs) { + return lhs.value <= static_cast(rhs) << PRECISION; + } + + constexpr friend bool operator<=(int32_t const& lhs, fixed_point_t const& rhs) { + return static_cast(lhs) << PRECISION <= rhs.value; + } + + constexpr friend bool operator>(fixed_point_t const& lhs, fixed_point_t const& rhs) { + return lhs.value > rhs.value; + } + + constexpr friend bool operator>(fixed_point_t const& lhs, int32_t const& rhs) { + return lhs.value > static_cast(rhs) << PRECISION; + } + + constexpr friend bool operator>(int32_t const& lhs, fixed_point_t const& rhs) { + return static_cast(lhs) << PRECISION > rhs.value; + } + + constexpr friend bool operator>=(fixed_point_t const& lhs, fixed_point_t const& rhs) { + return lhs.value >= rhs.value; + } + + constexpr friend bool operator>=(fixed_point_t const& lhs, int32_t const& rhs) { + return lhs.value >= static_cast(rhs) << PRECISION; + } + + constexpr friend bool operator>=(int32_t const& lhs, fixed_point_t const& rhs) { + return static_cast(lhs) << PRECISION >= rhs.value; + } + + constexpr friend bool operator==(fixed_point_t const& lhs, fixed_point_t const& rhs) { + return lhs.value == rhs.value; + } + + constexpr friend bool operator==(fixed_point_t const& lhs, int32_t const& rhs) { + return lhs.value == static_cast(rhs) << PRECISION; + } + + constexpr friend bool operator==(int32_t const& lhs, fixed_point_t const& rhs) { + return static_cast(lhs) << PRECISION == rhs.value; + } + + constexpr friend bool operator!=(fixed_point_t const& lhs, fixed_point_t const& rhs) { + return lhs.value != rhs.value; + } + + constexpr friend bool operator!=(fixed_point_t const& lhs, int32_t const& rhs) { + return lhs.value != static_cast(rhs) << PRECISION; + } + + constexpr friend bool operator!=(int32_t const& lhs, fixed_point_t const& rhs) { + return static_cast(lhs) << PRECISION != rhs.value; + } + + private: + int64_t value; + + static constexpr fixed_point_t parse_integer(char const* str, char const* const end, bool* successful) { + int64_t parsed_value = StringUtils::string_to_int64(str, end, successful, 10); + return parse(parsed_value); + } + + static constexpr fixed_point_t parse_fraction(char const* str, char const* end, bool* successful) { + char const* const read_end = str + PRECISION; + if (read_end < end) end = read_end; + uint64_t parsed_value = StringUtils::string_to_uint64(str, end, successful, 10); + while (end++ < read_end) { + parsed_value *= 10; + } + uint64_t decimal = NumberUtils::pow(static_cast(10), PRECISION); + int64_t ret = 0; + for (int i = PRECISION - 1; i >= 0; --i) { + decimal >>= 1; + if (parsed_value > decimal) { + parsed_value -= decimal; + ret |= 1 << i; + } + } + return ret; + } + }; + + static_assert(sizeof(fixed_point_t) == fixed_point_t::SIZE, "fixed_point_t is not 8 bytes"); +} diff --git a/src/openvic-simulation/types/fixed_point/FixedPointLUT.hpp b/src/openvic-simulation/types/fixed_point/FixedPointLUT.hpp new file mode 100644 index 0000000..466517b --- /dev/null +++ b/src/openvic-simulation/types/fixed_point/FixedPointLUT.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace OpenVic::FPLUT { + + #include "FixedPointLUT_sin.hpp" + + constexpr int32_t SHIFT = SIN_LUT_PRECISION - SIN_LUT_COUNT_LOG2; + + constexpr int64_t sin(int64_t value) { + int sign = 1; + if (value < 0) { + value = -value; + sign = -1; + } + + int index = value >> SHIFT; + int64_t a = SIN_LUT[index]; + int64_t b = SIN_LUT[index + 1]; + int64_t fraction = (value - (index << SHIFT)) << SIN_LUT_COUNT_LOG2; + int64_t result = a + (((b - a) * fraction) >> SIN_LUT_PRECISION); + return result * sign; + } +} diff --git a/src/openvic-simulation/types/fixed_point/FixedPointLUT_sin.hpp b/src/openvic-simulation/types/fixed_point/FixedPointLUT_sin.hpp new file mode 100644 index 0000000..0c75efe --- /dev/null +++ b/src/openvic-simulation/types/fixed_point/FixedPointLUT_sin.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include + +static constexpr int32_t SIN_LUT_PRECISION = 16; +static constexpr int32_t SIN_LUT_COUNT_LOG2 = 9; + +static constexpr int64_t SIN_LUT[(1 << SIN_LUT_COUNT_LOG2) + 1] = { + 0, 804, 1608, 2412, 3216, 4019, 4821, 5623, 6424, 7224, 8022, 8820, 9616, 10411, 11204, 11996, + 12785, 13573, 14359, 15143, 15924, 16703, 17479, 18253, 19024, 19792, 20557, 21320, 22078, 22834, 23586, 24335, + 25080, 25821, 26558, 27291, 28020, 28745, 29466, 30182, 30893, 31600, 32303, 33000, 33692, 34380, 35062, 35738, + 36410, 37076, 37736, 38391, 39040, 39683, 40320, 40951, 41576, 42194, 42806, 43412, 44011, 44604, 45190, 45769, + 46341, 46906, 47464, 48015, 48559, 49095, 49624, 50146, 50660, 51166, 51665, 52156, 52639, 53114, 53581, 54040, + 54491, 54934, 55368, 55794, 56212, 56621, 57022, 57414, 57798, 58172, 58538, 58896, 59244, 59583, 59914, 60235, + 60547, 60851, 61145, 61429, 61705, 61971, 62228, 62476, 62714, 62943, 63162, 63372, 63572, 63763, 63944, 64115, + 64277, 64429, 64571, 64704, 64827, 64940, 65043, 65137, 65220, 65294, 65358, 65413, 65457, 65492, 65516, 65531, + 65536, 65531, 65516, 65492, 65457, 65413, 65358, 65294, 65220, 65137, 65043, 64940, 64827, 64704, 64571, 64429, + 64277, 64115, 63944, 63763, 63572, 63372, 63162, 62943, 62714, 62476, 62228, 61971, 61705, 61429, 61145, 60851, + 60547, 60235, 59914, 59583, 59244, 58896, 58538, 58172, 57798, 57414, 57022, 56621, 56212, 55794, 55368, 54934, + 54491, 54040, 53581, 53114, 52639, 52156, 51665, 51166, 50660, 50146, 49624, 49095, 48559, 48015, 47464, 46906, + 46341, 45769, 45190, 44604, 44011, 43412, 42806, 42194, 41576, 40951, 40320, 39683, 39040, 38391, 37736, 37076, + 36410, 35738, 35062, 34380, 33692, 33000, 32303, 31600, 30893, 30182, 29466, 28745, 28020, 27291, 26558, 25821, + 25080, 24335, 23586, 22834, 22078, 21320, 20557, 19792, 19024, 18253, 17479, 16703, 15924, 15143, 14359, 13573, + 12785, 11996, 11204, 10411, 9616, 8820, 8022, 7224, 6424, 5623, 4821, 4019, 3216, 2412, 1608, 804, + 0, -804, -1608, -2412, -3216, -4019, -4821, -5623, -6424, -7224, -8022, -8820, -9616, -10411, -11204, -11996, + -12785, -13573, -14359, -15143, -15924, -16703, -17479, -18253, -19024, -19792, -20557, -21320, -22078, -22834, -23586, -24335, + -25080, -25821, -26558, -27291, -28020, -28745, -29466, -30182, -30893, -31600, -32303, -33000, -33692, -34380, -35062, -35738, + -36410, -37076, -37736, -38391, -39040, -39683, -40320, -40951, -41576, -42194, -42806, -43412, -44011, -44604, -45190, -45769, + -46341, -46906, -47464, -48015, -48559, -49095, -49624, -50146, -50660, -51166, -51665, -52156, -52639, -53114, -53581, -54040, + -54491, -54934, -55368, -55794, -56212, -56621, -57022, -57414, -57798, -58172, -58538, -58896, -59244, -59583, -59914, -60235, + -60547, -60851, -61145, -61429, -61705, -61971, -62228, -62476, -62714, -62943, -63162, -63372, -63572, -63763, -63944, -64115, + -64277, -64429, -64571, -64704, -64827, -64940, -65043, -65137, -65220, -65294, -65358, -65413, -65457, -65492, -65516, -65531, + -65536, -65531, -65516, -65492, -65457, -65413, -65358, -65294, -65220, -65137, -65043, -64940, -64827, -64704, -64571, -64429, + -64277, -64115, -63944, -63763, -63572, -63372, -63162, -62943, -62714, -62476, -62228, -61971, -61705, -61429, -61145, -60851, + -60547, -60235, -59914, -59583, -59244, -58896, -58538, -58172, -57798, -57414, -57022, -56621, -56212, -55794, -55368, -54934, + -54491, -54040, -53581, -53114, -52639, -52156, -51665, -51166, -50660, -50146, -49624, -49095, -48559, -48015, -47464, -46906, + -46341, -45769, -45190, -44604, -44011, -43412, -42806, -42194, -41576, -40951, -40320, -39683, -39040, -38391, -37736, -37076, + -36410, -35738, -35062, -34380, -33692, -33000, -32303, -31600, -30893, -30182, -29466, -28745, -28020, -27291, -26558, -25821, + -25080, -24335, -23586, -22834, -22078, -21320, -20557, -19792, -19024, -18253, -17479, -16703, -15924, -15143, -14359, -13573, + -12785, -11996, -11204, -10411, -9616, -8820, -8022, -7224, -6424, -5623, -4821, -4019, -3216, -2412, -1608, -804, + 0 +}; diff --git a/src/openvic-simulation/types/fixed_point/FixedPointMath.hpp b/src/openvic-simulation/types/fixed_point/FixedPointMath.hpp new file mode 100644 index 0000000..c0b5f42 --- /dev/null +++ b/src/openvic-simulation/types/fixed_point/FixedPointMath.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include "FixedPoint.hpp" + +namespace OpenVic::FPMath { + constexpr fixed_point_t sin(fixed_point_t number) { + number %= fixed_point_t::pi2(); + number *= fixed_point_t::one_div_pi2(); + return FPLUT::sin(number.get_raw_value()); + } +} diff --git a/src/openvic-simulation/types/fixed_point/lut_generator/lut_generator.py b/src/openvic-simulation/types/fixed_point/lut_generator/lut_generator.py new file mode 100644 index 0000000..8ae7a32 --- /dev/null +++ b/src/openvic-simulation/types/fixed_point/lut_generator/lut_generator.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python +from math import pi, sin +from argparse import ArgumentParser +from sys import exit + +def generate_sin_lut(precision : int, count_log2 : int): + one = 1 << precision + count = 1 << count_log2 + + SinLut = [] + + for i in range(count): + angle = 2 * pi * i / count + + sin_value = sin(angle) + moved_sin = sin_value * one + rounded_sin = int(moved_sin + 0.5) if moved_sin > 0 else int(moved_sin - 0.5) + SinLut.append(rounded_sin) + + SinLut.append(SinLut[0]) + + output = [ + "#pragma once", + "", + "#include ", + "", + f"static constexpr int32_t SIN_LUT_PRECISION = {precision};", + f"static constexpr int32_t SIN_LUT_COUNT_LOG2 = {count_log2};", + "", + "static constexpr int64_t SIN_LUT[(1 << SIN_LUT_COUNT_LOG2) + 1] = {" + ] + + VALS_PER_LINE = 16 + + lines = [SinLut[i : i + VALS_PER_LINE] for i in range(0, len(SinLut), VALS_PER_LINE)] + + for line in lines: + output.append("\t" + ", ".join(str(value) for value in line) + ",") + + output[-1] = output[-1][:-1] # Remove the last comma + output += ["};", ""] + + cpp_code = "\n".join(output) + + with open(f"FixedPointLUT_sin.hpp", "w", newline="\n") as file: + file.write(cpp_code) + +PRECISION = 16 +COUNT = 9 + +if __name__ == "__main__": + + parser = ArgumentParser(prog="Fixed Point LUT Generator", description="Fixed-Point number Look-Up Table generator") + parser.add_argument("-p", "--precision", type=int, default=PRECISION, choices=range(1, 65), help="The number of bits after the point (fractional bits)") + parser.add_argument("-c", "--count", type=int, default=COUNT, choices=range(1, 65), help="The base 2 log of the number of values in the look-up table (must be <= precision)") + args = parser.parse_args() + + if args.precision < args.count: + print("ERROR: invalid count ", args.count, " - can't be greater than precision (", args.precision, ")") + exit(-1) + else: + generate_sin_lut(args.precision, args.count) + exit(0) diff --git a/src/openvic-simulation/utility/BMP.cpp b/src/openvic-simulation/utility/BMP.cpp new file mode 100644 index 0000000..531870b --- /dev/null +++ b/src/openvic-simulation/utility/BMP.cpp @@ -0,0 +1,163 @@ +#include "BMP.hpp" + +#include +#include + +#include "openvic-simulation/utility/Logger.hpp" + +using namespace OpenVic; + +BMP::~BMP() { + close(); +} + +bool BMP::open(char const* filepath) { + reset(); + errno = 0; + file = fopen(filepath, "rb"); + if (file == nullptr || errno != 0) { + Logger::error("Failed to open BMP file \"", filepath, "\" (errno = ", errno, ")"); + file = nullptr; + return false; + } + return true; +} + +bool BMP::read_header() { + if (header_validated) { + Logger::error("BMP header already validated!"); + return false; + } + if (file == nullptr) { + Logger::error("Cannot read BMP header before opening a file"); + return false; + } + if (fseek(file, 0, SEEK_SET) != 0) { + Logger::error("Failed to move to the beginning of the BMP file!"); + return false; + } + if (fread(&header, sizeof(header), 1, file) != 1) { + Logger::error("Failed to read BMP header!"); + return false; + } + + header_validated = true; + + // Validate constants + static constexpr uint16_t BMP_SIGNATURE = 0x4d42; + if (header.signature != BMP_SIGNATURE) { + Logger::error("Invalid BMP signature: ", header.signature, " (must be ", BMP_SIGNATURE, ")"); + header_validated = false; + } + static constexpr uint32_t DIB_HEADER_SIZE = 40; + if (header.dib_header_size != DIB_HEADER_SIZE) { + Logger::error("Invalid BMP DIB header size: ", header.dib_header_size, " (must be ", DIB_HEADER_SIZE, ")"); + header_validated = false; + } + static constexpr uint16_t NUM_PLANES = 1; + if (header.num_planes != NUM_PLANES) { + Logger::error("Invalid BMP plane count: ", header.num_planes, " (must be ", NUM_PLANES, ")"); + header_validated = false; + } + static constexpr uint16_t COMPRESSION = 0; // Only support uncompressed BMPs + if (header.compression != COMPRESSION) { + Logger::error("Invalid BMP compression method: ", header.compression, " (must be ", COMPRESSION, ")"); + header_validated = false; + } + + // Validate sizes and dimensions + // TODO - image_size_bytes can be 0 for non-compressed BMPs + if (header.file_size != header.offset + header.image_size_bytes) { + Logger::error("Invalid BMP memory sizes: file size = ", header.file_size, " != ", header.offset + header.image_size_bytes, + " = ", header.offset, " + ", header.image_size_bytes, " = image data offset + image data size"); + header_validated = false; + } + // TODO - support negative widths (i.e. horizontal flip) + if (header.width_px <= 0) { + Logger::error("Invalid BMP width: ", header.width_px, " (must be positive)"); + header_validated = false; + } + // TODO - support negative heights (i.e. vertical flip) + if (header.height_px <= 0) { + Logger::error("Invalid BMP height: ", header.height_px, " (must be positive)"); + header_validated = false; + } + // TODO - validate x_resolution_ppm + // TODO - validate y_resolution_ppm + + // Validate colours +#define VALID_BITS_PER_PIXEL 1, 2, 4, 8, 16, 24, 32 +#define STR(x) #x + static const std::set BITS_PER_PIXEL { VALID_BITS_PER_PIXEL }; + if (!BITS_PER_PIXEL.contains(header.bits_per_pixel)) { + Logger::error("Invalid BMP bits per pixel: ", header.bits_per_pixel, " (must be one of " STR(VALID_BITS_PER_PIXEL) ")"); + header_validated = false; + } +#undef VALID_BITS_PER_PIXEL +#undef STR + static constexpr uint16_t PALETTE_BITS_PER_PIXEL_LIMIT = 8; + if (header.num_colours != 0 && header.bits_per_pixel > PALETTE_BITS_PER_PIXEL_LIMIT) { + Logger::error("Invalid BMP palette size: ", header.num_colours, " (should be 0 as bits per pixel is ", header.bits_per_pixel, " > 8)"); + header_validated = false; + } + // TODO - validate important_colours + + palette_size = header.bits_per_pixel > PALETTE_BITS_PER_PIXEL_LIMIT ? 0 + // Use header.num_colours if it's greater than 0 and at most 1 << header.bits_per_pixel + : 0 < header.num_colours && header.num_colours - 1 >> header.bits_per_pixel == 0 ? header.num_colours + : 1 << header.bits_per_pixel; + + const uint32_t expected_offset = palette_size * PALETTE_COLOUR_SIZE + sizeof(header); + if (header.offset != expected_offset) { + Logger::error("Invalid BMP image data offset: ", header.offset, " (should be ", expected_offset, ")"); + header_validated = false; + } + + return header_validated; +} + +bool BMP::read_palette() { + if (file == nullptr) { + Logger::error("Cannot read BMP palette before opening a file"); + return false; + } + if (!header_validated) { + Logger::error("Cannot read palette before BMP header is validated!"); + return false; + } + if (palette_size == 0) { + Logger::error("Cannot read BMP palette - header indicates this file doesn't have one"); + return false; + } + if (fseek(file, sizeof(header), SEEK_SET) != 0) { + Logger::error("Failed to move to the palette in the BMP file!"); + return false; + } + palette.resize(palette_size); + if (fread(palette.data(), palette_size * PALETTE_COLOUR_SIZE, 1, file) != 1) { + Logger::error("Failed to read BMP header!"); + palette.clear(); + return false; + } + return true; +} + +void BMP::close() { + if (file != nullptr) { + if (fclose(file) != 0) + Logger::error("Failed to close BMP!"); + file = nullptr; + } +} + +void BMP::reset() { + close(); + memset(&header, 0, sizeof(header)); + header_validated = false; + palette_size = 0; + palette.clear(); +} + +std::vector const& BMP::get_palette() const { + return palette; +} diff --git a/src/openvic-simulation/utility/BMP.hpp b/src/openvic-simulation/utility/BMP.hpp new file mode 100644 index 0000000..f04b41a --- /dev/null +++ b/src/openvic-simulation/utility/BMP.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include +#include + +#include "openvic-simulation/types/Colour.hpp" + +namespace OpenVic { + class BMP { +#pragma pack(push) +#pragma pack(1) + struct header_t { + uint16_t signature; // Signature: 0x4d42 (or 'B' 'M') + uint32_t file_size; // File size in bytes + uint16_t reserved1; // Not used + uint16_t reserved2; // Not used + uint32_t offset; // Offset to image data in bytes from beginning of file + uint32_t dib_header_size; // DIB header size in bytes + int32_t width_px; // Width of the image + int32_t height_px; // Height of image + uint16_t num_planes; // Number of colour planes + uint16_t bits_per_pixel; // Bits per pixel + uint32_t compression; // Compression type + uint32_t image_size_bytes; // Image size in bytes + int32_t x_resolution_ppm; // Pixels per meter + int32_t y_resolution_ppm; // Pixels per meter + uint32_t num_colours; // Number of colours + uint32_t important_colours; // Important colours + } header; +#pragma pack(pop) + + FILE* file = nullptr; + bool header_validated = false; + uint32_t palette_size = 0; + std::vector palette; + + public: + static constexpr uint32_t PALETTE_COLOUR_SIZE = sizeof(colour_t); + + BMP() = default; + ~BMP(); + + bool open(char const* filepath); + bool read_header(); + bool read_palette(); + void close(); + void reset(); + + std::vector const& get_palette() const; + }; +} diff --git a/src/openvic-simulation/utility/Logger.cpp b/src/openvic-simulation/utility/Logger.cpp new file mode 100644 index 0000000..bf9a67c --- /dev/null +++ b/src/openvic-simulation/utility/Logger.cpp @@ -0,0 +1,20 @@ +#include "Logger.hpp" + +#include + +using namespace OpenVic; + +Logger::log_func_t Logger::info_func {}; +Logger::log_queue_t Logger::info_queue {}; +Logger::log_func_t Logger::error_func {}; +Logger::log_queue_t Logger::error_queue {}; + +char const* Logger::get_filename(char const* filepath) { + if (filepath == nullptr) return nullptr; + char const* last_slash = filepath; + while (*filepath != '\0') { + if (*filepath == '\\' || *filepath == '/') last_slash = filepath + 1; + filepath++; + } + return last_slash; +} diff --git a/src/openvic-simulation/utility/Logger.hpp b/src/openvic-simulation/utility/Logger.hpp new file mode 100644 index 0000000..417aba7 --- /dev/null +++ b/src/openvic-simulation/utility/Logger.hpp @@ -0,0 +1,87 @@ +#pragma once + +#include +#include +#include +#ifdef __cpp_lib_source_location +#include +#endif + +namespace OpenVic { + +#ifndef __cpp_lib_source_location +#include + // Implementation of std::source_location for compilers that do not support it + // Note: uses non-standard extensions that are supported by Clang, GCC, and MSVC + // https://clang.llvm.org/docs/LanguageExtensions.html#source-location-builtins + // https://stackoverflow.com/a/67970107 + class source_location { + std::string _file; + int _line; + std::string _function; + + public: + source_location(std::string f, int l, std::string n) : _file(f), _line(l), _function(n) {} + static source_location current(std::string f = __builtin_FILE(), int l = __builtin_LINE(), std::string n = __builtin_FUNCTION()) { + return source_location(f, l, n); + } + + inline char const* file_name() const { return _file.c_str(); } + inline int line() const { return _line; } + inline char const* function_name() const { return _function.c_str(); } + }; +#endif + + class Logger { + using log_func_t = std::function; + using log_queue_t = std::queue; + +#ifdef __cpp_lib_source_location + using source_location = std::source_location; +#else + using source_location = OpenVic::source_location; +#endif + + static char const* get_filename(char const* filepath); + + template + struct log { + log(log_func_t log_func, log_queue_t& log_queue, Ts&&... ts, source_location const& location) { + std::stringstream stream; + stream << "\n" << get_filename(location.file_name()) << "(" + << location.line() << ") `" << location.function_name() << "`: "; + ((stream << std::forward(ts)), ...); + stream << std::endl; + log_queue.push(stream.str()); + if (log_func) { + do { + log_func(std::move(log_queue.front())); + log_queue.pop(); + } while (!log_queue.empty()); + } + } + }; + +#define LOG_FUNC(name) \ + private: \ + static log_func_t name##_func; \ + static log_queue_t name##_queue; \ + public: \ + static void set_##name##_func(log_func_t log_func) { \ + name##_func = log_func; \ + } \ + template \ + struct name { \ + name(Ts&&... ts, source_location const& location = source_location::current()) { \ + log{ name##_func, name##_queue, std::forward(ts)..., location }; \ + } \ + }; \ + template \ + name(Ts&&...) -> name; + + LOG_FUNC(info) + LOG_FUNC(error) + +#undef LOG_FUNC + }; +} diff --git a/src/openvic-simulation/utility/NumberUtils.hpp b/src/openvic-simulation/utility/NumberUtils.hpp new file mode 100644 index 0000000..6211772 --- /dev/null +++ b/src/openvic-simulation/utility/NumberUtils.hpp @@ -0,0 +1,27 @@ +#include + +namespace OpenVic::NumberUtils { + constexpr int64_t round_to_int64(float num) { + return (num > 0.0f) ? (num + 0.5f) : (num - 0.5f); + } + + constexpr int64_t round_to_int64(double num) { + return (num > 0.0) ? (num + 0.5) : (num - 0.5); + } + + constexpr uint64_t pow(uint64_t base, size_t exponent) { + uint64_t ret = 1; + while (exponent-- > 0) { + ret *= base; + } + return ret; + } + + constexpr int64_t pow(int64_t base, size_t exponent) { + int64_t ret = 1; + while (exponent-- > 0) { + ret *= base; + } + return ret; + } +} diff --git a/src/openvic-simulation/utility/StringUtils.hpp b/src/openvic-simulation/utility/StringUtils.hpp new file mode 100644 index 0000000..5d6001c --- /dev/null +++ b/src/openvic-simulation/utility/StringUtils.hpp @@ -0,0 +1,127 @@ +#include +#include +#include + +namespace OpenVic::StringUtils { + /* The constexpr function 'string_to_uint64' will convert a string into a uint64_t integer value. + * The function takes four parameters: the input string (as a pair of pointers marking the start and + * end of the string), a bool pointer for reporting success, and the base for numerical conversion. + * The base parameter defaults to 10 (decimal), but it can be any value between 2 and 36. If the base + * given is 0, it will be set to 16 if the string starts with "0x" or "0X", otherwise 8 if the string + * still starts with "0", otherwise 10. The success bool pointer parameter is used to report whether + * or not conversion was successful. It can be nullptr if this information is not needed. + */ + constexpr uint64_t string_to_uint64(char const* str, const char* const end, bool* successful = nullptr, int base = 10) { + if (successful != nullptr) *successful = false; + + // Base value should be between 2 and 36. If it's not, return 0 as an invalid case. + if (str == nullptr || end <= str || base < 0 || base == 1 || base > 36) + return 0; + + // The result of the conversion will be stored in this variable. + uint64_t result = 0; + + // If base is zero, base is determined by the string prefix. + if (base == 0) { + if (*str == '0') { + if (str + 1 != end && (str[1] == 'x' || str[1] == 'X')) { + base = 16; // Hexadecimal. + str += 2; // Skip '0x' or '0X' + if (str == end) return 0; + } else { + base = 8; // Octal. + } + } else { + base = 10; // Decimal. + } + } else if (base == 16) { + // If base is 16 and string starts with '0x' or '0X', skip these characters. + if (*str == '0' && str + 1 != end && (str[1] == 'x' || str[1] == 'X')) { + str += 2; + if (str == end) return 0; + } + } + + // Convert the number in the string. + for (; str != end; ++str) { + int digit; + if (*str >= '0' && *str <= '9') { + digit = *str - '0'; // Calculate digit value for '0'-'9'. + } else if (*str >= 'a' && *str <= 'z') { + digit = *str - 'a' + 10; // Calculate digit value for 'a'-'z'. + } else if (*str >= 'A' && *str <= 'Z') { + digit = *str - 'A' + 10; // Calculate digit value for 'A'-'Z'. + } else { + break; // Stop conversion if current character is not a digit. + } + + if (digit >= base) { + break; // Stop conversion if current digit is greater than or equal to the base. + } + + // Check for overflow on multiplication + if (result > std::numeric_limits::max() / base) { + return std::numeric_limits::max(); + } + + result *= base; + + // Check for overflow on addition + if (result > std::numeric_limits::max() - digit) { + return std::numeric_limits::max(); + } + + result += digit; + } + + // If successful is not null and the entire string was parsed, + // set *successful to true (if not it is already false). + if (successful != nullptr && str == end) *successful = true; + + return result; + } + + constexpr uint64_t string_to_uint64(char const* str, size_t length, bool* successful = nullptr, int base = 10) { + return string_to_uint64(str, str + length, successful, base); + } + + inline uint64_t string_to_uint64(const std::string_view str, bool* successful = nullptr, int base = 10) { + return string_to_uint64(str.data(), str.length(), successful, base); + } + + constexpr int64_t string_to_int64(char const* str, const char* const end, bool* successful = nullptr, int base = 10) { + if (successful != nullptr) *successful = false; + + if (str == nullptr || end <= str) return 0; + + // This flag will be set if the number is negative. + bool is_negative = false; + + // Check if there is a sign character. + if (*str == '+' || *str == '-') { + if (*str == '-') + is_negative = true; + ++str; + if (str == end) return 0; + } + + const uint64_t result = string_to_uint64(str, end, successful, base); + if (!is_negative) { + if (result >= std::numeric_limits::max()) + return std::numeric_limits::max(); + return result; + } else { + if (result > std::numeric_limits::max()) + return std::numeric_limits::min(); + return -result; + } + } + + constexpr int64_t string_to_int64(char const* str, size_t length, bool* successful = nullptr, int base = 10) { + return string_to_int64(str, str + length, successful, base); + } + + inline int64_t string_to_int64(const std::string_view str, bool* successful = nullptr, int base = 10) { + return string_to_int64(str.data(), str.length(), successful, base); + } +} diff --git a/src/openvic/GameAdvancementHook.cpp b/src/openvic/GameAdvancementHook.cpp deleted file mode 100644 index ac16158..0000000 --- a/src/openvic/GameAdvancementHook.cpp +++ /dev/null @@ -1,76 +0,0 @@ -#include "GameAdvancementHook.hpp" - -using namespace OpenVic; - -const std::vector GameAdvancementHook::GAME_SPEEDS = { - std::chrono::milliseconds { 4000 }, - std::chrono::milliseconds { 3000 }, - std::chrono::milliseconds { 2000 }, - std::chrono::milliseconds { 1000 }, - std::chrono::milliseconds { 100 }, - std::chrono::milliseconds { 1 } -}; - -GameAdvancementHook::GameAdvancementHook(AdvancementFunction tickFunction, - RefreshFunction updateFunction, bool startPaused, speed_t startingSpeed) - : triggerFunction { tickFunction }, - refreshFunction { updateFunction }, - isPaused { startPaused } { - lastPolledTime = std::chrono::high_resolution_clock::now(); - setSimulationSpeed(startingSpeed); -} - -void GameAdvancementHook::setSimulationSpeed(speed_t speed) { - if (speed < 0) - currentSpeed = 0; - else if (speed >= GAME_SPEEDS.size()) - currentSpeed = GAME_SPEEDS.size() - 1; - else - currentSpeed = speed; -} - -GameAdvancementHook::speed_t GameAdvancementHook::getSimulationSpeed() const { - return currentSpeed; -} - -void GameAdvancementHook::increaseSimulationSpeed() { - setSimulationSpeed(currentSpeed + 1); -} - -void GameAdvancementHook::decreaseSimulationSpeed() { - setSimulationSpeed(currentSpeed - 1); -} - -bool GameAdvancementHook::canIncreaseSimulationSpeed() const { - return currentSpeed + 1 < GAME_SPEEDS.size(); -} - -bool GameAdvancementHook::canDecreaseSimulationSpeed() const { - return currentSpeed > 0; -} - -GameAdvancementHook& GameAdvancementHook::operator++() { - increaseSimulationSpeed(); - return *this; -}; - -GameAdvancementHook& GameAdvancementHook::operator--() { - decreaseSimulationSpeed(); - return *this; -}; - -void GameAdvancementHook::conditionallyAdvanceGame() { - if (!isPaused) { - time_point_t currentTime = std::chrono::high_resolution_clock::now(); - if (std::chrono::duration_cast(currentTime - lastPolledTime) >= GAME_SPEEDS[currentSpeed]) { - lastPolledTime = currentTime; - if (triggerFunction) triggerFunction(); - } - } - if (refreshFunction) refreshFunction(); -} - -void GameAdvancementHook::reset() { - isPaused = true; - currentSpeed = 0; -} diff --git a/src/openvic/GameAdvancementHook.hpp b/src/openvic/GameAdvancementHook.hpp deleted file mode 100644 index 59e43a4..0000000 --- a/src/openvic/GameAdvancementHook.hpp +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace OpenVic { - // Conditionally advances game with provided behaviour - // Class governs game speed and pause state - class GameAdvancementHook { - public: - using AdvancementFunction = std::function; - using RefreshFunction = std::function; - using speed_t = int8_t; - - // Minimum number of miliseconds before the simulation advances - static const std::vector GAME_SPEEDS; - - private: - using time_point_t = std::chrono::time_point; - - time_point_t lastPolledTime; - // A function pointer that advances the simulation, intended to be a capturing lambda or something similar. May need to be reworked later - AdvancementFunction triggerFunction; - RefreshFunction refreshFunction; - speed_t currentSpeed; - - public: - bool isPaused; - - GameAdvancementHook(AdvancementFunction tickFunction, RefreshFunction updateFunction, bool startPaused = true, speed_t startingSpeed = 0); - - void setSimulationSpeed(speed_t speed); - speed_t getSimulationSpeed() const; - void increaseSimulationSpeed(); - void decreaseSimulationSpeed(); - bool canIncreaseSimulationSpeed() const; - bool canDecreaseSimulationSpeed() const; - GameAdvancementHook& operator++(); - GameAdvancementHook& operator--(); - void conditionallyAdvanceGame(); - void reset(); - }; -} \ No newline at end of file diff --git a/src/openvic/GameManager.cpp b/src/openvic/GameManager.cpp deleted file mode 100644 index 052b814..0000000 --- a/src/openvic/GameManager.cpp +++ /dev/null @@ -1,132 +0,0 @@ -#include "GameManager.hpp" - -#include "openvic/utility/Logger.hpp" - -using namespace OpenVic; - -GameManager::GameManager(state_updated_func_t state_updated_callback) - : clock { [this]() { tick(); }, [this]() { update_state(); } }, - state_updated { state_updated_callback } {} - -void GameManager::set_needs_update() { - needs_update = true; -} - -void GameManager::update_state() { - if (needs_update) { - Logger::info("Update: ", today); - map.update_state(today); - if (state_updated) state_updated(); - needs_update = false; - } -} - -/* REQUIREMENTS: - * SS-98, SS-101 - */ -void GameManager::tick() { - today++; - Logger::info("Tick: ", today); - map.tick(today); - set_needs_update(); -} - -bool GameManager::setup() { - session_start = time(nullptr); - clock.reset(); - today = { 1836 }; - good_manager.reset_to_defaults(); - bool ret = map.setup(good_manager, building_manager, pop_manager); - set_needs_update(); - return ret; -} - -Date const& GameManager::get_today() const { - return today; -} - -bool GameManager::expand_building(Province::index_t province_index, const std::string_view building_type_identifier) { - set_needs_update(); - Province* province = map.get_province_by_index(province_index); - if (province == nullptr) { - Logger::error("Invalid province index ", province_index, " while trying to expand building ", building_type_identifier); - return false; - } - return province->expand_building(building_type_identifier); -} - -bool GameManager::load_hardcoded_defines() { - bool ret = true; - - static constexpr colour_t LOW_ALPHA_VALUE = float_to_alpha_value(0.4f); - static constexpr colour_t HIGH_ALPHA_VALUE = float_to_alpha_value(0.7f); - using mapmode_t = std::pair; - const std::vector mapmodes { - { "mapmode_terrain", - [](Map const&, Province const& province) -> colour_t { - return LOW_ALPHA_VALUE | (province.is_water() ? 0x4287F5 : 0x0D7017); - } }, - { "mapmode_province", - [](Map const&, Province const& province) -> colour_t { - return HIGH_ALPHA_VALUE | province.get_colour(); - } }, - { "mapmode_region", - [](Map const&, Province const& province) -> colour_t { - Region const* region = province.get_region(); - if (region != nullptr) return HIGH_ALPHA_VALUE | region->get_colour(); - return NULL_COLOUR; - } }, - { "mapmode_index", - [](Map const& map, Province const& province) -> colour_t { - const colour_t f = fraction_to_colour_byte(province.get_index(), map.get_province_count() + 1); - return HIGH_ALPHA_VALUE | (f << 16) | (f << 8) | f; - } }, - { "mapmode_rgo", - [](Map const& map, Province const& province) -> colour_t { - Good const* rgo = province.get_rgo(); - if (rgo != nullptr) return HIGH_ALPHA_VALUE | rgo->get_colour(); - return NULL_COLOUR; - } }, - { "mapmode_infrastructure", - [](Map const& map, Province const& province) -> colour_t { - Building const* railroad = province.get_building_by_identifier("building_railroad"); - if (railroad != nullptr) { - colour_t val = fraction_to_colour_byte(railroad->get_level(), railroad->get_type().get_max_level() + 1, 0.5f, 1.0f); - switch (railroad->get_expansion_state()) { - case Building::ExpansionState::CannotExpand: val <<= 16; break; - case Building::ExpansionState::CanExpand: break; - default: val <<= 8; break; - } - return HIGH_ALPHA_VALUE | val; - } - return NULL_COLOUR; - } }, - { "mapmode_population", - [](Map const& map, Province const& province) -> colour_t { - return HIGH_ALPHA_VALUE | (fraction_to_colour_byte(province.get_total_population(), map.get_highest_province_population() + 1, 0.1f, 1.0f) << 8); - } }, - { "mapmode_culture", - [](Map const& map, Province const& province) -> colour_t { - HasIdentifierAndColour const* largest = get_largest_item(province.get_culture_distribution()).first; - return largest != nullptr ? HIGH_ALPHA_VALUE | largest->get_colour() : NULL_COLOUR; - } }, - { "mapmode_religion", - [](Map const& map, Province const& province) -> colour_t { - HasIdentifierAndColour const* largest = get_largest_item(province.get_religion_distribution()).first; - return largest != nullptr ? HIGH_ALPHA_VALUE | largest->get_colour() : NULL_COLOUR; - } } - }; - for (mapmode_t const& mapmode : mapmodes) - ret &= map.add_mapmode(mapmode.first, mapmode.second); - map.lock_mapmodes(); - - using building_type_t = std::tuple; - const std::vector building_types { - { "building_fort", 4, 8 }, { "building_naval_base", 6, 15 }, { "building_railroad", 5, 10 } - }; - for (building_type_t const& type : building_types) - ret &= building_manager.add_building_type(std::get<0>(type), std::get<1>(type), std::get<2>(type)); - building_manager.lock_building_types(); - - return ret; -} diff --git a/src/openvic/GameManager.hpp b/src/openvic/GameManager.hpp deleted file mode 100644 index af2ec4f..0000000 --- a/src/openvic/GameManager.hpp +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -#include "openvic/GameAdvancementHook.hpp" -#include "openvic/economy/Good.hpp" -#include "openvic/map/Map.hpp" - -namespace OpenVic { - struct GameManager { - using state_updated_func_t = std::function; - - Map map; - BuildingManager building_manager; - GoodManager good_manager; - PopManager pop_manager; - GameAdvancementHook clock; - - private: - time_t session_start; /* SS-54, as well as allowing time-tracking */ - Date today; - state_updated_func_t state_updated; - bool needs_update; - - void set_needs_update(); - void update_state(); - void tick(); - - public: - GameManager(state_updated_func_t state_updated_callback); - - bool setup(); - - Date const& get_today() const; - bool expand_building(Province::index_t province_index, const std::string_view building_type_identifier); - - /* Hardcoded data for defining things for which parsing from files has - * not been implemented, currently mapmodes and building types. - */ - bool load_hardcoded_defines(); - }; -} diff --git a/src/openvic/dataloader/Dataloader.cpp b/src/openvic/dataloader/Dataloader.cpp deleted file mode 100644 index 55994d3..0000000 --- a/src/openvic/dataloader/Dataloader.cpp +++ /dev/null @@ -1,288 +0,0 @@ -#include "Dataloader.hpp" - -#include "openvic/GameManager.hpp" -#include "openvic/utility/Logger.hpp" - -#include -#include -#include - -using namespace OpenVic; -using namespace OpenVic::NodeTools; -using namespace ovdl; - -bool Dataloader::set_roots(std::vector new_roots) { - if (!roots.empty()) { - Logger::error("Overriding existing dataloader roots!"); - roots.clear(); - } - bool ret = true; - for (std::reverse_iterator::const_iterator> it = new_roots.crbegin(); it != new_roots.crend(); ++it) { - if (std::find(roots.begin(), roots.end(), *it) == roots.end()) { - if (std::filesystem::is_directory(*it)) { - Logger::info("Adding dataloader root: ", *it); - roots.push_back(*it); - } else { - Logger::error("Invalid dataloader root (must be an existing directory): ", *it); - ret = false; - } - } else { - Logger::error("Duplicate dataloader root: ", *it); - ret = false; - } - } - if (roots.empty()) { - Logger::error("Dataloader has no roots after attempting to add ", new_roots.size()); - ret = false; - } - return ret; -} - -std::filesystem::path Dataloader::lookup_file(std::filesystem::path const& path) const { - for (std::filesystem::path const& root : roots) { - const std::filesystem::path composed = root / path; - if (std::filesystem::is_regular_file(composed)) { - return composed; - } - } - Logger::error("Lookup for ", path, " failed!"); - return {}; -} - -const std::filesystem::path Dataloader::TXT = ".txt"; - -static bool contains_file_with_name(std::vector const& paths, - std::filesystem::path const& name) { - - for (std::filesystem::path const& path : paths) { - if (path.filename() == name) return true; - } - return false; -} - -std::vector Dataloader::lookup_files_in_dir(std::filesystem::path const& path, - std::filesystem::path const* extension) const { - - std::vector ret; - for (std::filesystem::path const& root : roots) { - const std::filesystem::path composed = root / path; - std::error_code ec; - for (std::filesystem::directory_entry const& entry : std::filesystem::directory_iterator { composed, ec }) { - if (entry.is_regular_file()) { - const std::filesystem::path file = entry; - if (extension == nullptr || file.extension() == *extension) { - if (!contains_file_with_name(ret, file.filename())) { - ret.push_back(file); - } - } - } - } - } - return ret; -} - -bool Dataloader::apply_to_files_in_dir(std::filesystem::path const& path, - std::function callback, - std::filesystem::path const* extension) const { - - bool ret = true; - for (std::filesystem::path const& file : lookup_files_in_dir(path, extension)) { - ret &= callback(file); - } - return ret; -} - -template Parser, bool(Parser::*parse_func)()> -static Parser _run_ovdl_parser(std::filesystem::path const& path) { - Parser parser; - std::string buffer; - auto error_log_stream = ovdl::detail::CallbackStream { - [](void const* s, std::streamsize n, void* user_data) -> std::streamsize { - if (s != nullptr && n > 0 && user_data != nullptr) { - static_cast(user_data)->append(static_cast(s), n); - return n; - } else { - Logger::error("Invalid input to parser error log callback: ", s, " / ", n, " / ", user_data); - return 0; - } - }, &buffer - }; - parser.set_error_log_to(error_log_stream); - parser.load_from_file(path); - if (!buffer.empty()) { - Logger::error("Parser load errors:\n\n", buffer, "\n"); - buffer.clear(); - } - if (parser.has_fatal_error() || parser.has_error()) { - Logger::error("Parser errors while loading ", path); - return parser; - } - if (!(parser.*parse_func)()) { - Logger::error("Parse function returned false!"); - } - if (!buffer.empty()) { - Logger::error("Parser parse errors:\n\n", buffer, "\n"); - buffer.clear(); - } - if (parser.has_fatal_error() || parser.has_error()) { - Logger::error("Parser errors while parsing ", path); - } - return parser; -} - -static v2script::Parser _parse_defines(std::filesystem::path const& path) { - return _run_ovdl_parser(path); -} - -static csv::Windows1252Parser _parse_csv(std::filesystem::path const& path) { - return _run_ovdl_parser(path); -} - -bool Dataloader::_load_pop_types(PopManager& pop_manager, std::filesystem::path const& pop_type_directory) const { - const bool ret = apply_to_files_in_dir(pop_type_directory, - [&pop_manager](std::filesystem::path const& file) -> bool { - return pop_manager.load_pop_type_file(file, _parse_defines(file).get_file_node()); - } - ); - if (!ret) { - Logger::error("Failed to load pop types!"); - } - pop_manager.lock_pop_types(); - return ret; -} - -bool Dataloader::_load_map_dir(Map& map, std::filesystem::path const& map_directory) const { - static const std::filesystem::path defaults_filename = "default.map"; - static const std::string default_definitions = "definition.csv"; - static const std::string default_provinces = "provinces.bmp"; - static const std::string default_positions = "positions.txt"; - static const std::string default_terrain = "terrain.bmp"; - static const std::string default_rivers = "rivers.bmp"; - static const std::string default_terrain_definition = "terrain.txt"; - static const std::string default_tree_definition = "trees.txt"; - static const std::string default_continent = "continent.txt"; - static const std::string default_adjacencies = "adjacencies.csv"; - static const std::string default_region = "region.txt"; - static const std::string default_region_sea = "region_sea.txt"; - static const std::string default_province_flag_sprite = "province_flag_sprites"; - - const v2script::Parser parser = _parse_defines(lookup_file(map_directory / defaults_filename)); - - std::vector water_province_identifiers; - -#define APPLY_TO_MAP_PATHS(F) \ - F(definitions) F(provinces) F(positions) F(terrain) F(rivers) \ - F(terrain_definition) F(tree_definition) F(continent) F(adjacencies) \ - F(region) F(region_sea) F(province_flag_sprite) - -#define MAP_PATH_VAR(X) std::string_view X = default_##X; - APPLY_TO_MAP_PATHS(MAP_PATH_VAR) -#undef MAP_PATH_VAR - - bool ret = expect_dictionary_keys( - "max_provinces", ONE_EXACTLY, - expect_uint( - [&map](uint64_t val) -> bool { - if (Province::NULL_INDEX < val && val <= Province::MAX_INDEX) { - return map.set_max_provinces(val); - } - Logger::error("Invalid max province count ", val, " (out of valid range ", Province::NULL_INDEX, " < max_provinces <= ", Province::MAX_INDEX, ")"); - return false; - } - ), - "sea_starts", ONE_EXACTLY, - expect_list_reserve_length( - water_province_identifiers, - expect_identifier( - [&water_province_identifiers](std::string_view identifier) -> bool { - water_province_identifiers.push_back(identifier); - return true; - } - ) - ), - -#define MAP_PATH_DICT_ENTRY(X) \ - #X, ONE_EXACTLY, expect_string(assign_variable_callback(X)), - APPLY_TO_MAP_PATHS(MAP_PATH_DICT_ENTRY) -#undef MAP_PATH_DICT_ENTRY - -#undef APPLY_TO_MAP_PATHS - - "border_heights", ZERO_OR_ONE, success_callback, - "terrain_sheet_heights", ZERO_OR_ONE, success_callback, - "tree", ZERO_OR_ONE, success_callback, - "border_cutoff", ZERO_OR_ONE, success_callback - )(parser.get_file_node()); - - if (!ret) { - Logger::error("Failed to load map default file!"); - } - - if (!map.load_province_definitions(_parse_csv(lookup_file(map_directory / definitions)).get_lines())) { - Logger::error("Failed to load province definitions file!"); - ret = false; - } - - if (!map.set_water_province_list(water_province_identifiers)) { - Logger::error("Failed to set water provinces!"); - ret = false; - } - map.lock_water_provinces(); - - return ret; -} - -bool Dataloader::load_defines(GameManager& game_manager) const { - static const std::filesystem::path good_file = "common/goods.txt"; - static const std::filesystem::path pop_type_directory = "poptypes"; - static const std::filesystem::path graphical_culture_type_file = "common/graphicalculturetype.txt"; - static const std::filesystem::path culture_file = "common/cultures.txt"; - static const std::filesystem::path religion_file = "common/religion.txt"; - static const std::filesystem::path map_directory = "map"; - - bool ret = true; - - if (!game_manager.good_manager.load_good_file(_parse_defines(lookup_file(good_file)).get_file_node())) { - Logger::error("Failed to load goods!"); - ret = false; - } - if (!_load_pop_types(game_manager.pop_manager, pop_type_directory)) { - Logger::error("Failed to load pop types!"); - ret = false; - } - if (!game_manager.pop_manager.culture_manager.load_graphical_culture_type_file(_parse_defines(lookup_file(graphical_culture_type_file)).get_file_node())) { - Logger::error("Failed to load graphical culture types!"); - ret = false; - } - if (!game_manager.pop_manager.culture_manager.load_culture_file(_parse_defines(lookup_file(culture_file)).get_file_node())) { - Logger::error("Failed to load cultures!"); - ret = false; - } - if (!game_manager.pop_manager.religion_manager.load_religion_file(_parse_defines(lookup_file(religion_file)).get_file_node())) { - Logger::error("Failed to load religions!"); - ret = false; - } - if (!_load_map_dir(game_manager.map, map_directory)) { - Logger::error("Failed to load map!"); - ret = false; - } - - return ret; -} - -bool Dataloader::load_pop_history(GameManager& game_manager, std::filesystem::path const& path) const { - return apply_to_files_in_dir(path, - [&game_manager](std::filesystem::path const& file) -> bool { - return expect_dictionary( - [&game_manager](std::string_view province_key, ast::NodeCPtr province_node) -> bool { - Province* province = game_manager.map.get_province_by_identifier(province_key); - if (province == nullptr) { - Logger::error("Invalid province id: ", province_key); - return false; - } - return province->load_pop_list(game_manager.pop_manager, province_node); - } - )(_parse_defines(file).get_file_node()); - } - ); -} diff --git a/src/openvic/dataloader/Dataloader.hpp b/src/openvic/dataloader/Dataloader.hpp deleted file mode 100644 index f723803..0000000 --- a/src/openvic/dataloader/Dataloader.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace OpenVic { - struct GameManager; - struct PopManager; - struct Map; - - class Dataloader { - std::vector roots; - - bool _load_pop_types(PopManager& pop_manager, std::filesystem::path const& pop_type_directory) const; - bool _load_map_dir(Map& map, std::filesystem::path const& map_directory) const; - - public: - Dataloader() = default; - - /* In reverse-load order, so base defines first and final loaded mod last */ - bool set_roots(std::vector new_roots); - - std::filesystem::path lookup_file(std::filesystem::path const& path) const; - static const std::filesystem::path TXT; - std::vector lookup_files_in_dir(std::filesystem::path const& path, - std::filesystem::path const* extension = &TXT) const; - bool apply_to_files_in_dir(std::filesystem::path const& path, - std::function callback, - std::filesystem::path const* extension = &TXT) const; - - bool load_defines(GameManager& game_manager) const; - bool load_pop_history(GameManager& game_manager, std::filesystem::path const& path) const; - }; -} diff --git a/src/openvic/dataloader/NodeTools.cpp b/src/openvic/dataloader/NodeTools.cpp deleted file mode 100644 index 499527f..0000000 --- a/src/openvic/dataloader/NodeTools.cpp +++ /dev/null @@ -1,256 +0,0 @@ -#include "NodeTools.hpp" - -#include - -using namespace OpenVic; -using namespace OpenVic::NodeTools; - -template -static node_callback_t _expect_type(std::function callback) { - return [callback](ast::NodeCPtr node) -> bool { - if (node != nullptr) { - T const* cast_node = node->cast_to(); - if (cast_node != nullptr) { - return callback(*cast_node); - } - Logger::error("Invalid node type ", node->get_type(), " when expecting ", T::get_type_static()); - } else { - Logger::error("Null node when expecting ", T::get_type_static()); - } - return false; - }; -} - -template -requires(std::derived_from) -static std::function abstract_string_node_callback(std::function callback) { - return [callback](T const& node) -> bool { - return callback(node._name); - }; -} - -node_callback_t NodeTools::expect_identifier(std::function callback) { - return _expect_type(abstract_string_node_callback(callback)); -} - -node_callback_t NodeTools::expect_string(std::function callback) { - return _expect_type(abstract_string_node_callback(callback)); -} - -node_callback_t NodeTools::expect_identifier_or_string(std::function callback) { - return [callback](ast::NodeCPtr node) -> bool { - if (node != nullptr) { - ast::AbstractStringNode const* cast_node = node->cast_to(); - if (cast_node == nullptr) { - cast_node = node->cast_to(); - } - if (cast_node != nullptr) { - return abstract_string_node_callback(callback)(*cast_node); - } - Logger::error("Invalid node type ", node->get_type(), " when expecting ", ast::IdentifierNode::get_type_static(), " or ", ast::StringNode::get_type_static()); - } else { - Logger::error("Null node when expecting ", ast::IdentifierNode::get_type_static(), " or ", ast::StringNode::get_type_static()); - } - return false; - }; -} - -node_callback_t NodeTools::expect_bool(std::function callback) { - return expect_identifier( - [callback](std::string_view identifier) -> bool { - if (identifier == "yes") { - return callback(true); - } else if (identifier == "no") { - return callback(false); - } - Logger::error("Invalid bool identifier text: ", identifier); - return false; - } - ); -} - -node_callback_t NodeTools::expect_int(std::function callback) { - return expect_identifier( - [callback](std::string_view identifier) -> bool { - bool successful = false; - const int64_t val = StringUtils::string_to_int64(identifier, &successful, 10); - if (successful) { - return callback(val); - } - Logger::error("Invalid int identifier text: ", identifier); - return false; - } - ); -} - -node_callback_t NodeTools::expect_uint(std::function callback) { - return expect_identifier( - [callback](std::string_view identifier) -> bool { - bool successful = false; - const uint64_t val = StringUtils::string_to_uint64(identifier, &successful, 10); - if (successful) { - return callback(val); - } - Logger::error("Invalid uint identifier text: ", identifier); - return false; - } - ); -} - -node_callback_t NodeTools::expect_fixed_point(std::function callback) { - return expect_identifier( - [callback](std::string_view identifier) -> bool { - bool successful = false; - const FP val = FP::parse(identifier.data(), identifier.length(), &successful); - if (successful) { - return callback(val); - } - Logger::error("Invalid fixed point identifier text: ", identifier); - return false; - } - ); -} - -node_callback_t NodeTools::expect_colour(std::function callback) { - return [callback](ast::NodeCPtr node) -> bool { - colour_t col = NULL_COLOUR; - uint32_t components = 0; - bool ret = expect_list_of_length(3, - expect_fixed_point( - [&col, &components](FP val) -> bool { - components++; - col <<= 8; - if (val < 0 || val > 255) { - Logger::error("Invalid colour component: ", val); - return false; - } else { - if (val <= 1) val *= 255; - col |= val.to_int32_t(); - return true; - } - } - ) - )(node); - ret &= callback(col << 8 * (3 - components)); - return ret; - }; -} - -node_callback_t NodeTools::expect_date(std::function callback) { - return expect_identifier( - [callback](std::string_view identifier) -> bool { - bool successful = false; - const Date date = Date::from_string(identifier, &successful); - if (successful) { - return callback(date); - } - Logger::error("Invalid date identifier text: ", identifier); - return false; - } - ); -} - -node_callback_t NodeTools::expect_assign(key_value_callback_t callback) { - return _expect_type( - [callback](ast::AssignNode const& assign_node) -> bool { - return callback(assign_node._name, assign_node._initializer.get()); - } - ); -} - -node_callback_t NodeTools::expect_list_and_length(length_callback_t length_callback, node_callback_t callback) { - return _expect_type( - [length_callback, callback](ast::AbstractListNode const& list_node) -> bool { - std::vector const& list = list_node._statements; - bool ret = true; - size_t size = length_callback(list.size()); - if (size > list.size()) { - Logger::error("Trying to read more values than the list contains: ", size, " > ", list.size()); - size = list.size(); - ret = false; - } - std::for_each(list.begin(), list.begin() + size, - [callback, &ret](ast::NodeUPtr const& sub_node) -> void { - ret &= callback(sub_node.get()); - } - ); - return ret; - } - ); -} - -node_callback_t NodeTools::expect_list_of_length(size_t length, node_callback_t callback) { - return [length, callback](ast::NodeCPtr node) -> bool { - bool ret = true; - ret &= expect_list_and_length( - [length, &ret](size_t size) -> size_t { - if (size != length) { - Logger::error("List length ", size, " does not match expected length ", length); - ret = false; - if (length < size) return length; - } - return size; - }, - callback - )(node); - return ret; - }; -} - -node_callback_t NodeTools::expect_list(node_callback_t callback) { - return expect_list_and_length(default_length_callback, callback); -} - -node_callback_t NodeTools::expect_dictionary_and_length(length_callback_t length_callback, key_value_callback_t callback) { - return expect_list_and_length(length_callback, expect_assign(callback)); -} - -node_callback_t NodeTools::expect_dictionary(key_value_callback_t callback) { - return expect_dictionary_and_length(default_length_callback, callback); -} - -node_callback_t NodeTools::_expect_dictionary_keys_and_length(length_callback_t length_callback, bool allow_other_keys, key_map_t&& key_map) { - return [length_callback, allow_other_keys, key_map = std::move(key_map)](ast::NodeCPtr node) mutable -> bool { - bool ret = expect_dictionary_and_length( - length_callback, - [&key_map, allow_other_keys](std::string_view key, ast::NodeCPtr value) -> bool { - const key_map_t::iterator it = key_map.find(key); - if (it == key_map.end()) { - if (allow_other_keys) return true; - Logger::error("Invalid dictionary key: ", key); - return false; - } - dictionary_entry_t& entry = it->second; - if (++entry.count > 1 && !entry.can_repeat()) { - Logger::error("Invalid repeat of dictionary key: ", key); - return false; - } - return entry.callback(value); - } - )(node); - for (key_map_t::value_type const& key_entry : key_map) { - dictionary_entry_t const& entry = key_entry.second; - if (entry.must_appear() && entry.count < 1) { - Logger::error("Mandatory dictionary key not present: ", key_entry.first); - ret = false; - } - } - return ret; - }; -} - -node_callback_t NodeTools::name_list_callback(std::vector& list) { - return expect_list_reserve_length( - list, - expect_identifier_or_string( - [&list](std::string_view str) -> bool { - if (!str.empty()) { - list.push_back(std::string { str }); - return true; - } - Logger::error("Empty identifier or string"); - return false; - } - ) - ); -} diff --git a/src/openvic/dataloader/NodeTools.hpp b/src/openvic/dataloader/NodeTools.hpp deleted file mode 100644 index daea8ce..0000000 --- a/src/openvic/dataloader/NodeTools.hpp +++ /dev/null @@ -1,161 +0,0 @@ -#pragma once - -#include - -#include "openvic/types/Colour.hpp" -#include "openvic/types/Date.hpp" -#include "openvic/types/fixed_point/FP.hpp" - -#include - -namespace OpenVic { - namespace ast = ovdl::v2script::ast; - - namespace NodeTools { - - using node_callback_t = std::function; - constexpr bool success_callback(ast::NodeCPtr) { return true; } - - using key_value_callback_t = std::function; - - node_callback_t expect_identifier(std::function callback); - node_callback_t expect_string(std::function callback); - node_callback_t expect_identifier_or_string(std::function callback); - node_callback_t expect_bool(std::function callback); - node_callback_t expect_int(std::function callback); - node_callback_t expect_uint(std::function callback); - node_callback_t expect_fixed_point(std::function callback); - node_callback_t expect_colour(std::function callback); - node_callback_t expect_date(std::function callback); - node_callback_t expect_assign(key_value_callback_t callback); - - using length_callback_t = std::function; - constexpr size_t default_length_callback(size_t size) { return size; }; - - node_callback_t expect_list_and_length(length_callback_t length_callback, node_callback_t callback); - node_callback_t expect_list_of_length(size_t length, node_callback_t callback); - node_callback_t expect_list(node_callback_t callback); - - node_callback_t expect_dictionary_and_length(length_callback_t length_callback, key_value_callback_t callback); - node_callback_t expect_dictionary(key_value_callback_t callback); - - struct dictionary_entry_t { - const enum class expected_count_t : uint8_t { - _MUST_APPEAR = 0b01, - _CAN_REPEAT = 0b10, - - ZERO_OR_ONE = 0, - ONE_EXACTLY = _MUST_APPEAR, - ZERO_OR_MORE = _CAN_REPEAT, - ONE_OR_MORE = _MUST_APPEAR | _CAN_REPEAT - } expected_count; - const node_callback_t callback; - size_t count; - - dictionary_entry_t(expected_count_t new_expected_count, node_callback_t new_callback) - : expected_count { new_expected_count }, callback { new_callback }, count { 0 } {} - - constexpr bool must_appear() const { - return static_cast(expected_count) & static_cast(expected_count_t::_MUST_APPEAR); - } - constexpr bool can_repeat() const { - return static_cast(expected_count) & static_cast(expected_count_t::_CAN_REPEAT); - } - }; - using enum dictionary_entry_t::expected_count_t; - using key_map_t = std::map>; - - constexpr struct allow_other_keys_t {} ALLOW_OTHER_KEYS; - - node_callback_t _expect_dictionary_keys_and_length(length_callback_t length_callback, bool allow_other_keys, key_map_t&& key_map); - - template - node_callback_t _expect_dictionary_keys_and_length(length_callback_t length_callback, - bool allow_other_keys, key_map_t&& key_map, - const std::string_view key, dictionary_entry_t::expected_count_t expected_count, node_callback_t callback, - Args... args) { - if (key_map.find(key) == key_map.end()) { - key_map.emplace(key, dictionary_entry_t { expected_count, callback }); - } else { - Logger::error("Duplicate expected dictionary key: ", key); - } - return _expect_dictionary_keys_and_length(length_callback, allow_other_keys, std::move(key_map), args...); - } - - template - node_callback_t expect_dictionary_keys_and_length(length_callback_t length_callback, - const std::string_view key, dictionary_entry_t::expected_count_t expected_count, node_callback_t callback, - Args... args) { - return _expect_dictionary_keys_and_length(length_callback, false, {}, key, expected_count, callback, args...); - } - - template - node_callback_t expect_dictionary_keys_and_length(length_callback_t length_callback, - allow_other_keys_t, Args... args) { - return _expect_dictionary_keys_and_length(length_callback, true, {}, args...); - } - - template - node_callback_t expect_dictionary_keys(Args... args) { - return expect_dictionary_keys_and_length(default_length_callback, args...); - } - - template - concept Reservable = requires(T& t) { - { t.size() } -> std::same_as; - t.reserve( size_t {} ); - }; - template - node_callback_t expect_list_reserve_length(T& t, node_callback_t callback) { - return expect_list_and_length( - [&t](size_t size) -> size_t { - t.reserve(t.size() + size); - return size; - }, - callback - ); - } - template - node_callback_t expect_dictionary_reserve_length(T& t, key_value_callback_t callback) { - return expect_list_reserve_length(t, expect_assign(callback)); - } - - node_callback_t name_list_callback(std::vector& list); - - template - std::function assign_variable_callback(T& var) { - return [&var](T val) -> bool { - var = val; - return true; - }; - } - - template - requires(std::integral) - std::function assign_variable_callback_uint(const std::string_view name, T& var) { - return [&var, name](uint64_t val) -> bool { - if (val <= std::numeric_limits::max()) { - var = val; - return true; - } - Logger::error("Invalid ", name, ": ", val, " (valid range: [0, ", static_cast(std::numeric_limits::max()), "])"); - return false; - }; - } - - template - requires(std::integral) - std::function assign_variable_callback_int(const std::string_view name, T& var) { - return [&var, name](int64_t val) -> bool { - if (std::numeric_limits::lowest() <= val && val <= std::numeric_limits::max()) { - var = val; - return true; - } - Logger::error("Invalid ", name, ": ", val, " (valid range: [", - static_cast(std::numeric_limits::lowest()), ", ", - static_cast(std::numeric_limits::max()), "])"); - return false; - }; - } - } -} diff --git a/src/openvic/economy/Good.cpp b/src/openvic/economy/Good.cpp deleted file mode 100644 index 8ecaae0..0000000 --- a/src/openvic/economy/Good.cpp +++ /dev/null @@ -1,176 +0,0 @@ -#include "Good.hpp" - -#include - -using namespace OpenVic; -using namespace OpenVic::NodeTools; - -GoodCategory::GoodCategory(const std::string_view new_identifier) : HasIdentifier { new_identifier } {} - -Good::Good(const std::string_view new_identifier, colour_t new_colour, GoodCategory const& new_category, price_t new_base_price, - bool new_available_from_start, bool new_tradeable, bool new_money, bool new_overseas_penalty) - : HasIdentifierAndColour { new_identifier, new_colour, true }, - category { new_category }, - base_price { new_base_price }, - available_from_start { new_available_from_start }, - tradeable { new_tradeable }, - money { new_money }, - overseas_penalty { new_overseas_penalty } { - assert(base_price > NULL_PRICE); -} - -GoodCategory const& Good::get_category() const { - return category; -} - -Good::price_t Good::get_base_price() const { - return base_price; -} - -Good::price_t Good::get_price() const { - return price; -} - -bool Good::get_available_from_start() const { - return available_from_start; -} - -bool Good::get_available() const { - return available; -} - -bool Good::get_tradeable() const { - return tradeable; -} - -bool Good::get_money() const { - return money; -} - -bool Good::get_overseas_penalty() { - return overseas_penalty; -} - -void Good::reset_to_defaults() { - available = available_from_start; - price = base_price; -} - -GoodManager::GoodManager() : good_categories { "good categories" }, goods { "goods" } {} - -bool GoodManager::add_good_category(const std::string_view identifier) { - if (identifier.empty()) { - Logger::error("Invalid good category identifier - empty!"); - return false; - } - return good_categories.add_item({ identifier }); -} - -void GoodManager::lock_good_categories() { - good_categories.lock(); -} - -GoodCategory const* GoodManager::get_good_category_by_identifier(const std::string_view identifier) const { - return good_categories.get_item_by_identifier(identifier); -} - -size_t GoodManager::get_good_category_count() const { - return good_categories.size(); -} - -std::vector const& GoodManager::get_good_categories() const { - return good_categories.get_items(); -} - -bool GoodManager::add_good(const std::string_view identifier, colour_t colour, GoodCategory const* category, - Good::price_t base_price, bool available_from_start, bool tradeable, bool money, bool overseas_penalty) { - if (identifier.empty()) { - Logger::error("Invalid good identifier - empty!"); - return false; - } - if (colour > MAX_COLOUR_RGB) { - Logger::error("Invalid good colour for ", identifier, ": ", colour_to_hex_string(colour)); - return false; - } - if (category == nullptr) { - Logger::error("Invalid good category for ", identifier, ": null"); - return false; - } - if (base_price <= Good::NULL_PRICE) { - Logger::error("Invalid base price for ", identifier, ": ", base_price); - return false; - } - return goods.add_item({ identifier, colour, *category, base_price, available_from_start, tradeable, money, overseas_penalty }); -} - -void GoodManager::lock_goods() { - goods.lock(); -} - -Good const* GoodManager::get_good_by_index(size_t index) const { - return goods.get_item_by_index(index); -} - -Good const* GoodManager::get_good_by_identifier(const std::string_view identifier) const { - return goods.get_item_by_identifier(identifier); -} - -size_t GoodManager::get_good_count() const { - return goods.size(); -} - -std::vector const& GoodManager::get_goods() const { - return goods.get_items(); -} - -void GoodManager::reset_to_defaults() { - for (Good& good : goods.get_items()) - good.reset_to_defaults(); -} - -bool GoodManager::load_good_file(ast::NodeCPtr root) { - size_t total_expected_goods = 0; - bool ret = expect_dictionary_reserve_length( - good_categories, - [this, &total_expected_goods](std::string_view key, ast::NodeCPtr value) -> bool { - bool ret = expect_list_and_length( - [&total_expected_goods](size_t size) -> size_t { - total_expected_goods += size; - return 0; - }, - success_callback - )(value); - ret &= add_good_category(key); - return ret; - } - )(root); - lock_good_categories(); - goods.reserve(goods.size() + total_expected_goods); - ret &= expect_dictionary( - [this](std::string_view good_category_key, ast::NodeCPtr good_category_value) -> bool { - GoodCategory const* good_category = get_good_category_by_identifier(good_category_key); - - return expect_dictionary( - [this, good_category](std::string_view key, ast::NodeCPtr value) -> bool { - colour_t colour = NULL_COLOUR; - Good::price_t base_price; - bool available_from_start, tradeable = true; - bool money, overseas_penalty = false; - - bool ret = expect_dictionary_keys( - "color", ONE_EXACTLY, expect_colour(assign_variable_callback(colour)), - "cost", ONE_EXACTLY, expect_fixed_point(assign_variable_callback(base_price)), - "available_from_start", ZERO_OR_ONE, expect_bool(assign_variable_callback(available_from_start)), - "tradeable", ZERO_OR_ONE, expect_bool(assign_variable_callback(tradeable)), - "money", ZERO_OR_ONE, expect_bool(assign_variable_callback(money)), - "overseas_penalty", ZERO_OR_ONE, expect_bool(assign_variable_callback(overseas_penalty)) - )(value); - ret &= add_good(key, colour, good_category, base_price, available_from_start, tradeable, money, overseas_penalty); - return ret; - } - )(good_category_value); - } - )(root); - lock_goods(); - return ret; -} diff --git a/src/openvic/economy/Good.hpp b/src/openvic/economy/Good.hpp deleted file mode 100644 index f04c9b2..0000000 --- a/src/openvic/economy/Good.hpp +++ /dev/null @@ -1,86 +0,0 @@ -#pragma once - -#include "openvic/types/IdentifierRegistry.hpp" -#include "openvic/dataloader/NodeTools.hpp" - -namespace OpenVic { - struct GoodManager; - - struct GoodCategory : HasIdentifier { - friend struct GoodManager; - - private: - GoodCategory(const std::string_view new_identifier); - - public: - GoodCategory(GoodCategory&&) = default; - }; - - /* REQUIREMENTS: - * - * ECON-3 , ECON-4 , ECON-5 , ECON-6 , ECON-7 , ECON-8 , ECON-9 , ECON-10, ECON-11, ECON-12, ECON-13, ECON-14, - * ECON-15, ECON-16, ECON-17, ECON-18, ECON-19, ECON-20, ECON-21, ECON-22, ECON-23, ECON-24, ECON-25, ECON-26, - * ECON-27, ECON-28, ECON-29, ECON-30, ECON-31, ECON-32, ECON-33, ECON-34, ECON-35, ECON-36, ECON-37, ECON-38, - * ECON-39, ECON-40, ECON-41, ECON-42, ECON-43, ECON-44, ECON-45, ECON-46, ECON-47, ECON-48, ECON-49, ECON-50 - * - * ECON-123, ECON-124, ECON-125, ECON-126, ECON-127, ECON-128, ECON-129, ECON-130, ECON-131, ECON-132, ECON-133, ECON-134, - * ECON-135, ECON-136, ECON-137, ECON-138, ECON-139, ECON-140, ECON-141, ECON-142, ECON-234, ECON-235, ECON-236, ECON-237, - * ECON-238, ECON-239, ECON-240, ECON-241, ECON-242, ECON-243, ECON-244, ECON-245, ECON-246, ECON-247, ECON-248, ECON-249, - * ECON-250, ECON-251, ECON-252, ECON-253, ECON-254, ECON-255, ECON-256, ECON-257, ECON-258, ECON-259, ECON-260, ECON-261 - */ - struct Good : HasIdentifierAndColour { - friend struct GoodManager; - - using price_t = FP; - static constexpr price_t NULL_PRICE = FP::_0(); - - private: - GoodCategory const& category; - const price_t base_price; - price_t price; - const bool available_from_start, tradeable, money, overseas_penalty; - bool available; - - Good(const std::string_view new_identifier, colour_t new_colour, GoodCategory const& new_category, price_t new_base_price, - bool new_available_from_start, bool new_tradeable, bool new_money, bool new_overseas_penalty); - - public: - Good(Good&&) = default; - - GoodCategory const& get_category() const; - price_t get_base_price() const; - price_t get_price() const; - bool get_available_from_start() const; - bool get_available() const; - bool get_tradeable() const; - bool get_money() const; - bool get_overseas_penalty(); - void reset_to_defaults(); - }; - - struct GoodManager { - private: - IdentifierRegistry good_categories; - IdentifierRegistry goods; - - public: - GoodManager(); - - bool add_good_category(const std::string_view identifier); - void lock_good_categories(); - GoodCategory const* get_good_category_by_identifier(const std::string_view identifier) const; - size_t get_good_category_count() const; - std::vector const& get_good_categories() const; - - bool add_good(const std::string_view identifier, colour_t colour, GoodCategory const* category, Good::price_t base_price, - bool available_from_start, bool tradeable, bool money, bool overseas_penalty); - void lock_goods(); - Good const* get_good_by_index(size_t index) const; - Good const* get_good_by_identifier(const std::string_view identifier) const; - size_t get_good_count() const; - std::vector const& get_goods() const; - - void reset_to_defaults(); - bool load_good_file(ast::NodeCPtr root); - }; -} diff --git a/src/openvic/map/Building.cpp b/src/openvic/map/Building.cpp deleted file mode 100644 index 72bccc1..0000000 --- a/src/openvic/map/Building.cpp +++ /dev/null @@ -1,135 +0,0 @@ -#include "Building.hpp" - -#include - -#include "openvic/map/Province.hpp" -#include "openvic/utility/Logger.hpp" - -using namespace OpenVic; - -Building::Building(BuildingType const& new_type) - : HasIdentifier { new_type.get_identifier() }, - type { new_type } {} - -bool Building::_can_expand() const { - return level < type.get_max_level(); -} - -BuildingType const& Building::get_type() const { - return type; -} - -Building::level_t Building::get_level() const { - return level; -} - -Building::ExpansionState Building::get_expansion_state() const { - return expansion_state; -} - -Date const& Building::get_start_date() const { - return start; -} - -Date const& Building::get_end_date() const { - return end; -} - -float Building::get_expansion_progress() const { - return expansion_progress; -} - -bool Building::expand() { - if (expansion_state == ExpansionState::CanExpand) { - expansion_state = ExpansionState::Preparing; - expansion_progress = 0.0f; - return true; - } - return false; -} - -/* REQUIREMENTS: - * MAP-71, MAP-74, MAP-77 - */ -void Building::update_state(Date const& today) { - switch (expansion_state) { - case ExpansionState::Preparing: - start = today; - end = start + type.get_build_time(); - break; - case ExpansionState::Expanding: - expansion_progress = static_cast(today - start) / static_cast(end - start); - break; - default: expansion_state = _can_expand() ? ExpansionState::CanExpand : ExpansionState::CannotExpand; - } -} - -void Building::tick(Date const& today) { - if (expansion_state == ExpansionState::Preparing) { - expansion_state = ExpansionState::Expanding; - } - if (expansion_state == ExpansionState::Expanding) { - if (end <= today) { - level++; - expansion_state = ExpansionState::CannotExpand; - } - } -} - -BuildingType::BuildingType(const std::string_view new_identifier, Building::level_t new_max_level, Timespan new_build_time) - : HasIdentifier { new_identifier }, - max_level { new_max_level }, - build_time { new_build_time } { - assert(max_level >= 0); - assert(build_time >= 0); -} - -Building::level_t BuildingType::get_max_level() const { - return max_level; -} - -Timespan BuildingType::get_build_time() const { - return build_time; -} - -BuildingManager::BuildingManager() : building_types { "building types" } {} - -bool BuildingManager::add_building_type(const std::string_view identifier, Building::level_t max_level, Timespan build_time) { - if (identifier.empty()) { - Logger::error("Invalid building type identifier - empty!"); - return false; - } - if (max_level < 0) { - Logger::error("Invalid building type max level for ", identifier, ": ", max_level); - return false; - } - if (build_time < 0) { - Logger::error("Invalid building type build time for ", identifier, ": ", build_time); - return false; - } - return building_types.add_item({ identifier, max_level, build_time }); -} - -void BuildingManager::lock_building_types() { - building_types.lock(); -} - -BuildingType const* BuildingManager::get_building_type_by_identifier(const std::string_view identifier) const { - return building_types.get_item_by_identifier(identifier); -} - -bool BuildingManager::generate_province_buildings(Province& province) const { - province.reset_buildings(); - if (!building_types.is_locked()) { - Logger::error("Cannot generate buildings until building types are locked!"); - return false; - } - bool ret = true; - if (!province.is_water()) { - for (BuildingType const& type : building_types.get_items()) { - ret &= province.add_building({ type }); - } - } - province.lock_buildings(); - return ret; -} diff --git a/src/openvic/map/Building.hpp b/src/openvic/map/Building.hpp deleted file mode 100644 index d1c0348..0000000 --- a/src/openvic/map/Building.hpp +++ /dev/null @@ -1,86 +0,0 @@ -#pragma once - -#include - -#include "openvic/types/Date.hpp" -#include "openvic/types/IdentifierRegistry.hpp" - -namespace OpenVic { - - struct BuildingManager; - struct BuildingType; - - /* REQUIREMENTS: - * MAP-11, MAP-72, MAP-73 - * MAP-12, MAP-75, MAP-76 - * MAP-13, MAP-78, MAP-79 - */ - struct Building : HasIdentifier { - friend struct BuildingManager; - - using level_t = int8_t; - - enum class ExpansionState { - CannotExpand, - CanExpand, - Preparing, - Expanding - }; - - private: - BuildingType const& type; - level_t level = 0; - ExpansionState expansion_state = ExpansionState::CannotExpand; - Date start, end; - float expansion_progress; - - Building(BuildingType const& new_type); - - bool _can_expand() const; - - public: - Building(Building&&) = default; - - BuildingType const& get_type() const; - level_t get_level() const; - ExpansionState get_expansion_state() const; - Date const& get_start_date() const; - Date const& get_end_date() const; - float get_expansion_progress() const; - - bool expand(); - void update_state(Date const& today); - void tick(Date const& today); - }; - - struct BuildingType : HasIdentifier { - friend struct BuildingManager; - - private: - const Building::level_t max_level; - const Timespan build_time; - - BuildingType(const std::string_view new_identifier, Building::level_t new_max_level, Timespan new_build_time); - - public: - BuildingType(BuildingType&&) = default; - - Building::level_t get_max_level() const; - Timespan get_build_time() const; - }; - - struct Province; - - struct BuildingManager { - private: - IdentifierRegistry building_types; - - public: - BuildingManager(); - - bool add_building_type(const std::string_view identifier, Building::level_t max_level, Timespan build_time); - void lock_building_types(); - BuildingType const* get_building_type_by_identifier(const std::string_view identifier) const; - bool generate_province_buildings(Province& province) const; - }; -} diff --git a/src/openvic/map/Map.cpp b/src/openvic/map/Map.cpp deleted file mode 100644 index 6375691..0000000 --- a/src/openvic/map/Map.cpp +++ /dev/null @@ -1,529 +0,0 @@ -#include "Map.hpp" - -#include -#include - -#include "openvic/economy/Good.hpp" -#include "openvic/utility/Logger.hpp" - -using namespace OpenVic; - -Mapmode::Mapmode(const std::string_view new_identifier, index_t new_index, colour_func_t new_colour_func) - : HasIdentifier { new_identifier }, - index { new_index }, - colour_func { new_colour_func } { - assert(colour_func != nullptr); -} - -const Mapmode Mapmode::ERROR_MAPMODE { "mapmode_error", 0, - [](Map const& map, Province const& province) -> colour_t { return 0xFFFF0000; } }; - -Mapmode::index_t Mapmode::get_index() const { - return index; -} - -colour_t Mapmode::get_colour(Map const& map, Province const& province) const { - return colour_func ? colour_func(map, province) : NULL_COLOUR; -} - -Map::Map() : provinces { "provinces" }, - regions { "regions" }, - mapmodes { "mapmodes" } {} - -bool Map::add_province(const std::string_view identifier, colour_t colour) { - if (provinces.size() >= max_provinces) { - Logger::error("The map's province list is full - maximum number of provinces is ", max_provinces, " (this can be at most ", Province::MAX_INDEX, ")"); - return false; - } - if (identifier.empty()) { - Logger::error("Invalid province identifier - empty!"); - return false; - } - if (colour == NULL_COLOUR || colour > MAX_COLOUR_RGB) { - Logger::error("Invalid province colour for ", identifier, ": ", colour_to_hex_string(colour)); - return false; - } - Province new_province { identifier, colour, static_cast(provinces.size() + 1) }; - const Province::index_t index = get_index_from_colour(colour); - if (index != Province::NULL_INDEX) { - Logger::error("Duplicate province colours: ", get_province_by_index(index)->to_string(), " and ", new_province.to_string()); - return false; - } - colour_index_map[new_province.get_colour()] = new_province.get_index(); - return provinces.add_item(std::move(new_province)); -} - -void Map::lock_provinces() { - provinces.lock(); -} - -bool Map::set_water_province(const std::string_view identifier) { - if (water_provinces.is_locked()) { - Logger::error("The map's water provinces have already been locked!"); - return false; - } - Province* province = get_province_by_identifier(identifier); - if (province == nullptr) { - Logger::error("Unrecognised water province identifier: ", identifier); - return false; - } - if (province->is_water()) { - Logger::error("Province ", identifier, " is already a water province!"); - return false; - } - if (!water_provinces.add_province(province)) { - Logger::error("Failed to add province ", identifier, " to water province set!"); - return false; - } - province->water = true; - return true; -} - -bool Map::set_water_province_list(std::vector const& list) { - bool ret = true; - water_provinces.reserve(water_provinces.size() + list.size()); - for (std::string_view const& identifier : list) { - ret &= set_water_province(identifier); - } - return ret; -} - -void Map::lock_water_provinces() { - water_provinces.lock(); - Logger::info("Locked water provinces after registering ", water_provinces.size()); -} - -bool Map::add_region(const std::string_view identifier, std::vector const& province_identifiers) { - if (identifier.empty()) { - Logger::error("Invalid region identifier - empty!"); - return false; - } - Region new_region { identifier }; - bool ret = true; - for (const std::string_view province_identifier : province_identifiers) { - Province* province = get_province_by_identifier(province_identifier); - if (province != nullptr) { - if (new_region.contains_province(province)) { - Logger::error("Duplicate province identifier ", province_identifier, " in region ", identifier); - ret = false; - } else { - size_t other_region_index = reinterpret_cast(province->get_region()); - if (other_region_index != 0) { - other_region_index--; - if (other_region_index < regions.size()) - Logger::error("Cannot add province ", province_identifier, " to region ", identifier, " - it is already part of ", regions.get_item_by_index(other_region_index)->get_identifier()); - else - Logger::error("Cannot add province ", province_identifier, " to region ", identifier, " - it is already part of an unknown region with index ", other_region_index); - ret = false; - } else if (!new_region.add_province(province)) { - Logger::error("Failed to add province ", province_identifier, " to region ", identifier); - ret = false; - } - } - } else { - Logger::error("Invalid province identifier ", province_identifier, " for region ", identifier); - ret = false; - } - } - new_region.lock(); - if (new_region.empty()) { - Logger::error("No valid provinces in list for ", identifier); - return false; - } - - // Used to detect provinces listed in multiple regions, will - // be corrected once regions is stable (i.e. lock_regions). - Region* tmp_region_index = reinterpret_cast(regions.size()); - for (Province* province : new_region.get_provinces()) - province->region = tmp_region_index; - ret &= regions.add_item(std::move(new_region)); - return ret; -} - -void Map::lock_regions() { - regions.lock(); - for (Region& region : regions.get_items()) - for (Province* province : region.get_provinces()) - province->region = ®ion; -} - -size_t Map::get_province_count() const { - return provinces.size(); -} - -std::vector const& Map::get_provinces() const { - return provinces.get_items(); -} - -Province* Map::get_province_by_index(Province::index_t index) { - return index != Province::NULL_INDEX ? provinces.get_item_by_index(index - 1) : nullptr; -} - -Province const* Map::get_province_by_index(Province::index_t index) const { - return index != Province::NULL_INDEX ? provinces.get_item_by_index(index - 1) : nullptr; -} - -Province* Map::get_province_by_identifier(const std::string_view identifier) { - return provinces.get_item_by_identifier(identifier); -} - -Province const* Map::get_province_by_identifier(const std::string_view identifier) const { - return provinces.get_item_by_identifier(identifier); -} - -Province::index_t Map::get_index_from_colour(colour_t colour) const { - const colour_index_map_t::const_iterator it = colour_index_map.find(colour); - if (it != colour_index_map.end()) return it->second; - return Province::NULL_INDEX; -} - -Province::index_t Map::get_province_index_at(size_t x, size_t y) const { - if (x < width && y < height) return province_shape_image[x + y * width].index; - return Province::NULL_INDEX; -} - -bool Map::set_max_provinces(Province::index_t new_max_provinces) { - if (new_max_provinces <= Province::NULL_INDEX) { - Logger::error("Trying to set max province count to an invalid value ", new_max_provinces, " (must be greater than ", Province::NULL_INDEX, ")"); - return false; - } - if (!provinces.empty() || provinces.is_locked()) { - Logger::error("Trying to set max province count to ", new_max_provinces, " after provinces have already been added and/or locked"); - return false; - } - max_provinces = new_max_provinces; - return true; -} - -Province::index_t Map::get_max_provinces() const { - return max_provinces; -} - -void Map::set_selected_province(Province::index_t index) { - if (index > get_province_count()) { - Logger::error("Trying to set selected province to an invalid index ", index, " (max index is ", get_province_count(), ")"); - selected_province = Province::NULL_INDEX; - } else { - selected_province = index; - } -} - -Province::index_t Map::get_selected_province_index() const { - return selected_province; -} - -Province const* Map::get_selected_province() const { - return get_province_by_index(get_selected_province_index()); -} - -Region* Map::get_region_by_identifier(const std::string_view identifier) { - return regions.get_item_by_identifier(identifier); -} - -Region const* Map::get_region_by_identifier(const std::string_view identifier) const { - return regions.get_item_by_identifier(identifier); -} - -size_t Map::get_region_count() const { - return regions.size(); -} - -std::vector const& Map::get_regions() const { - return regions.get_items(); -} - -static colour_t colour_at(uint8_t const* colour_data, int32_t idx) { - idx *= 3; - return (colour_data[idx] << 16) | (colour_data[idx + 1] << 8) | colour_data[idx + 2]; -} - -bool Map::generate_province_shape_image(size_t new_width, size_t new_height, uint8_t const* colour_data, - uint8_t const* terrain_data, terrain_variant_map_t const& terrain_variant_map, bool detailed_errors) { - if (!province_shape_image.empty()) { - Logger::error("Province index image has already been generated!"); - return false; - } - if (!provinces.is_locked()) { - Logger::error("Province index image cannot be generated until after provinces are locked!"); - return false; - } - if (new_width < 1 || new_height < 1) { - Logger::error("Invalid province image dimensions: ", new_width, "x", new_height); - return false; - } - if (colour_data == nullptr) { - Logger::error("Province colour data pointer is null!"); - return false; - } - if (terrain_data == nullptr) { - Logger::error("Province terrain data pointer is null!"); - return false; - } - width = new_width; - height = new_height; - province_shape_image.resize(width * height); - - std::vector province_checklist(provinces.size()); - bool ret = true; - std::unordered_set unrecognised_province_colours, unrecognised_terrain_colours; - - for (int32_t y = 0; y < height; ++y) { - for (int32_t x = 0; x < width; ++x) { - const int32_t idx = x + y * width; - - const colour_t terrain_colour = colour_at(terrain_data, idx); - const terrain_variant_map_t::const_iterator it = terrain_variant_map.find(terrain_colour); - if (it != terrain_variant_map.end()) province_shape_image[idx].terrain = it->second; - else { - if (unrecognised_terrain_colours.find(terrain_colour) == unrecognised_terrain_colours.end()) { - unrecognised_terrain_colours.insert(terrain_colour); - if (detailed_errors) { - Logger::error("Unrecognised terrain colour ", colour_to_hex_string(terrain_colour), - " at (", x, ", ", y, ")"); - } - } - province_shape_image[idx].terrain = 0; - } - - const colour_t province_colour = colour_at(colour_data, idx); - if (x > 0) { - const int32_t jdx = idx - 1; - if (colour_at(colour_data, jdx) == province_colour) { - province_shape_image[idx].index = province_shape_image[jdx].index; - continue; - } - } - if (y > 0) { - const int32_t jdx = idx - width; - if (colour_at(colour_data, jdx) == province_colour) { - province_shape_image[idx].index = province_shape_image[jdx].index; - continue; - } - } - const Province::index_t index = get_index_from_colour(province_colour); - if (index != Province::NULL_INDEX) { - province_checklist[index - 1] = true; - province_shape_image[idx].index = index; - continue; - } - if (unrecognised_province_colours.find(province_colour) == unrecognised_province_colours.end()) { - unrecognised_province_colours.insert(province_colour); - if (detailed_errors) { - Logger::error("Unrecognised province colour ", colour_to_hex_string(province_colour), - " at (", x, ", ", y, ")"); - } - } - province_shape_image[idx].index = Province::NULL_INDEX; - } - } - if (!unrecognised_province_colours.empty()) { - Logger::error("Province image contains ", unrecognised_province_colours.size(), " unrecognised province colours"); - ret = false; - } - if (!unrecognised_terrain_colours.empty()) { - Logger::error("Terrain image contains ", unrecognised_terrain_colours.size(), " unrecognised terrain colours"); - ret = false; - } - - size_t missing = 0; - for (size_t idx = 0; idx < province_checklist.size(); ++idx) { - if (!province_checklist[idx]) { - if (detailed_errors) { - Logger::error("Province missing from shape image: ", provinces.get_item_by_index(idx)->to_string()); - } - missing++; - } - } - if (missing > 0) { - Logger::error("Province image is missing ", missing, " province colours"); - ret = false; - } - return ret; -} - -size_t Map::get_width() const { - return width; -} - -size_t Map::get_height() const { - return height; -} - -std::vector const& Map::get_province_shape_image() const { - return province_shape_image; -} - -bool Map::add_mapmode(const std::string_view identifier, Mapmode::colour_func_t colour_func) { - if (identifier.empty()) { - Logger::error("Invalid mapmode identifier - empty!"); - return false; - } - if (colour_func == nullptr) { - Logger::error("Mapmode colour function is null for identifier: ", identifier); - return false; - } - return mapmodes.add_item({ identifier, mapmodes.size(), colour_func }); -} - -void Map::lock_mapmodes() { - mapmodes.lock(); -} - -size_t Map::get_mapmode_count() const { - return mapmodes.size(); -} - -std::vector const& Map::get_mapmodes() const { - return mapmodes.get_items(); -} - -Mapmode const* Map::get_mapmode_by_index(size_t index) const { - return mapmodes.get_item_by_index(index); -} - -Mapmode const* Map::get_mapmode_by_identifier(const std::string_view identifier) const { - return mapmodes.get_item_by_identifier(identifier); -} - -bool Map::generate_mapmode_colours(Mapmode::index_t index, uint8_t* target) const { - if (target == nullptr) { - Logger::error("Mapmode colour target pointer is null!"); - return false; - } - bool ret = true; - Mapmode const* mapmode = mapmodes.get_item_by_index(index); - if (mapmode == nullptr) { - // Not an error if mapmodes haven't yet been loaded, - // e.g. if we want to allocate the province colour - // texture before mapmodes are loaded. - if (!(mapmodes.empty() && index == 0)) { - Logger::error("Invalid mapmode index: ", index); - ret = false; - } - mapmode = &Mapmode::ERROR_MAPMODE; - } - // Skip past Province::NULL_INDEX - for (size_t i = 0; i < MAPMODE_COLOUR_SIZE; ++i) - *target++ = 0; - for (Province const& province : provinces.get_items()) { - const colour_t colour = mapmode->get_colour(*this, province); - *target++ = (colour >> 16) & FULL_COLOUR; - *target++ = (colour >> 8) & FULL_COLOUR; - *target++ = colour & FULL_COLOUR; - *target++ = (colour >> 24) & FULL_COLOUR; - } - return ret; -} - -void Map::update_highest_province_population() { - highest_province_population = 0; - for (Province const& province : provinces.get_items()) { - highest_province_population = std::max(highest_province_population, province.get_total_population()); - } -} - -Pop::pop_size_t Map::get_highest_province_population() const { - return highest_province_population; -} - -void Map::update_total_map_population() { - total_map_population = 0; - for (Province const& province : provinces.get_items()) { - total_map_population += province.get_total_population(); - } -} - -Pop::pop_size_t Map::get_total_map_population() const { - return total_map_population; -} - -bool Map::setup(GoodManager const& good_manager, BuildingManager const& building_manager, PopManager const& pop_manager) { - bool ret = true; - for (Province& province : provinces.get_items()) { - province.clear_pops(); - // Set all land provinces to have an RGO based on their index to test them - if (!province.is_water() && good_manager.get_good_count() > 0) - province.rgo = good_manager.get_good_by_index(province.get_index() % good_manager.get_good_count()); - ret &= building_manager.generate_province_buildings(province); - } - return ret; -} - -void Map::update_state(Date const& today) { - for (Province& province : provinces.get_items()) - province.update_state(today); - update_highest_province_population(); - update_total_map_population(); -} - -void Map::tick(Date const& today) { - for (Province& province : provinces.get_items()) - province.tick(today); -} - -using namespace ovdl::csv; - -static bool validate_province_definitions_header(LineObject const& header) { - static const std::vector standard_header { "province", "red", "green", "blue" }; - for (size_t i = 0; i < standard_header.size(); ++i) { - const std::string_view val = header.get_value_for(i); - if (i == 0 && val.empty()) break; - if (val != standard_header[i]) return false; - } - return true; -} - -static bool parse_province_colour(colour_t& colour, std::array components) { - bool ret = true; - colour = NULL_COLOUR; - for (std::string_view& c : components) { - colour <<= 8; - if (c.ends_with('.')) c.remove_suffix(1); - bool successful = false; - uint64_t val = StringUtils::string_to_uint64(c, &successful, 10); - if (successful && val <= 255) { - colour |= val; - } else { - ret = false; - } - } - return ret; -} - -bool Map::load_province_definitions(std::vector const& lines) { - if (lines.empty()) { - Logger::error("No header or entries in province definition file!"); - return false; - } - { - LineObject const& header = lines.front(); - if (!validate_province_definitions_header(header)) { - Logger::error("Non-standard province definition file header - make sure this is not a province definition: ", header); - } - } - if (lines.size() <= 1) { - Logger::error("No entries in province definition file!"); - return false; - } - provinces.reserve(lines.size() - 1); - bool ret = true; - std::for_each(lines.begin() + 1, lines.end(), - [this, &ret](LineObject const& line) -> void { - const std::string_view identifier = line.get_value_for(0); - if (!identifier.empty()) { - colour_t colour; - if (!parse_province_colour(colour, { - line.get_value_for(1), - line.get_value_for(2), - line.get_value_for(3) - })) { - Logger::error("Error reading colour in province definition: ", line); - ret = false; - } - ret &= add_province(identifier, colour); - } - } - ); - lock_provinces(); - return ret; -} diff --git a/src/openvic/map/Map.hpp b/src/openvic/map/Map.hpp deleted file mode 100644 index 4684226..0000000 --- a/src/openvic/map/Map.hpp +++ /dev/null @@ -1,121 +0,0 @@ -#pragma once - -#include - -#include - -#include "openvic/map/Region.hpp" - -namespace OpenVic { - - struct Mapmode : HasIdentifier { - friend struct Map; - - using colour_func_t = std::function; - using index_t = size_t; - - private: - const index_t index; - const colour_func_t colour_func; - - Mapmode(const std::string_view new_identifier, index_t new_index, colour_func_t new_colour_func); - - public: - static const Mapmode ERROR_MAPMODE; - - Mapmode(Mapmode&&) = default; - - index_t get_index() const; - colour_t get_colour(Map const& map, Province const& province) const; - }; - - struct GoodManager; - - /* REQUIREMENTS: - * MAP-4 - */ - struct Map { - using terrain_t = uint8_t; - using terrain_variant_map_t = std::map; - -#pragma pack(push, 1) - struct shape_pixel_t { - Province::index_t index; - terrain_t terrain; - }; -#pragma pack(pop) - private: - using colour_index_map_t = std::map; - - IdentifierRegistry provinces; - IdentifierRegistry regions; - IdentifierRegistry mapmodes; - ProvinceSet water_provinces; - - size_t width = 0, height = 0; - std::vector province_shape_image; - colour_index_map_t colour_index_map; - Province::index_t max_provinces = Province::MAX_INDEX; - Province::index_t selected_province = Province::NULL_INDEX; - - Pop::pop_size_t highest_province_population, total_map_population; - - Province::index_t get_index_from_colour(colour_t colour) const; - - public: - Map(); - - bool add_province(const std::string_view identifier, colour_t colour); - void lock_provinces(); - bool set_water_province(const std::string_view identifier); - bool set_water_province_list(std::vector const& list); - void lock_water_provinces(); - bool add_region(const std::string_view identifier, std::vector const& province_identifiers); - void lock_regions(); - - size_t get_province_count() const; - std::vector const& get_provinces() const; - Province* get_province_by_index(Province::index_t index); - Province const* get_province_by_index(Province::index_t index) const; - Province* get_province_by_identifier(const std::string_view identifier); - Province const* get_province_by_identifier(const std::string_view identifier) const; - Province::index_t get_province_index_at(size_t x, size_t y) const; - bool set_max_provinces(Province::index_t new_max_provinces); - Province::index_t get_max_provinces() const; - void set_selected_province(Province::index_t index); - Province::index_t get_selected_province_index() const; - Province const* get_selected_province() const; - - Region* get_region_by_identifier(const std::string_view identifier); - Region const* get_region_by_identifier(const std::string_view identifier) const; - size_t get_region_count() const; - std::vector const& get_regions() const; - - bool generate_province_shape_image(size_t new_width, size_t new_height, uint8_t const* colour_data, - uint8_t const* terrain_data, terrain_variant_map_t const& terrain_variant_map, bool detailed_errors); - size_t get_width() const; - size_t get_height() const; - std::vector const& get_province_shape_image() const; - - bool add_mapmode(const std::string_view identifier, Mapmode::colour_func_t colour_func); - void lock_mapmodes(); - size_t get_mapmode_count() const; - std::vector const& get_mapmodes() const; - Mapmode const* get_mapmode_by_index(Mapmode::index_t index) const; - Mapmode const* get_mapmode_by_identifier(const std::string_view identifier) const; - static constexpr size_t MAPMODE_COLOUR_SIZE = 4; - bool generate_mapmode_colours(Mapmode::index_t index, uint8_t* target) const; - - bool setup(GoodManager const& good_manager, BuildingManager const& building_manager, PopManager const& pop_manager); - - void update_highest_province_population(); - Pop::pop_size_t get_highest_province_population() const; - void update_total_map_population(); - Pop::pop_size_t get_total_map_population() const; - - void update_state(Date const& today); - void tick(Date const& today); - - bool load_province_definitions(std::vector const& lines); - }; -} diff --git a/src/openvic/map/Province.cpp b/src/openvic/map/Province.cpp deleted file mode 100644 index cfdc263..0000000 --- a/src/openvic/map/Province.cpp +++ /dev/null @@ -1,145 +0,0 @@ -#include "Province.hpp" - -#include -#include -#include - -using namespace OpenVic; -using namespace OpenVic::NodeTools; - -Province::Province(const std::string_view new_identifier, colour_t new_colour, index_t new_index) - : HasIdentifierAndColour { new_identifier, new_colour, false }, - index { new_index }, - buildings { "buildings" } { - assert(index != NULL_INDEX); -} - -Province::index_t Province::get_index() const { - return index; -} - -Region* Province::get_region() const { - return region; -} - -bool Province::is_water() const { - return water; -} - -Province::life_rating_t Province::get_life_rating() const { - return life_rating; -} - -bool Province::add_building(Building&& building) { - return buildings.add_item(std::move(building)); -} - -void Province::lock_buildings() { - buildings.lock(false); -} - -void Province::reset_buildings() { - buildings.reset(); -} - -Building const* Province::get_building_by_identifier(const std::string_view identifier) const { - return buildings.get_item_by_identifier(identifier); -} - -size_t Province::get_building_count() const { - return buildings.size(); -} - -std::vector const& Province::get_buildings() const { - return buildings.get_items(); -} - -bool Province::expand_building(const std::string_view building_type_identifier) { - Building* building = buildings.get_item_by_identifier(building_type_identifier); - if (building == nullptr) return false; - return building->expand(); -} - -Good const* Province::get_rgo() const { - return rgo; -} - -std::string Province::to_string() const { - std::stringstream stream; - stream << "(#" << std::to_string(index) << ", " << get_identifier() << ", 0x" << colour_to_hex_string() << ")"; - return stream.str(); -} - -bool Province::load_pop_list(PopManager const& pop_manager, ast::NodeCPtr root) { - return expect_list_reserve_length( - pops, - [this, &pop_manager](ast::NodeCPtr pop_node) -> bool { - return pop_manager.load_pop_into_province(*this, pop_node); - } - )(root); -} - -bool Province::add_pop(Pop&& pop) { - if (!is_water()) { - pops.push_back(std::move(pop)); - return true; - } else { - Logger::error("Trying to add pop to water province ", get_identifier()); - return false; - } -} - -void Province::clear_pops() { - pops.clear(); -} - -size_t Province::get_pop_count() const { - return pops.size(); -} - -std::vector const& Province::get_pops() const { - return pops; -} - -Pop::pop_size_t Province::get_total_population() const { - return total_population; -} - -distribution_t const& Province::get_pop_type_distribution() const { - return pop_types; -} - -distribution_t const& Province::get_culture_distribution() const { - return cultures; -} - -distribution_t const& Province::get_religion_distribution() const { - return religions; -} - -/* REQUIREMENTS: - * MAP-65 - */ -void Province::update_pops() { - total_population = 0; - pop_types.clear(); - cultures.clear(); - religions.clear(); - for (Pop const& pop : pops) { - total_population += pop.get_size(); - pop_types[&pop.get_type()] += pop.get_size(); - cultures[&pop.get_culture()] += pop.get_size(); - religions[&pop.get_religion()] += pop.get_size(); - } -} - -void Province::update_state(Date const& today) { - for (Building& building : buildings.get_items()) - building.update_state(today); - update_pops(); -} - -void Province::tick(Date const& today) { - for (Building& building : buildings.get_items()) - building.tick(today); -} diff --git a/src/openvic/map/Province.hpp b/src/openvic/map/Province.hpp deleted file mode 100644 index f64bca2..0000000 --- a/src/openvic/map/Province.hpp +++ /dev/null @@ -1,68 +0,0 @@ -#pragma once - -#include "openvic/map/Building.hpp" -#include "openvic/pop/Pop.hpp" - -namespace OpenVic { - struct Map; - struct Region; - struct Good; - - /* REQUIREMENTS: - * MAP-5, MAP-7, MAP-8, MAP-43, MAP-47 - */ - struct Province : HasIdentifierAndColour { - friend struct Map; - - using index_t = uint16_t; - using life_rating_t = int8_t; - - static constexpr index_t NULL_INDEX = 0, MAX_INDEX = (1 << (8 * sizeof(index_t))) - 1; - - private: - const index_t index; - Region* region = nullptr; - bool water = false; - life_rating_t life_rating = 0; - IdentifierRegistry buildings; - // TODO - change this into a factory-like structure - Good const* rgo = nullptr; - - std::vector pops; - Pop::pop_size_t total_population; - distribution_t pop_types, cultures, religions; - - Province(const std::string_view new_identifier, colour_t new_colour, index_t new_index); - - public: - Province(Province&&) = default; - - index_t get_index() const; - Region* get_region() const; - bool is_water() const; - life_rating_t get_life_rating() const; - bool add_building(Building&& building); - void lock_buildings(); - void reset_buildings(); - Building const* get_building_by_identifier(const std::string_view identifier) const; - size_t get_building_count() const; - std::vector const& get_buildings() const; - bool expand_building(const std::string_view building_type_identifier); - Good const* get_rgo() const; - std::string to_string() const; - - bool load_pop_list(PopManager const& pop_manager, ast::NodeCPtr root); - bool add_pop(Pop&& pop); - void clear_pops(); - size_t get_pop_count() const; - std::vector const& get_pops() const; - Pop::pop_size_t get_total_population() const; - distribution_t const& get_pop_type_distribution() const; - distribution_t const& get_culture_distribution() const; - distribution_t const& get_religion_distribution() const; - void update_pops(); - - void update_state(Date const& today); - void tick(Date const& today); - }; -} diff --git a/src/openvic/map/Region.cpp b/src/openvic/map/Region.cpp deleted file mode 100644 index 33092c5..0000000 --- a/src/openvic/map/Region.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#include "Region.hpp" - -using namespace OpenVic; - -bool ProvinceSet::add_province(Province* province) { - if (locked) { - Logger::error("Cannot add province to province set - locked!"); - return false; - } - if (province == nullptr) { - Logger::error("Cannot add province to province set - null province!"); - return false; - } - if (contains_province(province)) { - Logger::error("Cannot add province ", province->get_identifier(), " to province set - already in the set!"); - return false; - } - provinces.push_back(province); - return true; -} - -void ProvinceSet::lock(bool log) { - if (locked) { - Logger::error("Failed to lock province set - already locked!"); - } else { - locked = true; - if (log) Logger::info("Locked province set with ", size(), " provinces"); - } -} - -bool ProvinceSet::is_locked() const { - return locked; -} - -void ProvinceSet::reset() { - provinces.clear(); - locked = false; -} - -bool ProvinceSet::empty() const { - return provinces.empty(); -} - -size_t ProvinceSet::size() const { - return provinces.size(); -} - -void ProvinceSet::reserve(size_t size) { - if (locked) { - Logger::error("Failed to reserve space for ", size, " items in province set - already locked!"); - } else { - provinces.reserve(size); - } -} - -bool ProvinceSet::contains_province(Province const* province) const { - return province && std::find(provinces.begin(), provinces.end(), province) != provinces.end(); -} - -std::vector const& ProvinceSet::get_provinces() const { - return provinces; -} - -Region::Region(const std::string_view new_identifier) : HasIdentifier { new_identifier } {} - -colour_t Region::get_colour() const { - if (provinces.empty()) return FULL_COLOUR << 16; - return provinces.front()->get_colour(); -} diff --git a/src/openvic/map/Region.hpp b/src/openvic/map/Region.hpp deleted file mode 100644 index a3640ca..0000000 --- a/src/openvic/map/Region.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#include "openvic/map/Province.hpp" - -namespace OpenVic { - - struct ProvinceSet { - protected: - std::vector provinces; - bool locked = false; - - public: - bool add_province(Province* province); - void lock(bool log = false); - bool is_locked() const; - void reset(); - bool empty() const; - size_t size() const; - void reserve(size_t size); - bool contains_province(Province const* province) const; - std::vector const& get_provinces() const; - }; - - /* REQUIREMENTS: - * MAP-6, MAP-44, MAP-48 - */ - struct Region : HasIdentifier, ProvinceSet { - friend struct Map; - - private: - Region(const std::string_view new_identifier); - - public: - Region(Region&&) = default; - - colour_t get_colour() const; - }; -} diff --git a/src/openvic/pop/Culture.cpp b/src/openvic/pop/Culture.cpp deleted file mode 100644 index d86d608..0000000 --- a/src/openvic/pop/Culture.cpp +++ /dev/null @@ -1,233 +0,0 @@ -#include "Culture.hpp" - -#include "openvic/dataloader/NodeTools.hpp" - -using namespace OpenVic; -using namespace OpenVic::NodeTools; - -GraphicalCultureType::GraphicalCultureType(const std::string_view new_identifier) : HasIdentifier { new_identifier } {} - -CultureGroup::CultureGroup(const std::string_view new_identifier, const std::string_view new_leader, - GraphicalCultureType const& new_unit_graphical_culture_type, bool new_is_overseas) - : HasIdentifier { new_identifier }, leader { new_leader }, - unit_graphical_culture_type { new_unit_graphical_culture_type }, - is_overseas { new_is_overseas } {} - - -std::string const& CultureGroup::get_leader() const { - return leader; -} - -GraphicalCultureType const& CultureGroup::get_unit_graphical_culture_type() const { - return unit_graphical_culture_type; -} - -bool CultureGroup::get_is_overseas() const { - return is_overseas; -} - -Culture::Culture(const std::string_view new_identifier, colour_t new_colour, CultureGroup const& new_group, - std::vector const& new_first_names, std::vector const& new_last_names) - : HasIdentifierAndColour { new_identifier, new_colour, true }, - group { new_group }, - first_names { new_first_names }, - last_names { new_last_names } {} - -CultureGroup const& Culture::get_group() const { - return group; -} - -std::vector const& Culture::get_first_names() const { - return first_names; -} - -std::vector const& Culture::get_last_names() const { - return last_names; -} - -CultureManager::CultureManager() - : graphical_culture_types { "graphical culture types" }, - culture_groups { "culture groups" }, - cultures { "cultures" } {} - -bool CultureManager::add_graphical_culture_type(const std::string_view identifier) { - if (identifier.empty()) { - Logger::error("Invalid culture group identifier - empty!"); - return false; - } - return graphical_culture_types.add_item({ identifier }); -} - -void CultureManager::lock_graphical_culture_types() { - graphical_culture_types.lock(); -} - -GraphicalCultureType const* CultureManager::get_graphical_culture_type_by_identifier(const std::string_view identifier) const { - return graphical_culture_types.get_item_by_identifier(identifier); -} - -size_t CultureManager::get_graphical_culture_type_count() const { - return graphical_culture_types.size(); -} - -std::vector const& CultureManager::get_graphical_culture_types() const { - return graphical_culture_types.get_items(); -} - -bool CultureManager::add_culture_group(const std::string_view identifier, const std::string_view leader, GraphicalCultureType const* graphical_culture_type, bool is_overseas) { - if (!graphical_culture_types.is_locked()) { - Logger::error("Cannot register culture groups until graphical culture types are locked!"); - return false; - } - if (identifier.empty()) { - Logger::error("Invalid culture group identifier - empty!"); - return false; - } - if (leader.empty()) { - Logger::error("Invalid culture group leader - empty!"); - return false; - } - if (graphical_culture_type == nullptr) { - Logger::error("Null graphical culture type for ", identifier); - return false; - } - return culture_groups.add_item({ identifier, leader, *graphical_culture_type, is_overseas }); -} - -void CultureManager::lock_culture_groups() { - culture_groups.lock(); -} - -CultureGroup const* CultureManager::get_culture_group_by_identifier(const std::string_view identifier) const { - return culture_groups.get_item_by_identifier(identifier); -} - -size_t CultureManager::get_culture_group_count() const { - return culture_groups.size(); -} - -std::vector const& CultureManager::get_culture_groups() const { - return culture_groups.get_items(); -} - -bool CultureManager::add_culture(const std::string_view identifier, colour_t colour, CultureGroup const* group, std::vector const& first_names, std::vector const& last_names) { - if (!culture_groups.is_locked()) { - Logger::error("Cannot register cultures until culture groups are locked!"); - return false; - } - if (identifier.empty()) { - Logger::error("Invalid culture identifier - empty!"); - return false; - } - if (group == nullptr) { - Logger::error("Null culture group for ", identifier); - return false; - } - if (colour > MAX_COLOUR_RGB) { - Logger::error("Invalid culture colour for ", identifier, ": ", colour_to_hex_string(colour)); - return false; - } - return cultures.add_item({ identifier, colour, *group, first_names, last_names }); -} - -void CultureManager::lock_cultures() { - cultures.lock(); -} - -Culture const* CultureManager::get_culture_by_identifier(const std::string_view identifier) const { - return cultures.get_item_by_identifier(identifier); -} - -size_t CultureManager::get_culture_count() const { - return cultures.size(); -} - -std::vector const& CultureManager::get_cultures() const { - return cultures.get_items(); -} - -bool CultureManager::load_graphical_culture_type_file(ast::NodeCPtr root) { - const bool ret = expect_list_reserve_length( - graphical_culture_types, - expect_identifier( - std::bind(&CultureManager::add_graphical_culture_type, this, std::placeholders::_1) - ) - )(root); - lock_graphical_culture_types(); - return ret; -} - -bool CultureManager::load_culture_file(ast::NodeCPtr root) { - if (!graphical_culture_types.is_locked()) { - Logger::error("Cannot load culture groups until graphical culture types are locked!"); - return false; - } - - static const std::string default_unit_graphical_culture_type_identifier = "Generic"; - GraphicalCultureType const* const default_unit_graphical_culture_type = get_graphical_culture_type_by_identifier(default_unit_graphical_culture_type_identifier); - if (default_unit_graphical_culture_type == nullptr) { - Logger::error("Failed to find default unit graphical culture type: ", default_unit_graphical_culture_type_identifier); - } - - size_t total_expected_cultures = 0; - bool ret = expect_dictionary_reserve_length( - culture_groups, - [this, default_unit_graphical_culture_type, &total_expected_cultures](std::string_view key, ast::NodeCPtr value) -> bool { - std::string_view leader; - GraphicalCultureType const* unit_graphical_culture_type = default_unit_graphical_culture_type; - bool is_overseas = true; - - bool ret = expect_dictionary_keys_and_length( - [&total_expected_cultures](size_t size) -> size_t { - total_expected_cultures += size; - return size; - }, - ALLOW_OTHER_KEYS, - "leader", ONE_EXACTLY, expect_identifier(assign_variable_callback(leader)), - "unit", ZERO_OR_ONE, - expect_identifier( - [this, &unit_graphical_culture_type](std::string_view identifier) -> bool { - unit_graphical_culture_type = get_graphical_culture_type_by_identifier(identifier); - if (unit_graphical_culture_type != nullptr) return true; - Logger::error("Invalid unit graphical culture type: ", identifier); - return false; - } - ), - "union", ZERO_OR_ONE, success_callback, - "is_overseas", ZERO_OR_ONE, expect_bool(assign_variable_callback(is_overseas)) - )(value); - ret &= add_culture_group(key, leader, unit_graphical_culture_type, is_overseas); - return ret; - } - )(root); - lock_culture_groups(); - cultures.reserve(cultures.size() + total_expected_cultures); - - ret &= expect_dictionary( - [this](std::string_view culture_group_key, ast::NodeCPtr culture_group_value) -> bool { - - CultureGroup const* culture_group = get_culture_group_by_identifier(culture_group_key); - - return expect_dictionary( - [this, culture_group](std::string_view key, ast::NodeCPtr value) -> bool { - if (key == "leader" || key == "unit" || key == "union" || key == "is_overseas") return true; - - colour_t colour = NULL_COLOUR; - std::vector first_names, last_names; - - bool ret = expect_dictionary_keys( - "color", ONE_EXACTLY, expect_colour(assign_variable_callback(colour)), - "first_names", ONE_EXACTLY, name_list_callback(first_names), - "last_names", ONE_EXACTLY, name_list_callback(last_names), - "radicalism", ZERO_OR_ONE, success_callback, - "primary", ZERO_OR_ONE, success_callback - )(value); - ret &= add_culture(key, colour, culture_group, first_names, last_names); - return ret; - } - )(culture_group_value); - } - )(root); - lock_cultures(); - return ret; -} diff --git a/src/openvic/pop/Culture.hpp b/src/openvic/pop/Culture.hpp deleted file mode 100644 index cc6c2a6..0000000 --- a/src/openvic/pop/Culture.hpp +++ /dev/null @@ -1,89 +0,0 @@ -#pragma once - -#include "openvic/dataloader/NodeTools.hpp" -#include "openvic/types/IdentifierRegistry.hpp" - -namespace OpenVic { - - struct CultureManager; - - struct GraphicalCultureType : HasIdentifier { - friend struct CultureManager; - - private: - GraphicalCultureType(const std::string_view new_identifier); - - public: - GraphicalCultureType(GraphicalCultureType&&) = default; - }; - - struct CultureGroup : HasIdentifier { - friend struct CultureManager; - - private: - const std::string leader; - GraphicalCultureType const& unit_graphical_culture_type; - const bool is_overseas; - - // TODO - union tag - - CultureGroup(const std::string_view new_identifier, const std::string_view new_leader, GraphicalCultureType const& new_unit_graphical_culture_type, bool new_is_overseas); - - public: - CultureGroup(CultureGroup&&) = default; - - std::string const& get_leader() const; - GraphicalCultureType const& get_unit_graphical_culture_type() const; - bool get_is_overseas() const; - }; - - struct Culture : HasIdentifierAndColour { - friend struct CultureManager; - - private: - CultureGroup const& group; - const std::vector first_names, last_names; - - // TODO - radicalism, primary tag - - Culture(const std::string_view new_identifier, colour_t new_colour, CultureGroup const& new_group, std::vector const& new_first_names, std::vector const& new_last_names); - - public: - Culture(Culture&&) = default; - - CultureGroup const& get_group() const; - std::vector const& get_first_names() const; - std::vector const& get_last_names() const; - }; - - struct CultureManager { - private: - IdentifierRegistry graphical_culture_types; - IdentifierRegistry culture_groups; - IdentifierRegistry cultures; - - public: - CultureManager(); - - bool add_graphical_culture_type(const std::string_view identifier); - void lock_graphical_culture_types(); - GraphicalCultureType const* get_graphical_culture_type_by_identifier(const std::string_view identifier) const; - size_t get_graphical_culture_type_count() const; - std::vector const& get_graphical_culture_types() const; - - bool add_culture_group(const std::string_view identifier, const std::string_view leader, GraphicalCultureType const* new_graphical_culture_type, bool is_overseas); - void lock_culture_groups(); - CultureGroup const* get_culture_group_by_identifier(const std::string_view identifier) const; - size_t get_culture_group_count() const; - std::vector const& get_culture_groups() const; - - bool add_culture(const std::string_view identifier, colour_t colour, CultureGroup const* group, std::vector const& first_names, std::vector const& last_names); - void lock_cultures(); - Culture const* get_culture_by_identifier(const std::string_view identifier) const; - size_t get_culture_count() const; - std::vector const& get_cultures() const; - - bool load_graphical_culture_type_file(ast::NodeCPtr root); - bool load_culture_file(ast::NodeCPtr root); - }; -} diff --git a/src/openvic/pop/Pop.cpp b/src/openvic/pop/Pop.cpp deleted file mode 100644 index fbe8708..0000000 --- a/src/openvic/pop/Pop.cpp +++ /dev/null @@ -1,195 +0,0 @@ -#include "Pop.hpp" - -#include - -#include "openvic/dataloader/NodeTools.hpp" -#include "openvic/map/Province.hpp" -#include "openvic/utility/Logger.hpp" - -using namespace OpenVic; -using namespace OpenVic::NodeTools; - -Pop::Pop(PopType const& new_type, Culture const& new_culture, Religion const& new_religion, pop_size_t new_size) - : type { new_type }, - culture { new_culture }, - religion { new_religion }, - size { new_size } { - assert(size > 0); -} - -PopType const& Pop::get_type() const { - return type; -} - -Culture const& Pop::get_culture() const { - return culture; -} - -Religion const& Pop::get_religion() const { - return religion; -} - -Pop::pop_size_t Pop::get_size() const { - return size; -} - -Pop::pop_size_t Pop::get_num_promoted() const { - return num_promoted; -} - -Pop::pop_size_t Pop::get_num_demoted() const { - return num_demoted; -} - -Pop::pop_size_t Pop::get_num_migrated() const { - return num_migrated; -} - -Pop::pop_size_t Pop::get_pop_daily_change() const { - return Pop::get_num_promoted() - (Pop::get_num_demoted() + Pop::get_num_migrated()); -} - -PopType::PopType(const std::string_view new_identifier, colour_t new_colour, - strata_t new_strata, sprite_t new_sprite, - Pop::pop_size_t new_max_size, Pop::pop_size_t new_merge_max_size, - bool new_state_capital_only, bool new_demote_migrant, bool new_is_artisan, bool new_is_slave) - : HasIdentifierAndColour { new_identifier, new_colour, true }, - strata { new_strata }, - sprite { new_sprite }, - max_size { new_max_size }, - merge_max_size { new_merge_max_size }, - state_capital_only { new_state_capital_only }, - demote_migrant { new_demote_migrant }, - is_artisan { new_is_artisan }, - is_slave { new_is_slave } { - assert(sprite > 0); - assert(max_size > 0); - assert(merge_max_size > 0); -} - -PopType::sprite_t PopType::get_sprite() const { - return sprite; -} - -PopType::strata_t PopType::get_strata() const { - return strata; -} - -Pop::pop_size_t PopType::get_max_size() const { - return max_size; -} - -Pop::pop_size_t PopType::get_merge_max_size() const { - return merge_max_size; -} - -bool PopType::get_state_capital_only() const { - return state_capital_only; -} - -bool PopType::get_demote_migrant() const { - return demote_migrant; -} - -bool PopType::get_is_artisan() const { - return is_artisan; -} - -bool PopType::get_is_slave() const { - return is_slave; -} - -PopManager::PopManager() : pop_types { "pop types" } {} - -bool PopManager::add_pop_type(const std::string_view identifier, colour_t colour, PopType::strata_t strata, PopType::sprite_t sprite, - Pop::pop_size_t max_size, Pop::pop_size_t merge_max_size, bool state_capital_only, bool demote_migrant, bool is_artisan, bool is_slave) { - if (identifier.empty()) { - Logger::error("Invalid pop type identifier - empty!"); - return false; - } - if (colour > MAX_COLOUR_RGB) { - Logger::error("Invalid pop type colour for ", identifier, ": ", colour_to_hex_string(colour)); - return false; - } - if (sprite <= 0) { - Logger::error("Invalid pop type sprite index for ", identifier, ": ", sprite); - return false; - } - if (max_size <= 0) { - Logger::error("Invalid pop type max size for ", identifier, ": ", max_size); - return false; - } - if (merge_max_size <= 0) { - Logger::error("Invalid pop type merge max size for ", identifier, ": ", merge_max_size); - return false; - } - return pop_types.add_item({ identifier, colour, strata, sprite, max_size, merge_max_size, state_capital_only, demote_migrant, is_artisan, is_slave }); -} - -void PopManager::lock_pop_types() { - pop_types.lock(); -} - -PopType const* PopManager::get_pop_type_by_identifier(const std::string_view identifier) const { - return pop_types.get_item_by_identifier(identifier); -} - -size_t PopManager::get_pop_type_count() const { - return pop_types.size(); -} - -std::vector const& PopManager::get_pop_types() const { - return pop_types.get_items(); -} - -bool PopManager::load_pop_type_file(std::filesystem::path const& path, ast::NodeCPtr root) { - - // TODO - pop type loading - - if (pop_types.empty()) - return add_pop_type("test_pop_type", 0xFF0000, PopType::strata_t::POOR, 1, 1, 1, false, false, false, false); - return true; -} - -bool PopManager::load_pop_into_province(Province& province, ast::NodeCPtr root) const { - static PopType const* type = get_pop_type_by_identifier("test_pop_type"); - Culture const* culture = nullptr; - Religion const* religion = nullptr; - Pop::pop_size_t size = 0; - - bool ret = expect_assign( - [this, &culture, &religion, &size](std::string_view, ast::NodeCPtr pop_node) -> bool { - return expect_dictionary_keys( - "culture", ONE_EXACTLY, - expect_identifier( - [&culture, this](std::string_view identifier) -> bool { - culture = culture_manager.get_culture_by_identifier(identifier); - if (culture != nullptr) return true; - Logger::error("Invalid pop culture: ", identifier); - return false; - } - ), - "religion", ONE_EXACTLY, - expect_identifier( - [&religion, this](std::string_view identifier) -> bool { - religion = religion_manager.get_religion_by_identifier(identifier); - if (religion != nullptr) return true; - Logger::error("Invalid pop religion: ", identifier); - return false; - } - ), - "size", ONE_EXACTLY, expect_uint(assign_variable_callback_uint("pop size", size)), - "militancy", ZERO_OR_ONE, success_callback, - "rebel_type", ZERO_OR_ONE, success_callback - )(pop_node); - } - )(root); - - if (type != nullptr && culture != nullptr && religion != nullptr && size > 0) { - ret &= province.add_pop({ *type, *culture, *religion, size }); - } else { - Logger::error("Some pop arguments are invalid: type = ", type, ", culture = ", culture, ", religion = ", religion, ", size = ", size); - ret = false; - } - return ret; -} diff --git a/src/openvic/pop/Pop.hpp b/src/openvic/pop/Pop.hpp deleted file mode 100644 index f4694f0..0000000 --- a/src/openvic/pop/Pop.hpp +++ /dev/null @@ -1,104 +0,0 @@ -#pragma once - -#include - -#include "openvic/pop/Culture.hpp" -#include "openvic/pop/Religion.hpp" - -namespace OpenVic { - - struct PopManager; - struct PopType; - - /* REQUIREMENTS: - * POP-18, POP-19, POP-20, POP-21 - */ - struct Pop { - friend struct PopManager; - - using pop_size_t = int64_t; - - private: - PopType const& type; - Culture const& culture; - Religion const& religion; - pop_size_t size, num_promoted, num_demoted, num_migrated; - - Pop(PopType const& new_type, Culture const& new_culture, Religion const& new_religion, pop_size_t new_size); - - public: - Pop(Pop const&) = delete; - Pop(Pop&&) = default; - Pop& operator=(Pop const&) = delete; - Pop& operator=(Pop&&) = delete; - - PopType const& get_type() const; - Culture const& get_culture() const; - Religion const& get_religion() const; - pop_size_t get_size() const; - pop_size_t get_num_promoted() const; - pop_size_t get_num_demoted() const; - pop_size_t get_num_migrated() const; - pop_size_t get_pop_daily_change() const; - }; - - /* REQUIREMENTS: - * POP-26 - */ - struct PopType : HasIdentifierAndColour { - friend struct PopManager; - - using sprite_t = uint8_t; - - private: - const enum class strata_t { - POOR, - MIDDLE, - RICH - } strata; - const sprite_t sprite; - const Pop::pop_size_t max_size, merge_max_size; - const bool state_capital_only, demote_migrant, is_artisan, is_slave; - - // TODO - rebel composition, life/everyday/luxury needs, country and province migration targets, promote_to targets, ideologies and issues - - PopType(const std::string_view new_identifier, colour_t new_colour, strata_t new_strata, sprite_t new_sprite, Pop::pop_size_t new_max_size, Pop::pop_size_t new_merge_max_size, - bool new_state_capital_only, bool new_demote_migrant, bool new_is_artisan, bool new_is_slave); - - public: - PopType(PopType&&) = default; - - strata_t get_strata() const; - sprite_t get_sprite() const; - Pop::pop_size_t get_max_size() const; - Pop::pop_size_t get_merge_max_size() const; - bool get_state_capital_only() const; - bool get_demote_migrant() const; - bool get_is_artisan() const; - bool get_is_slave() const; - }; - - struct Province; - - struct PopManager { - private: - IdentifierRegistry pop_types; - - public: - CultureManager culture_manager; - ReligionManager religion_manager; - - PopManager(); - - bool add_pop_type(const std::string_view identifier, colour_t new_colour, PopType::strata_t strata, PopType::sprite_t sprite, - Pop::pop_size_t max_size, Pop::pop_size_t merge_max_size, bool state_capital_only, bool demote_migrant, - bool is_artisan, bool is_slave); - void lock_pop_types(); - PopType const* get_pop_type_by_identifier(const std::string_view identifier) const; - size_t get_pop_type_count() const; - std::vector const& get_pop_types() const; - - bool load_pop_type_file(std::filesystem::path const& path, ast::NodeCPtr root); - bool load_pop_into_province(Province& province, ast::NodeCPtr root) const; - }; -} diff --git a/src/openvic/pop/Religion.cpp b/src/openvic/pop/Religion.cpp deleted file mode 100644 index f7c657a..0000000 --- a/src/openvic/pop/Religion.cpp +++ /dev/null @@ -1,142 +0,0 @@ -#include "Religion.hpp" - -#include - -using namespace OpenVic; -using namespace OpenVic::NodeTools; - -ReligionGroup::ReligionGroup(const std::string_view new_identifier) : HasIdentifier { new_identifier } {} - -Religion::Religion(const std::string_view new_identifier, colour_t new_colour, - ReligionGroup const& new_group, icon_t new_icon, bool new_pagan) - : HasIdentifierAndColour { new_identifier, new_colour, true }, - group { new_group }, - icon { new_icon }, - pagan { new_pagan } { - assert(icon > 0); -} - -ReligionGroup const& Religion::get_group() const { - return group; -} - -Religion::icon_t Religion::get_icon() const { - return icon; -} - -bool Religion::get_pagan() const { - return pagan; -} - -ReligionManager::ReligionManager() - : religion_groups { "religion groups" }, - religions { "religions" } {} - -bool ReligionManager::add_religion_group(const std::string_view identifier) { - if (identifier.empty()) { - Logger::error("Invalid religion group identifier - empty!"); - return false; - } - return religion_groups.add_item({ identifier }); -} - -void ReligionManager::lock_religion_groups() { - religion_groups.lock(); -} - -ReligionGroup const* ReligionManager::get_religion_group_by_identifier(const std::string_view identifier) const { - return religion_groups.get_item_by_identifier(identifier); -} - -size_t ReligionManager::get_religion_group_count() const { - return religion_groups.size(); -} - -std::vector const& ReligionManager::get_religion_groups() const { - return religion_groups.get_items(); -} - -bool ReligionManager::add_religion(const std::string_view identifier, colour_t colour, ReligionGroup const* group, Religion::icon_t icon, bool pagan) { - if (!religion_groups.is_locked()) { - Logger::error("Cannot register religions until religion groups are locked!"); - return false; - } - if (identifier.empty()) { - Logger::error("Invalid religion identifier - empty!"); - return false; - } - if (group == nullptr) { - Logger::error("Null religion group for ", identifier); - return false; - } - if (colour > MAX_COLOUR_RGB) { - Logger::error("Invalid religion colour for ", identifier, ": ", colour_to_hex_string(colour)); - return false; - } - if (icon <= 0) { - Logger::error("Invalid religion icon for ", identifier, ": ", icon); - return false; - } - return religions.add_item({ identifier, colour, *group, icon, pagan }); -} - -void ReligionManager::lock_religions() { - religions.lock(); -} - -Religion const* ReligionManager::get_religion_by_identifier(const std::string_view identifier) const { - return religions.get_item_by_identifier(identifier); -} - -size_t ReligionManager::get_religion_count() const { - return religions.size(); -} - -std::vector const& ReligionManager::get_religions() const { - return religions.get_items(); -} - -bool ReligionManager::load_religion_file(ast::NodeCPtr root) { - - size_t total_expected_religions = 0; - bool ret = expect_dictionary_reserve_length( - religion_groups, - [this, &total_expected_religions](std::string_view key, ast::NodeCPtr value) -> bool { - bool ret = expect_list_and_length( - [&total_expected_religions](size_t size) -> size_t { - total_expected_religions += size; - return 0; - }, - success_callback - )(value); - ret &= add_religion_group(key); - return ret; - } - )(root); - lock_religion_groups(); - religions.reserve(religions.size() + total_expected_religions); - ret &= expect_dictionary( - [this](std::string_view religion_group_key, ast::NodeCPtr religion_group_value) -> bool { - - ReligionGroup const* religion_group = get_religion_group_by_identifier(religion_group_key); - - return expect_dictionary( - [this, religion_group](std::string_view key, ast::NodeCPtr value) -> bool { - colour_t colour = NULL_COLOUR; - Religion::icon_t icon = 0; - bool pagan = false; - - bool ret = expect_dictionary_keys( - "icon", ONE_EXACTLY, expect_uint(assign_variable_callback_uint("religion icon", icon)), - "color", ONE_EXACTLY, expect_colour(assign_variable_callback(colour)), - "pagan", ZERO_OR_ONE, expect_bool(assign_variable_callback(pagan)) - )(value); - ret &= add_religion(key, colour, religion_group, icon, pagan); - return ret; - } - )(religion_group_value); - } - )(root); - lock_religions(); - return ret; -} diff --git a/src/openvic/pop/Religion.hpp b/src/openvic/pop/Religion.hpp deleted file mode 100644 index f04b035..0000000 --- a/src/openvic/pop/Religion.hpp +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once - -#include "openvic/types/IdentifierRegistry.hpp" -#include "openvic/dataloader/NodeTools.hpp" - -namespace OpenVic { - - struct ReligionManager; - - struct ReligionGroup : HasIdentifier { - friend struct ReligionManager; - - private: - ReligionGroup(const std::string_view new_identifier); - - public: - ReligionGroup(ReligionGroup&&) = default; - }; - - struct Religion : HasIdentifierAndColour { - friend struct ReligionManager; - - using icon_t = uint8_t; - - private: - ReligionGroup const& group; - const icon_t icon; - const bool pagan; - - Religion(const std::string_view new_identifier, colour_t new_colour, ReligionGroup const& new_group, icon_t new_icon, bool new_pagan); - - public: - Religion(Religion&&) = default; - - ReligionGroup const& get_group() const; - icon_t get_icon() const; - bool get_pagan() const; - }; - - struct ReligionManager { - private: - IdentifierRegistry religion_groups; - IdentifierRegistry religions; - - public: - ReligionManager(); - - bool add_religion_group(const std::string_view identifier); - void lock_religion_groups(); - ReligionGroup const* get_religion_group_by_identifier(const std::string_view identifier) const; - size_t get_religion_group_count() const; - std::vector const& get_religion_groups() const; - - bool add_religion(const std::string_view identifier, colour_t colour, ReligionGroup const* group, Religion::icon_t icon, bool pagan); - void lock_religions(); - Religion const* get_religion_by_identifier(const std::string_view identifier) const; - size_t get_religion_count() const; - std::vector const& get_religions() const; - - bool load_religion_file(ast::NodeCPtr root); - }; -} diff --git a/src/openvic/types/Colour.hpp b/src/openvic/types/Colour.hpp deleted file mode 100644 index 01f3852..0000000 --- a/src/openvic/types/Colour.hpp +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -namespace OpenVic { - // Represents a 24-bit RGB integer OR a 32-bit ARGB integer - using colour_t = uint32_t; - /* When colour_t is used as an identifier, NULL_COLOUR is disallowed - * and should be reserved as an error value. - * When colour_t is used in a purely graphical context, NULL_COLOUR - * should be allowed. - */ - static constexpr colour_t NULL_COLOUR = 0, FULL_COLOUR = 0xFF, MAX_COLOUR_RGB = 0xFFFFFF; - constexpr colour_t float_to_colour_byte(float f, float min = 0.0f, float max = 1.0f) { - return static_cast(std::clamp(min + f * (max - min), min, max) * 255.0f); - } - constexpr colour_t fraction_to_colour_byte(int n, int d, float min = 0.0f, float max = 1.0f) { - return float_to_colour_byte(static_cast(n) / static_cast(d), min, max); - } - constexpr colour_t float_to_alpha_value(float a) { - return float_to_colour_byte(a) << 24; - } - constexpr float colour_byte_to_float(colour_t colour) { - return std::clamp(static_cast(colour) / 255.0f, 0.0f, 1.0f); - } - - inline std::string colour_to_hex_string(colour_t colour) { - std::ostringstream stream; - stream << std::hex << std::setfill('0') << std::setw(6) << colour; - return stream.str(); - } -} diff --git a/src/openvic/types/Date.cpp b/src/openvic/types/Date.cpp deleted file mode 100644 index 1f13808..0000000 --- a/src/openvic/types/Date.cpp +++ /dev/null @@ -1,262 +0,0 @@ -#include "Date.hpp" - -#include -#include -#include -#include - -#include "openvic/utility/Logger.hpp" -#include "openvic/utility/StringUtils.hpp" - -using namespace OpenVic; - -Timespan::Timespan(day_t value) : days { value } {} - -bool Timespan::operator<(Timespan other) const { return days < other.days; }; -bool Timespan::operator>(Timespan other) const { return days > other.days; }; -bool Timespan::operator<=(Timespan other) const { return days <= other.days; }; -bool Timespan::operator>=(Timespan other) const { return days >= other.days; }; -bool Timespan::operator==(Timespan other) const { return days == other.days; }; -bool Timespan::operator!=(Timespan other) const { return days != other.days; }; - -Timespan Timespan::operator+(Timespan other) const { return days + other.days; } - -Timespan Timespan::operator-(Timespan other) const { return days - other.days; } - -Timespan Timespan::operator*(day_t factor) const { return days * factor; } - -Timespan Timespan::operator/(day_t factor) const { return days / factor; } - -Timespan& Timespan::operator+=(Timespan other) { - days += other.days; - return *this; -} - -Timespan& Timespan::operator-=(Timespan other) { - days -= other.days; - return *this; -} - -Timespan& Timespan::operator++() { - days++; - return *this; -} - -Timespan Timespan::operator++(int) { - Timespan old = *this; - ++(*this); - return old; -} - -Timespan::operator day_t() const { - return days; -} - -Timespan::operator double() const { - return days; -} - -std::string Timespan::to_string() const { - return std::to_string(days); -} - -Timespan::operator std::string() const { - return to_string(); -} - -std::ostream& OpenVic::operator<<(std::ostream& out, Timespan const& timespan) { - return out << timespan.to_string(); -} - -Timespan Date::_dateToTimespan(year_t year, month_t month, day_t day) { - month = std::clamp(month, 1, MONTHS_IN_YEAR); - day = std::clamp(day, 1, DAYS_IN_MONTH[month - 1]); - return year * DAYS_IN_YEAR + DAYS_UP_TO_MONTH[month - 1] + day - 1; -} - -Timespan::day_t const* Date::DAYS_UP_TO_MONTH = generate_days_up_to_month(); - -Timespan::day_t const* Date::generate_days_up_to_month() { - static Timespan::day_t days_up_to_month[MONTHS_IN_YEAR]; - Timespan::day_t days = 0; - for (int month = 0; month < MONTHS_IN_YEAR; - days_up_to_month[month] = days, days += DAYS_IN_MONTH[month++]); - assert(days == DAYS_IN_YEAR); - return days_up_to_month; -} - -Date::month_t const* Date::MONTH_FROM_DAY_IN_YEAR = generate_month_from_day_in_year(); - -Date::month_t const* Date::generate_month_from_day_in_year() { - static month_t month_from_day_in_year[DAYS_IN_YEAR]; - Timespan::day_t days_left = 0; - for (int day = 0, month = 0; day < DAYS_IN_YEAR; - days_left = (days_left > 0 ? days_left : DAYS_IN_MONTH[month++]) - 1, - month_from_day_in_year[day++] = month); - assert(days_left == 0); - assert(month_from_day_in_year[DAYS_IN_YEAR - 1] == MONTHS_IN_YEAR); - return month_from_day_in_year; -} - -Date::Date(Timespan total_days) : timespan { total_days } { - if (timespan < 0) { - Logger::error("Invalid timespan for date: ", timespan, " (cannot be negative)"); - timespan = 0; - } -} - -Date::Date(year_t year, month_t month, day_t day) : timespan { _dateToTimespan(year, month, day) } {} - -Date::year_t Date::getYear() const { - return static_cast(timespan) / DAYS_IN_YEAR; -} - -Date::month_t Date::getMonth() const { - return MONTH_FROM_DAY_IN_YEAR[static_cast(timespan) % DAYS_IN_YEAR]; -} - -Date::day_t Date::getDay() const { - return (static_cast(timespan) % DAYS_IN_YEAR) - DAYS_UP_TO_MONTH[getMonth() - 1] + 1; -} - -bool Date::operator<(Date other) const { return timespan < other.timespan; }; -bool Date::operator>(Date other) const { return timespan > other.timespan; }; -bool Date::operator<=(Date other) const { return timespan <= other.timespan; }; -bool Date::operator>=(Date other) const { return timespan >= other.timespan; }; -bool Date::operator==(Date other) const { return timespan == other.timespan; }; -bool Date::operator!=(Date other) const { return timespan != other.timespan; }; - -Date Date::operator+(Timespan other) const { return timespan + other; } - -Timespan Date::operator-(Date other) const { return timespan - other.timespan; } - -Date& Date::operator+=(Timespan other) { - timespan += other; - return *this; -} - -Date& Date::operator-=(Timespan other) { - timespan -= other; - return *this; -} - -Date& Date::operator++() { - timespan++; - return *this; -} - -Date Date::operator++(int) { - Date old = *this; - ++(*this); - return old; -} - -std::string Date::to_string() const { - std::stringstream ss; - ss << *this; - return ss.str(); -} - -Date::operator std::string() const { - return to_string(); -} - -std::ostream& OpenVic::operator<<(std::ostream& out, Date const& date) { - return out << static_cast(date.getYear()) << Date::SEPARATOR_CHARACTER << static_cast(date.getMonth()) << Date::SEPARATOR_CHARACTER << static_cast(date.getDay()); -} - -// Parsed from string of the form YYYY.MM.DD -Date Date::from_string(char const* const str, char const* const end, bool* successful) { - if (successful != nullptr) *successful = true; - - year_t year = 0; - month_t month = 1; - day_t day = 1; - - if (str == nullptr || end <= str) { - Logger::error("Invalid string start/end pointers: ", static_cast(str), " - ", static_cast(end)); - if (successful != nullptr) *successful = false; - return { year, month, day }; - } - - char const* year_end = str; - while (std::isdigit(*year_end) && ++year_end < end); - - if (year_end <= str) { - Logger::error("Failed to find year digits in date: ", std::string_view { str, static_cast(end - str) }); - if (successful != nullptr) *successful = false; - return { year, month, day }; - } - - bool sub_successful = false; - uint64_t val = StringUtils::string_to_uint64(str, year_end, &sub_successful, 10); - if (!sub_successful || val >= 1 << (8 * sizeof(year_t))) { - Logger::error("Failed to read year: ", std::string_view { str, static_cast(end - str) }); - if (successful != nullptr) *successful = false; - return { year, month, day }; - } - year = val; - if (year_end < end) { - if (*year_end == SEPARATOR_CHARACTER) { - char const* const month_start = year_end + 1; - char const* month_end = month_start; - if (month_start < end) { - while (std::isdigit(*month_end) && ++month_end < end); - } - if (month_start >= month_end) { - Logger::error("Failed to find month digits in date: ", std::string_view { str, static_cast(end - str) }); - if (successful != nullptr) *successful = false; - } else { - sub_successful = false; - val = StringUtils::string_to_uint64(month_start, month_end, &sub_successful, 10); - if (!sub_successful || val < 1 || val > MONTHS_IN_YEAR) { - Logger::error("Failed to read month: ", std::string_view { str, static_cast(end - str) }); - if (successful != nullptr) *successful = false; - } else { - month = val; - if (month_end < end) { - if (*month_end == SEPARATOR_CHARACTER) { - char const* const day_start = month_end + 1; - char const* day_end = day_start; - if (day_start < end) { - while (std::isdigit(*day_end) && ++day_end < end); - } - if (day_start >= day_end) { - Logger::error("Failed to find day digits in date: ", std::string_view { str, static_cast(end - str) }); - if (successful != nullptr) *successful = false; - } else { - sub_successful = false; - val = StringUtils::string_to_uint64(day_start, day_end, &sub_successful); - if (!sub_successful || val < 1 || val > DAYS_IN_MONTH[month - 1]) { - Logger::error("Failed to read day: ", std::string_view { str, static_cast(end - str) }); - if (successful != nullptr) *successful = false; - } else { - day = val; - if (day_end < end) { - Logger::error("Unexpected string \"", std::string_view { day_end, static_cast(end - day_end) }, "\" at the end of date ", std::string_view { str, static_cast(end - str) }); - if (successful != nullptr) *successful = false; - } - } - } - } else { - Logger::error("Unexpected character \"", *month_end, "\" in month of date ", std::string_view { str, static_cast(end - str) }); - if (successful != nullptr) *successful = false; - } - } - } - } - } else { - Logger::error("Unexpected character \"", *year_end, "\" in year of date ", std::string_view { str, static_cast(end - str) }); - if (successful != nullptr) *successful = false; - } - } - return { year, month, day }; -}; - -Date Date::from_string(char const* str, size_t length, bool* successful) { - return from_string(str, str + length, successful); -} - -Date Date::from_string(const std::string_view str, bool* successful) { - return from_string(str.data(), str.length(), successful); -} diff --git a/src/openvic/types/Date.hpp b/src/openvic/types/Date.hpp deleted file mode 100644 index 601f9dc..0000000 --- a/src/openvic/types/Date.hpp +++ /dev/null @@ -1,96 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace OpenVic { - // A relative period between points in time, measured in days - struct Timespan { - using day_t = int64_t; - - private: - day_t days; - - public: - Timespan(day_t value = 0); - - bool operator<(Timespan other) const; - bool operator>(Timespan other) const; - bool operator<=(Timespan other) const; - bool operator>=(Timespan other) const; - bool operator==(Timespan other) const; - bool operator!=(Timespan other) const; - - Timespan operator+(Timespan other) const; - Timespan operator-(Timespan other) const; - Timespan operator*(day_t factor) const; - Timespan operator/(day_t factor) const; - Timespan& operator+=(Timespan other); - Timespan& operator-=(Timespan other); - Timespan& operator++(); - Timespan operator++(int); - - explicit operator day_t() const; - explicit operator double() const; - std::string to_string() const; - explicit operator std::string() const; - }; - std::ostream& operator<<(std::ostream& out, Timespan const& timespan); - - // Represents an in-game date - // Note: Current implementation does not account for leap-years, or dates before Year 0 - struct Date { - using year_t = uint16_t; - using month_t = uint8_t; - using day_t = uint8_t; - - static constexpr Timespan::day_t MONTHS_IN_YEAR = 12; - static constexpr Timespan::day_t DAYS_IN_YEAR = 365; - static constexpr Timespan::day_t DAYS_IN_MONTH[MONTHS_IN_YEAR] { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; - static Timespan::day_t const* DAYS_UP_TO_MONTH; - static month_t const* MONTH_FROM_DAY_IN_YEAR; - - static constexpr char SEPARATOR_CHARACTER = '.'; - - private: - // Number of days since Jan 1st, Year 0 - Timespan timespan; - - static Timespan _dateToTimespan(year_t year, month_t month, day_t day); - static Timespan::day_t const* generate_days_up_to_month(); - static month_t const* generate_month_from_day_in_year(); - - public: - // The Timespan is considered to be the number of days since Jan 1st, Year 0 - Date(Timespan total_days); - // Year month day specification - Date(year_t year = 0, month_t month = 1, day_t day = 1); - - year_t getYear() const; - month_t getMonth() const; - day_t getDay() const; - - bool operator<(Date other) const; - bool operator>(Date other) const; - bool operator<=(Date other) const; - bool operator>=(Date other) const; - bool operator==(Date other) const; - bool operator!=(Date other) const; - - Date operator+(Timespan other) const; - Timespan operator-(Date other) const; - Date& operator+=(Timespan other); - Date& operator-=(Timespan other); - Date& operator++(); - Date operator++(int); - - std::string to_string() const; - explicit operator std::string() const; - // Parsed from string of the form YYYY.MM.DD - static Date from_string(char const* str, char const* end, bool* successful = nullptr); - static Date from_string(char const* str, size_t length, bool* successful = nullptr); - static Date from_string(const std::string_view str, bool* successful = nullptr); - }; - std::ostream& operator<<(std::ostream& out, Date const& date); -} diff --git a/src/openvic/types/IdentifierRegistry.cpp b/src/openvic/types/IdentifierRegistry.cpp deleted file mode 100644 index 6d5221b..0000000 --- a/src/openvic/types/IdentifierRegistry.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include "IdentifierRegistry.hpp" - -#include - -using namespace OpenVic; - -HasIdentifier::HasIdentifier(const std::string_view new_identifier) - : identifier { new_identifier } { - assert(!identifier.empty()); -} - -std::string const& HasIdentifier::get_identifier() const { - return identifier; -} - -HasColour::HasColour(colour_t const new_colour, bool can_be_null) : colour(new_colour) { - assert((can_be_null || colour != NULL_COLOUR) && colour <= MAX_COLOUR_RGB); -} - -colour_t HasColour::get_colour() const { return colour; } - -std::string HasColour::colour_to_hex_string() const { - return OpenVic::colour_to_hex_string(colour); -} - -HasIdentifierAndColour::HasIdentifierAndColour(const std::string_view new_identifier, - const colour_t new_colour, bool can_be_null) - : HasIdentifier { new_identifier }, - HasColour { new_colour, can_be_null } {} - -distribution_t::value_type OpenVic::get_largest_item(distribution_t const& dist) { - const distribution_t::const_iterator result = std::max_element(dist.begin(), dist.end(), - [](distribution_t::value_type a, distribution_t::value_type b) -> bool { - return a.second < b.second; - }); - return result != dist.end() ? *result : distribution_t::value_type { nullptr, -1.0f }; -} diff --git a/src/openvic/types/IdentifierRegistry.hpp b/src/openvic/types/IdentifierRegistry.hpp deleted file mode 100644 index 502b74b..0000000 --- a/src/openvic/types/IdentifierRegistry.hpp +++ /dev/null @@ -1,152 +0,0 @@ -#pragma once - -#include -#include - -#include "openvic/types/Colour.hpp" -#include "openvic/utility/Logger.hpp" - -namespace OpenVic { - /* - * Base class for objects with a non-empty string identifier, - * uniquely named instances of which can be entered into an - * IdentifierRegistry instance. - */ - class HasIdentifier { - const std::string identifier; - - protected: - HasIdentifier(const std::string_view new_identifier); - - public: - HasIdentifier(HasIdentifier const&) = delete; - HasIdentifier(HasIdentifier&&) = default; - HasIdentifier& operator=(HasIdentifier const&) = delete; - HasIdentifier& operator=(HasIdentifier&&) = delete; - - std::string const& get_identifier() const; - }; - - /* - * Base class for objects with associated colour information. - */ - class HasColour { - const colour_t colour; - - protected: - HasColour(const colour_t new_colour, bool can_be_null); - - public: - HasColour(HasColour const&) = delete; - HasColour(HasColour&&) = default; - HasColour& operator=(HasColour const&) = delete; - HasColour& operator=(HasColour&&) = delete; - - colour_t get_colour() const; - std::string colour_to_hex_string() const; - }; - - /* - * Base class for objects with a unique string identifier - * and associated colour information. - */ - class HasIdentifierAndColour : public HasIdentifier, public HasColour { - protected: - HasIdentifierAndColour(const std::string_view new_identifier, const colour_t new_colour, bool can_be_null); - - public: - HasIdentifierAndColour(HasIdentifierAndColour const&) = delete; - HasIdentifierAndColour(HasIdentifierAndColour&&) = default; - HasIdentifierAndColour& operator=(HasIdentifierAndColour const&) = delete; - HasIdentifierAndColour& operator=(HasIdentifierAndColour&&) = delete; - }; - - using distribution_t = std::map; - - distribution_t::value_type get_largest_item(distribution_t const& dist); - - /* - * Template for a list of objects with unique string identifiers that can - * be locked to prevent any further additions. The template argument T is - * the type of object that the registry will store, and the second part ensures - * that HasIdentifier is a base class of T. - */ - template - requires(std::derived_from) - class IdentifierRegistry { - using identifier_index_map_t = std::map>; - - const std::string name; - std::vector items; - bool locked = false; - identifier_index_map_t identifier_index_map; - - public: - IdentifierRegistry(const std::string_view new_name) : name { new_name } {} - bool add_item(T&& item) { - if (locked) { - Logger::error("Cannot add item to the ", name, " registry - locked!"); - return false; - } - T const* old_item = get_item_by_identifier(item.get_identifier()); - if (old_item != nullptr) { - Logger::error("Cannot add item to the ", name, " registry - an item with the identifier \"", item.get_identifier(), "\" already exists!"); - return false; - } - identifier_index_map[item.get_identifier()] = items.size(); - items.push_back(std::move(item)); - return true; - } - void lock(bool log = true) { - if (locked) { - Logger::error("Failed to lock ", name, " registry - already locked!"); - } else { - locked = true; - if (log) Logger::info("Locked ", name, " registry after registering ", size(), " items"); - } - } - bool is_locked() const { - return locked; - } - void reset() { - identifier_index_map.clear(); - items.clear(); - locked = false; - } - size_t size() const { - return items.size(); - } - bool empty() const { - return items.empty(); - } - void reserve(size_t size) { - if (locked) { - Logger::error("Failed to reserve space for ", size, " items in ", name, " registry - already locked!"); - } else { - items.reserve(size); - } - } - T* get_item_by_identifier(const std::string_view identifier) { - const identifier_index_map_t::const_iterator it = identifier_index_map.find(identifier); - if (it != identifier_index_map.end()) return &items[it->second]; - return nullptr; - } - T const* get_item_by_identifier(const std::string_view identifier) const { - const identifier_index_map_t::const_iterator it = identifier_index_map.find(identifier); - if (it != identifier_index_map.end()) return &items[it->second]; - return nullptr; - } - T* get_item_by_index(size_t index) { - return index < items.size() ? &items[index] : nullptr; - } - T const* get_item_by_index(size_t index) const { - return index < items.size() ? &items[index] : nullptr; - } - std::vector& get_items() { - return items; - } - std::vector const& get_items() const { - return items; - } - }; -} diff --git a/src/openvic/types/fixed_point/FP.hpp b/src/openvic/types/fixed_point/FP.hpp deleted file mode 100644 index 42ddf79..0000000 --- a/src/openvic/types/fixed_point/FP.hpp +++ /dev/null @@ -1,581 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "openvic/utility/Logger.hpp" -#include "openvic/utility/NumberUtils.hpp" -#include "openvic/utility/StringUtils.hpp" - -#include "FPLUT.hpp" - -namespace OpenVic { - struct FP { - static constexpr size_t SIZE = 8; - - constexpr FP() : value { 0 } {} - constexpr FP(int64_t new_value) : value { new_value } {} - constexpr FP(int32_t new_value) : value { static_cast(new_value) << FPLUT::PRECISION } {} - - // Trivial destructor - ~FP() = default; - - static constexpr FP max() { - return std::numeric_limits::max(); - } - - static constexpr FP min() { - return std::numeric_limits::min(); - } - - static constexpr FP usable_max() { - return static_cast(2147483648LL); - } - - static constexpr FP usable_min() { - return -usable_max(); - } - - static constexpr FP _0() { - return 0; - } - - static constexpr FP _1() { - return 1; - } - - static constexpr FP _2() { - return 2; - } - - static constexpr FP _3() { - return 3; - } - - static constexpr FP _4() { - return 4; - } - - static constexpr FP _5() { - return 5; - } - - static constexpr FP _6() { - return 6; - } - - static constexpr FP _7() { - return 7; - } - - static constexpr FP _8() { - return 8; - } - - static constexpr FP _9() { - return 9; - } - - static constexpr FP _10() { - return 10; - } - - static constexpr FP _50() { - return 50; - } - - static constexpr FP _100() { - return 100; - } - - static constexpr FP _200() { - return 200; - } - - static constexpr FP _0_01() { - return _1() / _100(); - } - - static constexpr FP _0_02() { - return _0_01() * 2; - } - - static constexpr FP _0_03() { - return _0_01() * 3; - } - - static constexpr FP _0_04() { - return _0_01() * 4; - } - - static constexpr FP _0_05() { - return _0_01() * 5; - } - - static constexpr FP _0_10() { - return _1() / 10; - } - - static constexpr FP _0_20() { - return _0_10() * 2; - } - - static constexpr FP _0_25() { - return _1() / 4; - } - - static constexpr FP _0_33() { - return _1() / 3; - } - - static constexpr FP _0_50() { - return _1() / 2; - } - - static constexpr FP _0_75() { - return _1() - _0_25(); - } - - static constexpr FP _0_95() { - return _1() - _0_05(); - } - - static constexpr FP _0_99() { - return _1() - _0_01(); - } - - static constexpr FP _1_01() { - return _1() + _0_01(); - } - - static constexpr FP _1_10() { - return _1() + _0_10(); - } - - static constexpr FP _1_50() { - return _1() + _0_50(); - } - - static constexpr FP minus_one() { - return -1; - } - - static constexpr FP pi() { - return static_cast(205887LL); - } - - static constexpr FP pi2() { - return pi() * 2; - } - - static constexpr FP pi_quarter() { - return pi() / 4; - } - - static constexpr FP pi_half() { - return pi() / 2; - } - - static constexpr FP one_div_pi2() { - return 1 / pi2(); - } - - static constexpr FP deg2rad() { - return static_cast(1143LL); - } - - static constexpr FP rad2deg() { - return static_cast(3754936LL); - } - - static constexpr FP e() { - return static_cast(178145LL); - } - - constexpr bool is_negative() const { - return value < 0; - } - - constexpr FP abs() const { - return !is_negative() ? value : -value; - } - - // Doesn't account for sign, so -n.abc -> 1 - 0.abc - constexpr FP get_frac() const { - return value & (FPLUT::ONE - 1); - } - - constexpr int64_t to_int64_t() const { - return value >> FPLUT::PRECISION; - } - - constexpr void set_raw_value(int64_t value) { - this->value = value; - } - - constexpr int64_t get_raw_value() const { - return value; - } - - constexpr int32_t to_int32_t() const { - return static_cast(to_int64_t()); - } - - constexpr float to_float() const { - return value / static_cast(FPLUT::ONE); - } - - constexpr float to_float_rounded() const { - return static_cast(NumberUtils::round_to_int64((value / static_cast(FPLUT::ONE)) * 100000.0f)) / 100000.0f; - } - - constexpr double to_double() const { - return value / static_cast(FPLUT::ONE); - } - - constexpr float to_double_rounded() const { - return NumberUtils::round_to_int64((value / static_cast(FPLUT::ONE)) * 100000.0) / 100000.0; - } - - std::string to_string() const { - FP val = abs(); - std::string str = std::to_string(val.to_int64_t()) + "."; - if (is_negative()) str = "-" + str; - val = val.get_frac(); - do { - val *= 10; - str.push_back('0' + static_cast(val.to_int64_t())); - val = val.get_frac(); - } while (val > 0); - return str; - } - - // Deterministic - static constexpr FP parse_raw(int64_t value) { - return value; - } - - // Deterministic - static constexpr FP parse(int64_t value) { - return value << FPLUT::PRECISION; - } - - // Deterministic - static constexpr FP parse(char const* str, char const* const end, bool* successful = nullptr) { - if (successful != nullptr) *successful = false; - - if (str == nullptr || str >= end) { - return _0(); - } - - bool negative = false; - - if (*str == '-') { - negative = true; - ++str; - if (str == end) return _0(); - } - - char const* dot_pointer = str; - while (*dot_pointer != '.' && ++dot_pointer != end); - - if (dot_pointer == str && dot_pointer + 1 == end) { - // Invalid: ".", "+." or "-." - return _0(); - } - - FP result = _0(); - if (successful != nullptr) *successful = true; - - if (dot_pointer != str) { - // Non-empty integer part - bool int_successful = false; - result += parse_integer(str, dot_pointer, &int_successful); - if (!int_successful && successful != nullptr) *successful = false; - } - - if (dot_pointer + 1 < end) { - // Non-empty fractional part - bool frac_successful = false; - result += parse_fraction(dot_pointer + 1, end, &frac_successful); - if (!frac_successful && successful != nullptr) *successful = false; - } - - return negative ? -result : result; - } - - static constexpr FP parse(char const* str, size_t length, bool* successful = nullptr) { - return parse(str, str + length, successful); - } - - static FP parse(const std::string_view str, bool* successful = nullptr) { - return parse(str.data(), str.length(), successful); - } - - // Not Deterministic - static constexpr FP parse_unsafe(float value) { - return static_cast(value * FPLUT::ONE + 0.5f * (value < 0 ? -1 : 1)); - } - - // Not Deterministic - static FP parse_unsafe(char const* value) { - char* endpointer; - double double_value = std::strtod(value, &endpointer); - - if (*endpointer != '\0') { - Logger::error("Unsafe fixed point parse failed to parse the end of a string: \"", endpointer, "\""); - } - - int64_t integer_value = static_cast(double_value * FPLUT::ONE + 0.5 * (double_value < 0 ? -1 : 1)); - - return integer_value; - } - - constexpr operator int32_t() const { - return to_int32_t(); - } - - constexpr operator int64_t() const { - return to_int64_t(); - } - - constexpr operator float() const { - return to_float(); - } - - constexpr operator double() const { - return to_double(); - } - - operator std::string() const { - return to_string(); - } - - friend std::ostream& operator<<(std::ostream& stream, FP const& obj) { - return stream << obj.to_string(); - } - - constexpr friend FP operator-(FP const& obj) { - return -obj.value; - } - - constexpr friend FP operator+(FP const& obj) { - return +obj.value; - } - - constexpr friend FP operator+(FP const& lhs, FP const& rhs) { - return lhs.value + rhs.value; - } - - constexpr friend FP operator+(FP const& lhs, int32_t const& rhs) { - return lhs.value + (static_cast(rhs) << FPLUT::PRECISION); - } - - constexpr friend FP operator+(int32_t const& lhs, FP const& rhs) { - return (static_cast(lhs) << FPLUT::PRECISION) + rhs.value; - } - - constexpr FP operator+=(FP const& obj) { - value += obj.value; - return *this; - } - - constexpr FP operator+=(int32_t const& obj) { - value += (static_cast(obj) << FPLUT::PRECISION); - return *this; - } - - constexpr friend FP operator-(FP const& lhs, FP const& rhs) { - return lhs.value - rhs.value; - } - - constexpr friend FP operator-(FP const& lhs, int32_t const& rhs) { - return lhs.value - (static_cast(rhs) << FPLUT::PRECISION); - } - - constexpr friend FP operator-(int32_t const& lhs, FP const& rhs) { - return (static_cast(lhs) << FPLUT::PRECISION) - rhs.value; - } - - constexpr FP operator-=(FP const& obj) { - value -= obj.value; - return *this; - } - - constexpr FP operator-=(int32_t const& obj) { - value -= (static_cast(obj) << FPLUT::PRECISION); - return *this; - } - - constexpr friend FP operator*(FP const& lhs, FP const& rhs) { - return lhs.value * rhs.value >> FPLUT::PRECISION; - } - - constexpr friend FP operator*(FP const& lhs, int32_t const& rhs) { - return lhs.value * rhs; - } - - constexpr friend FP operator*(int32_t const& lhs, FP const& rhs) { - return lhs * rhs.value; - } - - constexpr FP operator*=(FP const& obj) { - value *= obj.value >> FPLUT::PRECISION; - return *this; - } - - constexpr FP operator*=(int32_t const& obj) { - value *= obj; - return *this; - } - - constexpr friend FP operator/(FP const& lhs, FP const& rhs) { - return (lhs.value << FPLUT::PRECISION) / rhs.value; - } - - constexpr friend FP operator/(FP const& lhs, int32_t const& rhs) { - return lhs.value / rhs; - } - - constexpr friend FP operator/(int32_t const& lhs, FP const& rhs) { - return (static_cast(lhs) << (2 * FPLUT::PRECISION)) / rhs.value; - } - - constexpr FP operator/=(FP const& obj) { - value = (value << FPLUT::PRECISION) / obj.value; - return *this; - } - - constexpr FP operator/=(int32_t const& obj) { - value /= obj; - return *this; - } - - constexpr friend FP operator%(FP const& lhs, FP const& rhs) { - return lhs.value % rhs.value; - } - - constexpr friend FP operator%(FP const& lhs, int32_t const& rhs) { - return lhs.value % (static_cast(rhs) << FPLUT::PRECISION); - } - - constexpr friend FP operator%(int32_t const& lhs, FP const& rhs) { - return (static_cast(lhs) << FPLUT::PRECISION) % rhs.value; - } - - constexpr FP operator%=(FP const& obj) { - value %= obj.value; - return *this; - } - - constexpr FP operator%=(int32_t const& obj) { - value %= (static_cast(obj) << FPLUT::PRECISION); - return *this; - } - - constexpr friend bool operator<(FP const& lhs, FP const& rhs) { - return lhs.value < rhs.value; - } - - constexpr friend bool operator<(FP const& lhs, int32_t const& rhs) { - return lhs.value < static_cast(rhs) << FPLUT::PRECISION; - } - - constexpr friend bool operator<(int32_t const& lhs, FP const& rhs) { - return static_cast(lhs) << FPLUT::PRECISION < rhs.value; - } - - constexpr friend bool operator<=(FP const& lhs, FP const& rhs) { - return lhs.value <= rhs.value; - } - - constexpr friend bool operator<=(FP const& lhs, int32_t const& rhs) { - return lhs.value <= static_cast(rhs) << FPLUT::PRECISION; - } - - constexpr friend bool operator<=(int32_t const& lhs, FP const& rhs) { - return static_cast(lhs) << FPLUT::PRECISION <= rhs.value; - } - - constexpr friend bool operator>(FP const& lhs, FP const& rhs) { - return lhs.value > rhs.value; - } - - constexpr friend bool operator>(FP const& lhs, int32_t const& rhs) { - return lhs.value > static_cast(rhs) << FPLUT::PRECISION; - } - - constexpr friend bool operator>(int32_t const& lhs, FP const& rhs) { - return static_cast(lhs) << FPLUT::PRECISION > rhs.value; - } - - constexpr friend bool operator>=(FP const& lhs, FP const& rhs) { - return lhs.value >= rhs.value; - } - - constexpr friend bool operator>=(FP const& lhs, int32_t const& rhs) { - return lhs.value >= static_cast(rhs) << FPLUT::PRECISION; - } - - constexpr friend bool operator>=(int32_t const& lhs, FP const& rhs) { - return static_cast(lhs) << FPLUT::PRECISION >= rhs.value; - } - - constexpr friend bool operator==(FP const& lhs, FP const& rhs) { - return lhs.value == rhs.value; - } - - constexpr friend bool operator==(FP const& lhs, int32_t const& rhs) { - return lhs.value == static_cast(rhs) << FPLUT::PRECISION; - } - - constexpr friend bool operator==(int32_t const& lhs, FP const& rhs) { - return static_cast(lhs) << FPLUT::PRECISION == rhs.value; - } - - constexpr friend bool operator!=(FP const& lhs, FP const& rhs) { - return lhs.value != rhs.value; - } - - constexpr friend bool operator!=(FP const& lhs, int32_t const& rhs) { - return lhs.value != static_cast(rhs) << FPLUT::PRECISION; - } - - constexpr friend bool operator!=(int32_t const& lhs, FP const& rhs) { - return static_cast(lhs) << FPLUT::PRECISION != rhs.value; - } - - private: - int64_t value; - - static constexpr FP parse_integer(char const* str, char const* const end, bool* successful) { - int64_t parsed_value = StringUtils::string_to_int64(str, end, successful, 10); - return parse(parsed_value); - } - - static constexpr FP parse_fraction(char const* str, char const* end, bool* successful) { - char const* const read_end = str + FPLUT::PRECISION; - if (read_end < end) end = read_end; - uint64_t parsed_value = StringUtils::string_to_uint64(str, end, successful, 10); - while (end++ < read_end) { - parsed_value *= 10; - } - uint64_t decimal = NumberUtils::pow(static_cast(10), FPLUT::PRECISION); - int64_t ret = 0; - for (int i = FPLUT::PRECISION - 1; i >= 0; --i) { - decimal >>= 1; - if (parsed_value > decimal) { - parsed_value -= decimal; - ret |= 1 << i; - } - } - return ret; - } - }; - - static_assert(sizeof(FP) == FP::SIZE, "FP is not 8 bytes"); -} diff --git a/src/openvic/types/fixed_point/FPLUT.hpp b/src/openvic/types/fixed_point/FPLUT.hpp deleted file mode 100644 index f9b21ab..0000000 --- a/src/openvic/types/fixed_point/FPLUT.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "FPLUT_sin_512.hpp" - -namespace OpenVic::FPLUT { - constexpr int32_t PRECISION = 16; - constexpr int64_t ONE = 1 << PRECISION; - - // The LUT index is between 0 and 2^16, the index table goes until 512, if we shift by 7 our index will be between 0 and 511 - constexpr int32_t SHIFT = 16 - 9; - - constexpr int64_t sin(int64_t value) { - int sign = 1; - if (value < 0) { - value = -value; - sign = -1; - } - - int index = value >> SHIFT; - int64_t a = SIN_LUT[index]; - int64_t b = SIN_LUT[index + 1]; - int64_t fraction = (value - (index << SHIFT)) << 9; - int64_t result = a + (((b - a) * fraction) >> PRECISION); - return result * sign; - } -} diff --git a/src/openvic/types/fixed_point/FPLUT_sin_512.hpp b/src/openvic/types/fixed_point/FPLUT_sin_512.hpp deleted file mode 100644 index 1af0380..0000000 --- a/src/openvic/types/fixed_point/FPLUT_sin_512.hpp +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once - -#include - -static constexpr int32_t SIN_LUT[] = { - 0, 804, 1608, 2412, 3216, 4019, 4821, 5623, 6424, 7224, - 8022, 8820, 9616, 10411, 11204, 11996, 12785, 13573, 14359, 15143, - 15924, 16703, 17479, 18253, 19024, 19792, 20557, 21320, 22078, 22834, - 23586, 24335, 25080, 25821, 26558, 27291, 28020, 28745, 29466, 30182, - 30893, 31600, 32303, 33000, 33692, 34380, 35062, 35738, 36410, 37076, - 37736, 38391, 39040, 39683, 40320, 40951, 41576, 42194, 42806, 43412, - 44011, 44604, 45190, 45769, 46341, 46906, 47464, 48015, 48559, 49095, - 49624, 50146, 50660, 51166, 51665, 52156, 52639, 53114, 53581, 54040, - 54491, 54934, 55368, 55794, 56212, 56621, 57022, 57414, 57798, 58172, - 58538, 58896, 59244, 59583, 59914, 60235, 60547, 60851, 61145, 61429, - 61705, 61971, 62228, 62476, 62714, 62943, 63162, 63372, 63572, 63763, - 63944, 64115, 64277, 64429, 64571, 64704, 64827, 64940, 65043, 65137, - 65220, 65294, 65358, 65413, 65457, 65492, 65516, 65531, 65536, 65531, - 65516, 65492, 65457, 65413, 65358, 65294, 65220, 65137, 65043, 64940, - 64827, 64704, 64571, 64429, 64277, 64115, 63944, 63763, 63572, 63372, - 63162, 62943, 62714, 62476, 62228, 61971, 61705, 61429, 61145, 60851, - 60547, 60235, 59914, 59583, 59244, 58896, 58538, 58172, 57798, 57414, - 57022, 56621, 56212, 55794, 55368, 54934, 54491, 54040, 53581, 53114, - 52639, 52156, 51665, 51166, 50660, 50146, 49624, 49095, 48559, 48015, - 47464, 46906, 46341, 45769, 45190, 44604, 44011, 43412, 42806, 42194, - 41576, 40951, 40320, 39683, 39040, 38391, 37736, 37076, 36410, 35738, - 35062, 34380, 33692, 33000, 32303, 31600, 30893, 30182, 29466, 28745, - 28020, 27291, 26558, 25821, 25080, 24335, 23586, 22834, 22078, 21320, - 20557, 19792, 19024, 18253, 17479, 16703, 15924, 15143, 14359, 13573, - 12785, 11996, 11204, 10411, 9616, 8820, 8022, 7224, 6424, 5623, - 4821, 4019, 3216, 2412, 1608, 804, 0, -804, -1608, -2412, - -3216, -4019, -4821, -5623, -6424, -7224, -8022, -8820, -9616, -10411, - -11204, -11996, -12785, -13573, -14359, -15143, -15924, -16703, -17479, -18253, - -19024, -19792, -20557, -21320, -22078, -22834, -23586, -24335, -25080, -25821, - -26558, -27291, -28020, -28745, -29466, -30182, -30893, -31600, -32303, -33000, - -33692, -34380, -35062, -35738, -36410, -37076, -37736, -38391, -39040, -39683, - -40320, -40951, -41576, -42194, -42806, -43412, -44011, -44604, -45190, -45769, - -46341, -46906, -47464, -48015, -48559, -49095, -49624, -50146, -50660, -51166, - -51665, -52156, -52639, -53114, -53581, -54040, -54491, -54934, -55368, -55794, - -56212, -56621, -57022, -57414, -57798, -58172, -58538, -58896, -59244, -59583, - -59914, -60235, -60547, -60851, -61145, -61429, -61705, -61971, -62228, -62476, - -62714, -62943, -63162, -63372, -63572, -63763, -63944, -64115, -64277, -64429, - -64571, -64704, -64827, -64940, -65043, -65137, -65220, -65294, -65358, -65413, - -65457, -65492, -65516, -65531, -65536, -65531, -65516, -65492, -65457, -65413, - -65358, -65294, -65220, -65137, -65043, -64940, -64827, -64704, -64571, -64429, - -64277, -64115, -63944, -63763, -63572, -63372, -63162, -62943, -62714, -62476, - -62228, -61971, -61705, -61429, -61145, -60851, -60547, -60235, -59914, -59583, - -59244, -58896, -58538, -58172, -57798, -57414, -57022, -56621, -56212, -55794, - -55368, -54934, -54491, -54040, -53581, -53114, -52639, -52156, -51665, -51166, - -50660, -50146, -49624, -49095, -48559, -48015, -47464, -46906, -46341, -45769, - -45190, -44604, -44011, -43412, -42806, -42194, -41576, -40951, -40320, -39683, - -39040, -38391, -37736, -37076, -36410, -35738, -35062, -34380, -33692, -33000, - -32303, -31600, -30893, -30182, -29466, -28745, -28020, -27291, -26558, -25821, - -25080, -24335, -23586, -22834, -22078, -21320, -20557, -19792, -19024, -18253, - -17479, -16703, -15924, -15143, -14359, -13573, -12785, -11996, -11204, -10411, - -9616, -8820, -8022, -7224, -6424, -5623, -4821, -4019, -3216, -2412, - -1608, -804, 0 -}; diff --git a/src/openvic/types/fixed_point/FPMath.hpp b/src/openvic/types/fixed_point/FPMath.hpp deleted file mode 100644 index 965d63c..0000000 --- a/src/openvic/types/fixed_point/FPMath.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include "FP.hpp" - -namespace OpenVic::FPMath { - constexpr FP sin(FP number) { - number %= FP::pi2(); - number *= FP::one_div_pi2(); - return FPLUT::sin(number.get_raw_value()); - } -} diff --git a/src/openvic/types/fixed_point/lut_generator/lut_generator.py b/src/openvic/types/fixed_point/lut_generator/lut_generator.py deleted file mode 100644 index 5c5ba8b..0000000 --- a/src/openvic/types/fixed_point/lut_generator/lut_generator.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env python -import math - -PRECISION = 16 -ONE = 1 << PRECISION -SIN_VALUE_COUNT = 512 - -SinLut = [] - -for i in range(SIN_VALUE_COUNT): - angle = 2 * math.pi * i / SIN_VALUE_COUNT - - sin_value = math.sin(angle) - moved_sin = sin_value * ONE - rounded_sin = int(moved_sin + 0.5) if moved_sin > 0 else int(moved_sin - 0.5) - SinLut.append(rounded_sin) - -SinLut.append(SinLut[0]) - -output = [ -"""#pragma once - -#include - -static constexpr int32_t SIN_LUT[] = {""" -] - -lines = [SinLut[i:i+10] for i in range(0, len(SinLut), 10)] - -for line in lines: - output.append("\t" + ", ".join(str(value) for value in line) + ",") - -output[-1] = output[-1][:-1] # Remove the last comma -output.append("};\n") - -cpp_code = "\n".join(output) - -with open(f"FPLUT_sin_{SIN_VALUE_COUNT}.hpp", "w", newline="\n") as file: - file.write(cpp_code) diff --git a/src/openvic/utility/BMP.cpp b/src/openvic/utility/BMP.cpp deleted file mode 100644 index 0ea0f30..0000000 --- a/src/openvic/utility/BMP.cpp +++ /dev/null @@ -1,163 +0,0 @@ -#include "BMP.hpp" - -#include -#include - -#include "openvic/utility/Logger.hpp" - -using namespace OpenVic; - -BMP::~BMP() { - close(); -} - -bool BMP::open(char const* filepath) { - reset(); - errno = 0; - file = fopen(filepath, "rb"); - if (file == nullptr || errno != 0) { - Logger::error("Failed to open BMP file \"", filepath, "\" (errno = ", errno, ")"); - file = nullptr; - return false; - } - return true; -} - -bool BMP::read_header() { - if (header_validated) { - Logger::error("BMP header already validated!"); - return false; - } - if (file == nullptr) { - Logger::error("Cannot read BMP header before opening a file"); - return false; - } - if (fseek(file, 0, SEEK_SET) != 0) { - Logger::error("Failed to move to the beginning of the BMP file!"); - return false; - } - if (fread(&header, sizeof(header), 1, file) != 1) { - Logger::error("Failed to read BMP header!"); - return false; - } - - header_validated = true; - - // Validate constants - static constexpr uint16_t BMP_SIGNATURE = 0x4d42; - if (header.signature != BMP_SIGNATURE) { - Logger::error("Invalid BMP signature: ", header.signature, " (must be ", BMP_SIGNATURE, ")"); - header_validated = false; - } - static constexpr uint32_t DIB_HEADER_SIZE = 40; - if (header.dib_header_size != DIB_HEADER_SIZE) { - Logger::error("Invalid BMP DIB header size: ", header.dib_header_size, " (must be ", DIB_HEADER_SIZE, ")"); - header_validated = false; - } - static constexpr uint16_t NUM_PLANES = 1; - if (header.num_planes != NUM_PLANES) { - Logger::error("Invalid BMP plane count: ", header.num_planes, " (must be ", NUM_PLANES, ")"); - header_validated = false; - } - static constexpr uint16_t COMPRESSION = 0; // Only support uncompressed BMPs - if (header.compression != COMPRESSION) { - Logger::error("Invalid BMP compression method: ", header.compression, " (must be ", COMPRESSION, ")"); - header_validated = false; - } - - // Validate sizes and dimensions - // TODO - image_size_bytes can be 0 for non-compressed BMPs - if (header.file_size != header.offset + header.image_size_bytes) { - Logger::error("Invalid BMP memory sizes: file size = ", header.file_size, " != ", header.offset + header.image_size_bytes, - " = ", header.offset, " + ", header.image_size_bytes, " = image data offset + image data size"); - header_validated = false; - } - // TODO - support negative widths (i.e. horizontal flip) - if (header.width_px <= 0) { - Logger::error("Invalid BMP width: ", header.width_px, " (must be positive)"); - header_validated = false; - } - // TODO - support negative heights (i.e. vertical flip) - if (header.height_px <= 0) { - Logger::error("Invalid BMP height: ", header.height_px, " (must be positive)"); - header_validated = false; - } - // TODO - validate x_resolution_ppm - // TODO - validate y_resolution_ppm - - // Validate colours -#define VALID_BITS_PER_PIXEL 1, 2, 4, 8, 16, 24, 32 -#define STR(x) #x - static const std::set BITS_PER_PIXEL { VALID_BITS_PER_PIXEL }; - if (!BITS_PER_PIXEL.contains(header.bits_per_pixel)) { - Logger::error("Invalid BMP bits per pixel: ", header.bits_per_pixel, " (must be one of " STR(VALID_BITS_PER_PIXEL) ")"); - header_validated = false; - } -#undef VALID_BITS_PER_PIXEL -#undef STR - static constexpr uint16_t PALETTE_BITS_PER_PIXEL_LIMIT = 8; - if (header.num_colours != 0 && header.bits_per_pixel > PALETTE_BITS_PER_PIXEL_LIMIT) { - Logger::error("Invalid BMP palette size: ", header.num_colours, " (should be 0 as bits per pixel is ", header.bits_per_pixel, " > 8)"); - header_validated = false; - } - // TODO - validate important_colours - - palette_size = header.bits_per_pixel > PALETTE_BITS_PER_PIXEL_LIMIT ? 0 - // Use header.num_colours if it's greater than 0 and at most 1 << header.bits_per_pixel - : 0 < header.num_colours && header.num_colours - 1 >> header.bits_per_pixel == 0 ? header.num_colours - : 1 << header.bits_per_pixel; - - const uint32_t expected_offset = palette_size * PALETTE_COLOUR_SIZE + sizeof(header); - if (header.offset != expected_offset) { - Logger::error("Invalid BMP image data offset: ", header.offset, " (should be ", expected_offset, ")"); - header_validated = false; - } - - return header_validated; -} - -bool BMP::read_palette() { - if (file == nullptr) { - Logger::error("Cannot read BMP palette before opening a file"); - return false; - } - if (!header_validated) { - Logger::error("Cannot read palette before BMP header is validated!"); - return false; - } - if (palette_size == 0) { - Logger::error("Cannot read BMP palette - header indicates this file doesn't have one"); - return false; - } - if (fseek(file, sizeof(header), SEEK_SET) != 0) { - Logger::error("Failed to move to the palette in the BMP file!"); - return false; - } - palette.resize(palette_size); - if (fread(palette.data(), palette_size * PALETTE_COLOUR_SIZE, 1, file) != 1) { - Logger::error("Failed to read BMP header!"); - palette.clear(); - return false; - } - return true; -} - -void BMP::close() { - if (file != nullptr) { - if (fclose(file) != 0) - Logger::error("Failed to close BMP!"); - file = nullptr; - } -} - -void BMP::reset() { - close(); - memset(&header, 0, sizeof(header)); - header_validated = false; - palette_size = 0; - palette.clear(); -} - -std::vector const& BMP::get_palette() const { - return palette; -} diff --git a/src/openvic/utility/BMP.hpp b/src/openvic/utility/BMP.hpp deleted file mode 100644 index 7ed36a5..0000000 --- a/src/openvic/utility/BMP.hpp +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once - -#include -#include - -#include "openvic/types/Colour.hpp" - -namespace OpenVic { - class BMP { -#pragma pack(push) -#pragma pack(1) - struct header_t { - uint16_t signature; // Signature: 0x4d42 (or 'B' 'M') - uint32_t file_size; // File size in bytes - uint16_t reserved1; // Not used - uint16_t reserved2; // Not used - uint32_t offset; // Offset to image data in bytes from beginning of file - uint32_t dib_header_size; // DIB header size in bytes - int32_t width_px; // Width of the image - int32_t height_px; // Height of image - uint16_t num_planes; // Number of colour planes - uint16_t bits_per_pixel; // Bits per pixel - uint32_t compression; // Compression type - uint32_t image_size_bytes; // Image size in bytes - int32_t x_resolution_ppm; // Pixels per meter - int32_t y_resolution_ppm; // Pixels per meter - uint32_t num_colours; // Number of colours - uint32_t important_colours; // Important colours - } header; -#pragma pack(pop) - - FILE* file = nullptr; - bool header_validated = false; - uint32_t palette_size = 0; - std::vector palette; - - public: - static constexpr uint32_t PALETTE_COLOUR_SIZE = sizeof(colour_t); - - BMP() = default; - ~BMP(); - - bool open(char const* filepath); - bool read_header(); - bool read_palette(); - void close(); - void reset(); - - std::vector const& get_palette() const; - }; -} diff --git a/src/openvic/utility/Logger.cpp b/src/openvic/utility/Logger.cpp deleted file mode 100644 index bf9a67c..0000000 --- a/src/openvic/utility/Logger.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include "Logger.hpp" - -#include - -using namespace OpenVic; - -Logger::log_func_t Logger::info_func {}; -Logger::log_queue_t Logger::info_queue {}; -Logger::log_func_t Logger::error_func {}; -Logger::log_queue_t Logger::error_queue {}; - -char const* Logger::get_filename(char const* filepath) { - if (filepath == nullptr) return nullptr; - char const* last_slash = filepath; - while (*filepath != '\0') { - if (*filepath == '\\' || *filepath == '/') last_slash = filepath + 1; - filepath++; - } - return last_slash; -} diff --git a/src/openvic/utility/Logger.hpp b/src/openvic/utility/Logger.hpp deleted file mode 100644 index 417aba7..0000000 --- a/src/openvic/utility/Logger.hpp +++ /dev/null @@ -1,87 +0,0 @@ -#pragma once - -#include -#include -#include -#ifdef __cpp_lib_source_location -#include -#endif - -namespace OpenVic { - -#ifndef __cpp_lib_source_location -#include - // Implementation of std::source_location for compilers that do not support it - // Note: uses non-standard extensions that are supported by Clang, GCC, and MSVC - // https://clang.llvm.org/docs/LanguageExtensions.html#source-location-builtins - // https://stackoverflow.com/a/67970107 - class source_location { - std::string _file; - int _line; - std::string _function; - - public: - source_location(std::string f, int l, std::string n) : _file(f), _line(l), _function(n) {} - static source_location current(std::string f = __builtin_FILE(), int l = __builtin_LINE(), std::string n = __builtin_FUNCTION()) { - return source_location(f, l, n); - } - - inline char const* file_name() const { return _file.c_str(); } - inline int line() const { return _line; } - inline char const* function_name() const { return _function.c_str(); } - }; -#endif - - class Logger { - using log_func_t = std::function; - using log_queue_t = std::queue; - -#ifdef __cpp_lib_source_location - using source_location = std::source_location; -#else - using source_location = OpenVic::source_location; -#endif - - static char const* get_filename(char const* filepath); - - template - struct log { - log(log_func_t log_func, log_queue_t& log_queue, Ts&&... ts, source_location const& location) { - std::stringstream stream; - stream << "\n" << get_filename(location.file_name()) << "(" - << location.line() << ") `" << location.function_name() << "`: "; - ((stream << std::forward(ts)), ...); - stream << std::endl; - log_queue.push(stream.str()); - if (log_func) { - do { - log_func(std::move(log_queue.front())); - log_queue.pop(); - } while (!log_queue.empty()); - } - } - }; - -#define LOG_FUNC(name) \ - private: \ - static log_func_t name##_func; \ - static log_queue_t name##_queue; \ - public: \ - static void set_##name##_func(log_func_t log_func) { \ - name##_func = log_func; \ - } \ - template \ - struct name { \ - name(Ts&&... ts, source_location const& location = source_location::current()) { \ - log{ name##_func, name##_queue, std::forward(ts)..., location }; \ - } \ - }; \ - template \ - name(Ts&&...) -> name; - - LOG_FUNC(info) - LOG_FUNC(error) - -#undef LOG_FUNC - }; -} diff --git a/src/openvic/utility/NumberUtils.hpp b/src/openvic/utility/NumberUtils.hpp deleted file mode 100644 index 6211772..0000000 --- a/src/openvic/utility/NumberUtils.hpp +++ /dev/null @@ -1,27 +0,0 @@ -#include - -namespace OpenVic::NumberUtils { - constexpr int64_t round_to_int64(float num) { - return (num > 0.0f) ? (num + 0.5f) : (num - 0.5f); - } - - constexpr int64_t round_to_int64(double num) { - return (num > 0.0) ? (num + 0.5) : (num - 0.5); - } - - constexpr uint64_t pow(uint64_t base, size_t exponent) { - uint64_t ret = 1; - while (exponent-- > 0) { - ret *= base; - } - return ret; - } - - constexpr int64_t pow(int64_t base, size_t exponent) { - int64_t ret = 1; - while (exponent-- > 0) { - ret *= base; - } - return ret; - } -} diff --git a/src/openvic/utility/StringUtils.hpp b/src/openvic/utility/StringUtils.hpp deleted file mode 100644 index 5d6001c..0000000 --- a/src/openvic/utility/StringUtils.hpp +++ /dev/null @@ -1,127 +0,0 @@ -#include -#include -#include - -namespace OpenVic::StringUtils { - /* The constexpr function 'string_to_uint64' will convert a string into a uint64_t integer value. - * The function takes four parameters: the input string (as a pair of pointers marking the start and - * end of the string), a bool pointer for reporting success, and the base for numerical conversion. - * The base parameter defaults to 10 (decimal), but it can be any value between 2 and 36. If the base - * given is 0, it will be set to 16 if the string starts with "0x" or "0X", otherwise 8 if the string - * still starts with "0", otherwise 10. The success bool pointer parameter is used to report whether - * or not conversion was successful. It can be nullptr if this information is not needed. - */ - constexpr uint64_t string_to_uint64(char const* str, const char* const end, bool* successful = nullptr, int base = 10) { - if (successful != nullptr) *successful = false; - - // Base value should be between 2 and 36. If it's not, return 0 as an invalid case. - if (str == nullptr || end <= str || base < 0 || base == 1 || base > 36) - return 0; - - // The result of the conversion will be stored in this variable. - uint64_t result = 0; - - // If base is zero, base is determined by the string prefix. - if (base == 0) { - if (*str == '0') { - if (str + 1 != end && (str[1] == 'x' || str[1] == 'X')) { - base = 16; // Hexadecimal. - str += 2; // Skip '0x' or '0X' - if (str == end) return 0; - } else { - base = 8; // Octal. - } - } else { - base = 10; // Decimal. - } - } else if (base == 16) { - // If base is 16 and string starts with '0x' or '0X', skip these characters. - if (*str == '0' && str + 1 != end && (str[1] == 'x' || str[1] == 'X')) { - str += 2; - if (str == end) return 0; - } - } - - // Convert the number in the string. - for (; str != end; ++str) { - int digit; - if (*str >= '0' && *str <= '9') { - digit = *str - '0'; // Calculate digit value for '0'-'9'. - } else if (*str >= 'a' && *str <= 'z') { - digit = *str - 'a' + 10; // Calculate digit value for 'a'-'z'. - } else if (*str >= 'A' && *str <= 'Z') { - digit = *str - 'A' + 10; // Calculate digit value for 'A'-'Z'. - } else { - break; // Stop conversion if current character is not a digit. - } - - if (digit >= base) { - break; // Stop conversion if current digit is greater than or equal to the base. - } - - // Check for overflow on multiplication - if (result > std::numeric_limits::max() / base) { - return std::numeric_limits::max(); - } - - result *= base; - - // Check for overflow on addition - if (result > std::numeric_limits::max() - digit) { - return std::numeric_limits::max(); - } - - result += digit; - } - - // If successful is not null and the entire string was parsed, - // set *successful to true (if not it is already false). - if (successful != nullptr && str == end) *successful = true; - - return result; - } - - constexpr uint64_t string_to_uint64(char const* str, size_t length, bool* successful = nullptr, int base = 10) { - return string_to_uint64(str, str + length, successful, base); - } - - inline uint64_t string_to_uint64(const std::string_view str, bool* successful = nullptr, int base = 10) { - return string_to_uint64(str.data(), str.length(), successful, base); - } - - constexpr int64_t string_to_int64(char const* str, const char* const end, bool* successful = nullptr, int base = 10) { - if (successful != nullptr) *successful = false; - - if (str == nullptr || end <= str) return 0; - - // This flag will be set if the number is negative. - bool is_negative = false; - - // Check if there is a sign character. - if (*str == '+' || *str == '-') { - if (*str == '-') - is_negative = true; - ++str; - if (str == end) return 0; - } - - const uint64_t result = string_to_uint64(str, end, successful, base); - if (!is_negative) { - if (result >= std::numeric_limits::max()) - return std::numeric_limits::max(); - return result; - } else { - if (result > std::numeric_limits::max()) - return std::numeric_limits::min(); - return -result; - } - } - - constexpr int64_t string_to_int64(char const* str, size_t length, bool* successful = nullptr, int base = 10) { - return string_to_int64(str, str + length, successful, base); - } - - inline int64_t string_to_int64(const std::string_view str, bool* successful = nullptr, int base = 10) { - return string_to_int64(str.data(), str.length(), successful, base); - } -} -- cgit v1.2.3-56-ga3b1