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) --- .gitmodules | 3 + SConstruct | 295 ++++++++++- deps/openvic-dataloader | 1 + scripts/build/cache.py | 127 +++++ scripts/build/glob_recursive.py | 15 + scripts/build/option_handler.py | 39 ++ src/headless/main.cpp | 64 ++- src/openvic/Date.cpp | 226 --------- src/openvic/Date.hpp | 90 ---- src/openvic/GameManager.cpp | 85 +++- src/openvic/GameManager.hpp | 11 +- src/openvic/Simulation.hpp | 5 - src/openvic/Types.cpp | 37 -- src/openvic/Types.hpp | 171 ------- src/openvic/dataloader/Dataloader.cpp | 140 +++++ src/openvic/dataloader/Dataloader.hpp | 28 +- src/openvic/dataloader/NodeTools.cpp | 198 ++++++++ src/openvic/dataloader/NodeTools.hpp | 42 ++ src/openvic/economy/Good.cpp | 8 +- src/openvic/economy/Good.hpp | 8 +- src/openvic/map/Building.cpp | 4 +- src/openvic/map/Building.hpp | 4 +- src/openvic/map/Map.cpp | 7 +- src/openvic/map/Map.hpp | 4 +- src/openvic/map/Province.cpp | 10 +- src/openvic/map/Province.hpp | 6 +- src/openvic/map/Region.cpp | 2 - src/openvic/map/Region.hpp | 2 +- src/openvic/pop/Culture.cpp | 111 +++- src/openvic/pop/Culture.hpp | 11 +- src/openvic/pop/Pop.cpp | 67 ++- src/openvic/pop/Pop.hpp | 12 +- src/openvic/pop/Religion.hpp | 2 +- src/openvic/types/Colour.hpp | 27 + src/openvic/types/Date.cpp | 240 +++++++++ src/openvic/types/Date.hpp | 90 ++++ src/openvic/types/IdentifierRegistry.cpp | 37 ++ src/openvic/types/IdentifierRegistry.hpp | 142 ++++++ src/openvic/types/Return.hpp | 8 + src/openvic/types/fixed_point/FP.hpp | 563 +++++++++++++++++++++ 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 | 2 +- src/openvic/utility/BMP.hpp | 6 +- src/openvic/utility/FloatUtils.hpp | 11 + src/openvic/utility/StringUtils.hpp | 93 ++++ 48 files changed, 2538 insertions(+), 657 deletions(-) create mode 100644 .gitmodules create mode 160000 deps/openvic-dataloader create mode 100644 scripts/build/cache.py create mode 100644 scripts/build/glob_recursive.py create mode 100644 scripts/build/option_handler.py delete mode 100644 src/openvic/Date.cpp delete mode 100644 src/openvic/Date.hpp delete mode 100644 src/openvic/Simulation.hpp delete mode 100644 src/openvic/Types.cpp delete mode 100644 src/openvic/Types.hpp create mode 100644 src/openvic/dataloader/Dataloader.cpp create mode 100644 src/openvic/dataloader/NodeTools.cpp create mode 100644 src/openvic/dataloader/NodeTools.hpp create mode 100644 src/openvic/types/Colour.hpp create mode 100644 src/openvic/types/Date.cpp create mode 100644 src/openvic/types/Date.hpp create mode 100644 src/openvic/types/IdentifierRegistry.cpp create mode 100644 src/openvic/types/IdentifierRegistry.hpp create mode 100644 src/openvic/types/Return.hpp create mode 100644 src/openvic/types/fixed_point/FP.hpp create mode 100644 src/openvic/types/fixed_point/FPLUT.hpp create mode 100644 src/openvic/types/fixed_point/FPLUT_sin_512.hpp create mode 100644 src/openvic/types/fixed_point/FPMath.hpp create mode 100644 src/openvic/types/fixed_point/lut_generator/lut_generator.py create mode 100644 src/openvic/utility/FloatUtils.hpp create mode 100644 src/openvic/utility/StringUtils.hpp diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..df89ab5 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "deps/openvic-dataloader"] + path = deps/openvic-dataloader + url = https://github.com/OpenVicProject/OpenVic-Dataloader 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") diff --git a/deps/openvic-dataloader b/deps/openvic-dataloader new file mode 160000 index 0000000..b1f726c --- /dev/null +++ b/deps/openvic-dataloader @@ -0,0 +1 @@ +Subproject commit b1f726c79f20f89641d7e091b7abeed48227fad7 diff --git a/scripts/build/cache.py b/scripts/build/cache.py new file mode 100644 index 0000000..d48b0e0 --- /dev/null +++ b/scripts/build/cache.py @@ -0,0 +1,127 @@ +# Copied from https://github.com/godotengine/godot/blob/c3b0a92c3cd9a219c1b1776b48c147f1d0602f07/methods.py#L1049-L1172 +def show_progress(env): + import os + import sys + import glob + from SCons.Script import Progress, Command, AlwaysBuild + + screen = sys.stdout + # Progress reporting is not available in non-TTY environments since it + # messes with the output (for example, when writing to a file) + show_progress = env["progress"] and sys.stdout.isatty() + node_count = 0 + node_count_max = 0 + node_count_interval = 1 + node_count_fname = str(env.Dir("#")) + "/.scons_node_count" + + import time, math + + class cache_progress: + # The default is 1 GB cache and 12 hours half life + def __init__(self, path=None, limit=1073741824, half_life=43200): + self.path = path + self.limit = limit + self.exponent_scale = math.log(2) / half_life + if env["verbose"] and path != None: + screen.write( + "Current cache limit is {} (used: {})\n".format( + self.convert_size(limit), self.convert_size(self.get_size(path)) + ) + ) + self.delete(self.file_list()) + + def __call__(self, node, *args, **kw): + nonlocal node_count, node_count_max, node_count_interval, node_count_fname, show_progress + if show_progress: + # Print the progress percentage + node_count += node_count_interval + if node_count_max > 0 and node_count <= node_count_max: + screen.write("\r[%3d%%] " % (node_count * 100 / node_count_max)) + screen.flush() + elif node_count_max > 0 and node_count > node_count_max: + screen.write("\r[100%] ") + screen.flush() + else: + screen.write("\r[Initial build] ") + screen.flush() + + def delete(self, files): + if len(files) == 0: + return + if env["verbose"]: + # Utter something + screen.write("\rPurging %d %s from cache...\n" % (len(files), len(files) > 1 and "files" or "file")) + [os.remove(f) for f in files] + + def file_list(self): + if self.path is None: + # Nothing to do + return [] + # Gather a list of (filename, (size, atime)) within the + # cache directory + file_stat = [(x, os.stat(x)[6:8]) for x in glob.glob(os.path.join(self.path, "*", "*"))] + if file_stat == []: + # Nothing to do + return [] + # Weight the cache files by size (assumed to be roughly + # proportional to the recompilation time) times an exponential + # decay since the ctime, and return a list with the entries + # (filename, size, weight). + current_time = time.time() + file_stat = [(x[0], x[1][0], (current_time - x[1][1])) for x in file_stat] + # Sort by the most recently accessed files (most sensible to keep) first + file_stat.sort(key=lambda x: x[2]) + # Search for the first entry where the storage limit is + # reached + sum, mark = 0, None + for i, x in enumerate(file_stat): + sum += x[1] + if sum > self.limit: + mark = i + break + if mark is None: + return [] + else: + return [x[0] for x in file_stat[mark:]] + + def convert_size(self, size_bytes): + if size_bytes == 0: + return "0 bytes" + size_name = ("bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB") + i = int(math.floor(math.log(size_bytes, 1024))) + p = math.pow(1024, i) + s = round(size_bytes / p, 2) + return "%s %s" % (int(s) if i == 0 else s, size_name[i]) + + def get_size(self, start_path="."): + total_size = 0 + for dirpath, dirnames, filenames in os.walk(start_path): + for f in filenames: + fp = os.path.join(dirpath, f) + total_size += os.path.getsize(fp) + return total_size + + def progress_finish(target, source, env): + nonlocal node_count, progressor + try: + with open(node_count_fname, "w") as f: + f.write("%d\n" % node_count) + progressor.delete(progressor.file_list()) + except Exception: + pass + + try: + with open(node_count_fname) as f: + node_count_max = int(f.readline()) + except Exception: + pass + + cache_directory = os.environ.get("SCONS_CACHE") + # Simple cache pruning, attached to SCons' progress callback. Trim the + # cache directory to a size not larger than cache_limit. + cache_limit = float(os.getenv("SCONS_CACHE_LIMIT", 1024)) * 1024 * 1024 + progressor = cache_progress(cache_directory, cache_limit) + Progress(progressor, interval=node_count_interval) + + progress_finish_command = Command("progress_finish", [], progress_finish) + AlwaysBuild(progress_finish_command) diff --git a/scripts/build/glob_recursive.py b/scripts/build/glob_recursive.py new file mode 100644 index 0000000..db6eb80 --- /dev/null +++ b/scripts/build/glob_recursive.py @@ -0,0 +1,15 @@ +def GlobRecursive(pattern, nodes=['.']): + import SCons + import glob + fs = SCons.Node.FS.get_default_fs() + Glob = fs.Glob + + 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 diff --git a/scripts/build/option_handler.py b/scripts/build/option_handler.py new file mode 100644 index 0000000..3cebc1a --- /dev/null +++ b/scripts/build/option_handler.py @@ -0,0 +1,39 @@ +from typing import Tuple, Iterable +from SCons.Variables import Variables + +class OptionsClass: + def __init__(self, args): + self.opts = None + self.opt_list = [] + self.args = args + self.saved_args = args.copy() + + def Add(self, variableOrKey, *argv, **kwarg): + + self.opt_list.append([variableOrKey, argv, kwarg]) + # Neccessary to have our own build options without errors + if isinstance(variableOrKey, str): + self.args.pop(variableOrKey, True) + else: + self.args.pop(variableOrKey[0], True) + + def Make(self, customs : Iterable[str]): + self.args = self.saved_args + profile = self.args.get("profile", "") + if profile: + if os.path.isfile(profile): + customs.append(profile) + elif os.path.isfile(profile + ".py"): + customs.append(profile + ".py") + self.opts = Variables(customs, self.args) + for opt in self.opt_list: + if opt[1] == None and opt[2] == None: + self.opts.Add(opt[0]) + else: + self.opts.Add(opt[0], *opt[1], **opt[2]) + + def Finalize(self, env): + self.opts.Update(env) + + def GenerateHelpText(self, env): + return self.opts.GenerateHelpText(env) diff --git a/src/headless/main.cpp b/src/headless/main.cpp index 70a57a9..69e1e67 100644 --- a/src/headless/main.cpp +++ b/src/headless/main.cpp @@ -1,22 +1,56 @@ -#ifdef OPENVIC_HEADLESS_SIM -#include -#include -#include "openvic/Simulation.hpp" -#include "openvic/dataloader/Dataloader.hpp" +#ifdef OPENVIC_SIM_HEADLESS +#include +#include +#include -int main() { - std::cout << "HEADLESS SIMULATION" << std::endl; +using namespace OpenVic; +static char const* get_program_name(char const* name) { + static char const* const missing_name = ""; + if (name == nullptr) return missing_name; + char const* last_separator = name; + while (*name != '\0') { + if (*name == '/' || *name == '\\') { + last_separator = name + 1; + } + ++name; + } + if (*last_separator == '\0') return missing_name; + return last_separator; +} + +int main(int argc, char const* argv[]) { + if (argc < 2) { + std::cout << "Usage: " << get_program_name(argc > 0 ? argv[0] : nullptr) << " [[mod defines dir]+]" << std::endl; + std::cout << "Requires defines path(s) as arguments, starting with the base defines and continuing with mods (paths with spaces need to be enclosed in \"quotes\")." << std::endl; + return -1; + } + + std::cout << "!!! HEADLESS SIMULATION START !!!" << std::endl; - std::string vic2FolderLocation = ""; - if (vic2FolderLocation.length() <= 0) { - std::cout << "Path to Victoria 2 folder not specified. Manually specify location: "; - std::cin >> vic2FolderLocation; + Logger::set_info_func([](std::string&& str) { std::cout << str; }); + Logger::set_error_func([](std::string&& str) { std::cerr << str; }); + + GameManager game_manager { []() { + Logger::info("State updated"); + } }; + Dataloader dataloader; + + std::vector roots; + for (int i = 1; i < argc; ++i) { + roots.push_back(argv[i]); + } + dataloader.set_roots(roots); + + if (dataloader.load_defines(game_manager) != SUCCESS) { + Logger::error("Failed to load defines!"); + } + if (game_manager.load_hardcoded_defines() != SUCCESS) { + Logger::error("Failed to load hardcoded defines!"); } - std::filesystem::path path(vic2FolderLocation); - OpenVic::Simulation sim; - std::cout << (OpenVic::Dataloader::loadDir(path, sim) ? "Dataloader suceeded" : "Dataloader failed") << std::endl; + std::cout << "!!! HEADLESS SIMULATION END !!!" << std::endl; + return 0; } -#endif \ No newline at end of file +#endif diff --git a/src/openvic/Date.cpp b/src/openvic/Date.cpp deleted file mode 100644 index 7c37386..0000000 --- a/src/openvic/Date.cpp +++ /dev/null @@ -1,226 +0,0 @@ -#include "Date.hpp" - -#include -#include -#include -#include - -#include "utility/Logger.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; -} - -Timespan::operator std::string() const { - return std::to_string(days); -} - -std::ostream& OpenVic::operator<<(std::ostream& out, Timespan const& timespan) { - return out << static_cast(timespan); -} - -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; -} - -Date::operator std::string() const { - std::stringstream ss; - ss << *this; - return ss.str(); -} - -std::ostream& OpenVic::operator<<(std::ostream& out, Date const& date) { - return out << static_cast(date.getYear()) << '.' << static_cast(date.getMonth()) << '.' << static_cast(date.getDay()); -} - -// Parsed from string of the form YYYY.MM.DD -Date Date::from_string(const std::string_view date) { - size_t first_pos = 0; - while (first_pos < date.length() && std::isdigit(date[first_pos])) { - first_pos++; - } - - if (first_pos == 0) { - Logger::error("Failed to find year digits in date: ", date); - return {}; - } - - int val = 0; - char const* start = date.data(); - char const* end = start + first_pos; - std::from_chars_result result = std::from_chars(start, end, val); - if (result.ec != std::errc{} || result.ptr != end || val < 0 || val >= 1 << (8 * sizeof(year_t))) { - Logger::error("Failed to read year: ", date); - return {}; - } - year_t year = val; - month_t month = 1; - day_t day = 1; - if (first_pos < date.length()) { - if (date[first_pos] == '.') { - size_t second_pos = ++first_pos; - while (second_pos < date.length() && std::isdigit(date[second_pos])) { - second_pos++; - } - if (first_pos == second_pos) { - Logger::error("Failed to find month digits in date: ", date); - } else { - start = date.data() + first_pos; - end = date.data() + second_pos; - result = std::from_chars(start, end, val); - if (result.ec != std::errc{} || result.ptr != end || val < 1 || val > MONTHS_IN_YEAR) { - Logger::error("Failed to read month: ", date); - } else { - month = val; - if (second_pos < date.length()) { - if (date[second_pos] == '.') { - size_t third_pos = ++second_pos; - while (third_pos < date.length() && std::isdigit(date[third_pos])) { - third_pos++; - } - if (second_pos == third_pos) { - Logger::error("Failed to find day digits in date: ", date); - } else { - start = date.data() + second_pos; - end = date.data() + third_pos; - result = std::from_chars(start, end, val); - if (result.ec != std::errc{} || result.ptr != end || val < 1 || val > DAYS_IN_MONTH[month - 1]) { - Logger::error("Failed to read day: ", date); - } else { - day = val; - if (third_pos < date.length()) { - Logger::error("Unexpected string \"", date.substr(third_pos), "\" at the end of date ", date); - } - } - } - } else Logger::error("Unexpected character \"", date[second_pos], "\" in month of date ", date); - } - } - } - } else Logger::error("Unexpected character \"", date[first_pos], "\" in year of date ", date); - } - return _dateToTimespan(year, month, day); -}; diff --git a/src/openvic/Date.hpp b/src/openvic/Date.hpp deleted file mode 100644 index 2994523..0000000 --- a/src/openvic/Date.hpp +++ /dev/null @@ -1,90 +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; - 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; - - 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); - - explicit operator std::string() const; - // Parsed from string of the form YYYY.MM.DD - static Date from_string(const std::string_view date); - }; - std::ostream& operator<<(std::ostream& out, Date const& date); -} diff --git a/src/openvic/GameManager.cpp b/src/openvic/GameManager.cpp index ce56afc..b4fa8b5 100644 --- a/src/openvic/GameManager.cpp +++ b/src/openvic/GameManager.cpp @@ -1,6 +1,6 @@ #include "GameManager.hpp" -#include "utility/Logger.hpp" +#include "openvic/utility/Logger.hpp" using namespace OpenVic; @@ -51,3 +51,86 @@ return_t GameManager::expand_building(Province::index_t province_index, const st if (province == nullptr) return FAILURE; return province->expand_building(building_type_identifier); } + +return_t GameManager::load_hardcoded_defines() { + return_t ret = SUCCESS; + + 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", + // TODO - use std::max_element and maybe change distribution_t to be an ordered std::map + [](Map const& map, Province const& province) -> colour_t { + distribution_t const& cultures = province.get_culture_distribution(); + if (!cultures.empty()) { + // This breaks if replaced with distribution_t::value_type, something + // about operator=(volatile const&) being deleted. + std::pair culture = *cultures.begin(); + for (distribution_t::value_type const p : cultures) { + if (p.second > culture.second) culture = p; + } + return HIGH_ALPHA_VALUE | culture.first->get_colour(); + } + return NULL_COLOUR; + } } + }; + for (mapmode_t const& mapmode : mapmodes) + if (map.add_mapmode(mapmode.first, mapmode.second) != SUCCESS) + ret = FAILURE; + 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) + if (building_manager.add_building_type(std::get<0>(type), std::get<1>(type), std::get<2>(type)) != SUCCESS) + ret = FAILURE; + building_manager.lock_building_types(); + + return ret; +} diff --git a/src/openvic/GameManager.hpp b/src/openvic/GameManager.hpp index b06c228..cffa75b 100644 --- a/src/openvic/GameManager.hpp +++ b/src/openvic/GameManager.hpp @@ -1,8 +1,8 @@ #pragma once -#include "GameAdvancementHook.hpp" -#include "economy/Good.hpp" -#include "map/Map.hpp" +#include "openvic/GameAdvancementHook.hpp" +#include "openvic/economy/Good.hpp" +#include "openvic/map/Map.hpp" namespace OpenVic { struct GameManager { @@ -31,5 +31,10 @@ namespace OpenVic { Date const& get_today() const; return_t 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. + */ + return_t load_hardcoded_defines(); }; } diff --git a/src/openvic/Simulation.hpp b/src/openvic/Simulation.hpp deleted file mode 100644 index b0c2a19..0000000 --- a/src/openvic/Simulation.hpp +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -namespace OpenVic { - class Simulation {}; -} \ No newline at end of file diff --git a/src/openvic/Types.cpp b/src/openvic/Types.cpp deleted file mode 100644 index 4902ce3..0000000 --- a/src/openvic/Types.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include "Types.hpp" - -#include -#include -#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(colour_t const colour) { - std::ostringstream stream; - stream << std::hex << std::setfill('0') << std::setw(6) << colour; - return stream.str(); -} - -std::string HasColour::colour_to_hex_string() const { - return 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 } {} diff --git a/src/openvic/Types.hpp b/src/openvic/Types.hpp deleted file mode 100644 index 3e7ac1d..0000000 --- a/src/openvic/Types.hpp +++ /dev/null @@ -1,171 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "utility/Logger.hpp" - -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); - } - - // TODO: price_t must be changed to a fixed-point numeric type before multiplayer - using price_t = double; - static constexpr price_t NULL_PRICE = 0.0; - using return_t = bool; - - // This mirrors godot::Error, where `OK = 0` and `FAILED = 1`. - static constexpr return_t SUCCESS = false, FAILURE = true; - - /* - * 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; - static std::string colour_to_hex_string(colour_t const colour); - }; - - /* - * 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::unordered_map; - - /* - * 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::value>::type* = nullptr> - 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 } {} - return_t add_item(T&& item) { - if (locked) { - Logger::error("Cannot add item to the ", name, " registry - locked!"); - return FAILURE; - } - 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 FAILURE; - } - identifier_index_map[item.get_identifier()] = items.size(); - items.push_back(std::move(item)); - return SUCCESS; - } - 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 ", get_item_count(), " items"); - } - } - bool is_locked() const { - return locked; - } - void reset() { - identifier_index_map.clear(); - items.clear(); - locked = false; - } - size_t get_item_count() const { - return items.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/dataloader/Dataloader.cpp b/src/openvic/dataloader/Dataloader.cpp new file mode 100644 index 0000000..90537c9 --- /dev/null +++ b/src/openvic/dataloader/Dataloader.cpp @@ -0,0 +1,140 @@ +#include "Dataloader.hpp" + +#include "openvic/utility/Logger.hpp" +#include "openvic//GameManager.hpp" +#include "openvic/dataloader/NodeTools.hpp" + +#include + +using namespace OpenVic; +using namespace ovdl::v2script; + +return_t Dataloader::set_roots(std::vector new_roots) { + if (!roots.empty()) { + Logger::error("Overriding existing dataloader roots!"); + roots.clear(); + } + for (std::reverse_iterator::const_iterator> it = new_roots.crbegin(); it != new_roots.crend(); ++it) { + 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); + } + } + if (roots.empty()) { + Logger::error("Dataloader has no roots after attempting to add ", new_roots.size()); + return FAILURE; + } + return SUCCESS; +} + +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 {}; +} + +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) 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() && !contains_file_with_name(ret, entry.path().filename())) { + ret.push_back(entry); + } + } + } + return ret; +} + +static Parser parse_defines(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() || parser.has_warning()) { + Logger::error("Parser warnings/errors while loading ", path); + return parser; + } + parser.simple_parse(); + if (!buffer.empty()) { + Logger::error("Parser parse errors:\n\n", buffer, "\n"); + buffer.clear(); + } + if (parser.has_fatal_error() || parser.has_error() || parser.has_warning()) { + Logger::error("Parser warnings/errors while parsing ", path); + } + return parser; +} + +return_t Dataloader::load_defines(GameManager& game_manager) const { + static const std::filesystem::path graphical_culture_type_file = "common/graphicalculturetype.txt"; + static const std::filesystem::path culture_file = "common/cultures.txt"; + + return_t ret = SUCCESS; + + if (game_manager.pop_manager.culture_manager.load_graphical_culture_type_file(parse_defines( + lookup_file(graphical_culture_type_file)).get_file_node()) != SUCCESS) { + Logger::error("Failed to load graphical culture types!"); + ret = FAILURE; + } + if (game_manager.pop_manager.culture_manager.load_culture_file(parse_defines( + lookup_file(culture_file)).get_file_node()) != SUCCESS) { + Logger::error("Failed to load cultures!"); + ret = FAILURE; + } + + return ret; +} + +static return_t load_pop_history_file(GameManager& game_manager, std::filesystem::path const& path) { + return NodeTools::expect_dictionary(parse_defines(path).get_file_node(), [&game_manager](std::string_view province_key, ast::NodeCPtr province_node) -> return_t { + Province* province = game_manager.map.get_province_by_identifier(province_key); + if (province == nullptr) { + Logger::error("Invalid province id: ", province_key); + return FAILURE; + } + return NodeTools::expect_list(province_node, [&game_manager, &province](ast::NodeCPtr pop_node) -> return_t { + return game_manager.pop_manager.load_pop_into_province(*province, pop_node); + }); + }, true); +} + +return_t Dataloader::load_pop_history(GameManager& game_manager, std::filesystem::path const& path) const { + return_t ret = SUCCESS; + for (std::filesystem::path const& file : lookup_files_in_dir(path)) { + if (load_pop_history_file(game_manager, file) != SUCCESS) { + ret = FAILURE; + } + } + return ret; +} diff --git a/src/openvic/dataloader/Dataloader.hpp b/src/openvic/dataloader/Dataloader.hpp index 062f3d5..b080300 100644 --- a/src/openvic/dataloader/Dataloader.hpp +++ b/src/openvic/dataloader/Dataloader.hpp @@ -1,16 +1,24 @@ #pragma once -#include -#include "../Simulation.hpp" + +#include "openvic/types/Return.hpp" +#include "openvic-dataloader/v2script/Parser.hpp" namespace OpenVic { + struct GameManager; + class Dataloader { - public: - enum class LoadingMode { - DL_COMPATIBILITY - }; + std::vector roots; + + public: + Dataloader() = default; + + /* In reverse-load order, so base defines first and final loaded mod last */ + return_t set_roots(std::vector new_roots); + + std::filesystem::path lookup_file(std::filesystem::path const& path) const; + std::vector lookup_files_in_dir(std::filesystem::path const& path) const; - static bool loadDir(std::filesystem::path p, Simulation& sim, LoadingMode loadMode = LoadingMode::DL_COMPATIBILITY) { - return true; - } + return_t load_defines(GameManager& game_manager) const; + return_t load_pop_history(GameManager& game_manager, std::filesystem::path const& path) const; }; -} \ No newline at end of file +} diff --git a/src/openvic/dataloader/NodeTools.cpp b/src/openvic/dataloader/NodeTools.cpp new file mode 100644 index 0000000..a37892f --- /dev/null +++ b/src/openvic/dataloader/NodeTools.cpp @@ -0,0 +1,198 @@ +#include "NodeTools.hpp" + +#include + +using namespace OpenVic; + +template +return_t NodeTools::expect_type(ast::NodeCPtr node, std::function callback) { + if (node != nullptr) { + if (node->is_type()) { + return callback(ast::cast_node_cptr(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 FAILURE; +} + +static return_t identifier_callback_wrapper(std::function callback, std::string_view identifier) { + if (!identifier.empty()) { + return callback(identifier); + } + Logger::error("Empty identifier node string"); + return FAILURE; +} + +return_t NodeTools::expect_identifier(ast::NodeCPtr node, std::function callback) { + return expect_type(node, [callback](ast::IdentifierNode const& identifier_node) -> return_t { + return identifier_callback_wrapper(callback, identifier_node._name); + }); +} + +return_t NodeTools::expect_string(ast::NodeCPtr node, std::function callback) { + return expect_type(node, [callback](ast::StringNode const& string_node) -> return_t { + return callback(string_node._name); + }); +} + +return_t NodeTools::expect_identifier_or_string(ast::NodeCPtr node, std::function callback) { + if (node != nullptr) { + if (node->is_type()) { + return identifier_callback_wrapper(callback, ast::cast_node_cptr(node)._name); + } else if (node->is_type()) { + return callback(ast::cast_node_cptr(node)._name); + } else { + 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 FAILURE; +} + +return_t NodeTools::expect_bool(ast::NodeCPtr node, std::function callback) { + return expect_identifier(node, [callback](std::string_view identifier) -> return_t { + if (identifier == "yes") { + return callback(true); + } else if (identifier == "no") { + return callback(false); + } + Logger::error("Invalid bool identifier text: ", identifier); + return FAILURE; + }); +} + +return_t NodeTools::expect_int(ast::NodeCPtr node, std::function callback) { + return expect_identifier(node, [callback](std::string_view identifier) -> return_t { + int64_t val; + char const* const start = identifier.data(); + char const* const end = start + identifier.size(); + const std::from_chars_result result = std::from_chars(start, end, val); + if (result.ec == std::errc{} && result.ptr == end) { + return callback(val); + } + Logger::error("Invalid int identifier text: ", identifier); + return FAILURE; + }); +} + +return_t NodeTools::expect_uint(ast::NodeCPtr node, std::function callback) { + return expect_identifier(node, [callback](std::string_view identifier) -> return_t { + uint64_t val; + char const* identifier_end = identifier.data() + identifier.size(); + const std::from_chars_result result = std::from_chars(identifier.data(), identifier_end, val); + if (result.ec == std::errc{} && result.ptr == identifier_end) { + return callback(val); + } + Logger::error("Invalid uint identifier text: ", identifier); + return FAILURE; + }); +} + +return_t NodeTools::expect_fixed_point(ast::NodeCPtr node, std::function callback) { + return expect_identifier(node, [callback](std::string_view identifier) -> return_t { + bool successful = false; + FP val = FP::parse(identifier.data(), identifier.length(), &successful); + if (successful) { + return callback(val); + } + Logger::error("Invalid fixed point identifier text: ", identifier); + return FAILURE; + }); +} + +return_t NodeTools::expect_colour(ast::NodeCPtr node, std::function callback) { + colour_t col = NULL_COLOUR; + uint32_t components = 0; + return_t ret = expect_list(node, std::bind(expect_fixed_point, std::placeholders::_1, + [&col, &components](FP val) -> return_t { + return_t ret = SUCCESS; + if (val < 0 || val > 255) { + Logger::error("Invalid colour component: ", val); + val = FP::_0(); + ret = FAILURE; + } + if (val <= 1) val *= 255; + col = (col << 8) | val.to_int32_t(); + components++; + return ret; + }), 3); + if (components < 3) col <<= 8 * (3 - components); + if (callback(col) != SUCCESS) ret = FAILURE; + return ret; +} + +return_t NodeTools::expect_date(ast::NodeCPtr node, std::function callback) { + return expect_identifier(node, [callback](std::string_view identifier) -> return_t { + bool successful = false; + Date date = Date::from_string(identifier, &successful); + if (successful) { + return callback(date); + } + Logger::error("Invalid date identifier text: ", identifier); + return FAILURE; + }); +} + +return_t NodeTools::expect_assign(ast::NodeCPtr node, std::function callback) { + return expect_type(node, [callback](ast::AssignNode const& assign_node) -> return_t { + return callback(assign_node._name, assign_node._initializer.get()); + }); +} + +return_t NodeTools::expect_list(ast::NodeCPtr node, std::function callback, size_t length, bool file_node) { + const std::function const&)> list_func = [length, callback](std::vector const& list) -> return_t { + return_t ret = SUCCESS; + size_t size = list.size(); + if (length > 0 && size != length) { + Logger::error("List length ", size, " does not match expected length ", length); + ret = FAILURE; + if (length < size) size = length; + } + std::for_each(list.begin(), list.begin() + size, [callback, &ret](ast::NodeUPtr const& sub_node) { + if (callback(sub_node.get()) != SUCCESS) ret = FAILURE; + }); + return ret; + }; + if (!file_node) { + return expect_type(node, [list_func](ast::ListNode const& list_node) -> return_t { + return list_func(list_node._statements); + }); + } else { + return expect_type(node, [list_func](ast::FileNode const& file_node) -> return_t { + return list_func(file_node._statements); + }); + } +} + +return_t NodeTools::expect_dictionary(ast::NodeCPtr node, std::function callback, bool file_node) { + return expect_list(node, std::bind(expect_assign, std::placeholders::_1, callback), 0, file_node); +} + +return_t NodeTools::expect_dictionary_keys(ast::NodeCPtr node, dictionary_key_map_t const& keys, bool allow_other_keys, bool file_node) { + std::map> key_count; + return_t ret = expect_dictionary(node, [keys, allow_other_keys, &key_count](const std::string_view key, ast::NodeCPtr value) -> return_t { + const dictionary_key_map_t::const_iterator it = keys.find(key); + if (it == keys.end()) { + if (allow_other_keys) return SUCCESS; + Logger::error("Invalid dictionary key: ", key); + return FAILURE; + } + const size_t count = ++key_count[it->first]; + dictionary_entry_t const& entry = it->second; + if (!entry.can_repeat && count > 1) { + Logger::error("Invalid repeat of dictionary key: ", key); + return FAILURE; + } + return entry.callback(value); + }, file_node); + for (dictionary_key_map_t::value_type const& entry : keys) { + if (entry.second.must_appear && key_count.find(entry.first) == key_count.end()) { + Logger::error("Mandatory dictionary key not present: ", entry.first); + ret = FAILURE; + } + } + return ret; +} diff --git a/src/openvic/dataloader/NodeTools.hpp b/src/openvic/dataloader/NodeTools.hpp new file mode 100644 index 0000000..ca7130c --- /dev/null +++ b/src/openvic/dataloader/NodeTools.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include + +#include + +#include "openvic/types/Colour.hpp" +#include "openvic/types/Return.hpp" +#include "openvic/types/Date.hpp" +#include "openvic/types/fixed_point/FP.hpp" + +namespace OpenVic { + namespace ast = ovdl::v2script::ast; + + namespace NodeTools { + + template + return_t expect_type(ast::NodeCPtr node, std::function callback); + + return_t expect_identifier(ast::NodeCPtr node, std::function callback); + return_t expect_string(ast::NodeCPtr node, std::function callback); + return_t expect_identifier_or_string(ast::NodeCPtr node, std::function callback); + return_t expect_bool(ast::NodeCPtr node, std::function callback); + return_t expect_int(ast::NodeCPtr node, std::function callback); + return_t expect_uint(ast::NodeCPtr node, std::function callback); + return_t expect_fixed_point(ast::NodeCPtr node, std::function callback); + return_t expect_colour(ast::NodeCPtr node, std::function callback); + return_t expect_date(ast::NodeCPtr node, std::function callback); + return_t expect_assign(ast::NodeCPtr node, std::function callback); + return_t expect_list(ast::NodeCPtr node, std::function callback, size_t length = 0, bool file_node = false); + return_t expect_dictionary(ast::NodeCPtr node, std::function callback, bool file_node = false); + + static const std::function success_callback = [](ast::NodeCPtr) -> return_t { return SUCCESS; }; + + struct dictionary_entry_t { + bool must_appear, can_repeat; + std::function callback; + }; + using dictionary_key_map_t = std::map>; + return_t expect_dictionary_keys(ast::NodeCPtr node, dictionary_key_map_t const& keys, bool allow_other_keys = false, bool file_node = false); + } +} diff --git a/src/openvic/economy/Good.cpp b/src/openvic/economy/Good.cpp index 9e8a7dd..f14c170 100644 --- a/src/openvic/economy/Good.cpp +++ b/src/openvic/economy/Good.cpp @@ -20,11 +20,11 @@ std::string const& Good::get_category() const { return category; } -price_t Good::get_base_price() const { +Good::price_t Good::get_base_price() const { return base_price; } -price_t Good::get_price() const { +Good::price_t Good::get_price() const { return price; } @@ -44,7 +44,7 @@ void Good::reset_to_defaults() { GoodManager::GoodManager() : goods { "goods" } {} return_t GoodManager::add_good(const std::string_view identifier, colour_t colour, const std::string_view category, - price_t base_price, bool default_available, bool tradeable, bool currency, bool overseas_maintenance) { + Good::price_t base_price, bool default_available, bool tradeable, bool currency, bool overseas_maintenance) { if (identifier.empty()) { Logger::error("Invalid good identifier - empty!"); return FAILURE; @@ -57,7 +57,7 @@ return_t GoodManager::add_good(const std::string_view identifier, colour_t colou Logger::error("Invalid good category for ", identifier, ": empty!"); return FAILURE; } - if (base_price <= NULL_PRICE) { + if (base_price <= Good::NULL_PRICE) { Logger::error("Invalid base price for ", identifier, ": ", base_price); return FAILURE; } diff --git a/src/openvic/economy/Good.hpp b/src/openvic/economy/Good.hpp index cfe185d..5c8e68c 100644 --- a/src/openvic/economy/Good.hpp +++ b/src/openvic/economy/Good.hpp @@ -1,6 +1,7 @@ #pragma once -#include "../Types.hpp" +#include "openvic/types/IdentifierRegistry.hpp" +#include "openvic/types/fixed_point/FP.hpp" namespace OpenVic { struct GoodManager; @@ -20,6 +21,9 @@ namespace OpenVic { struct Good : HasIdentifierAndColour { friend struct GoodManager; + using price_t = FP; + static constexpr price_t NULL_PRICE = FP::_0(); + private: const std::string category; const price_t base_price; @@ -48,7 +52,7 @@ namespace OpenVic { public: GoodManager(); - return_t add_good(const std::string_view identifier, colour_t colour, const std::string_view category, price_t base_price, + return_t add_good(const std::string_view identifier, colour_t colour, const std::string_view category, Good::price_t base_price, bool default_available, bool tradeable, bool currency, bool overseas_maintenance); void lock_goods(); void reset_to_defaults(); diff --git a/src/openvic/map/Building.cpp b/src/openvic/map/Building.cpp index 73a1886..d2662c4 100644 --- a/src/openvic/map/Building.cpp +++ b/src/openvic/map/Building.cpp @@ -2,8 +2,8 @@ #include -#include "../utility/Logger.hpp" -#include "Province.hpp" +#include "openvic/utility/Logger.hpp" +#include "openvic/map/Province.hpp" using namespace OpenVic; diff --git a/src/openvic/map/Building.hpp b/src/openvic/map/Building.hpp index c00932b..2ff9409 100644 --- a/src/openvic/map/Building.hpp +++ b/src/openvic/map/Building.hpp @@ -2,8 +2,8 @@ #include -#include "../Date.hpp" -#include "../Types.hpp" +#include "openvic/types/Date.hpp" +#include "openvic/types/IdentifierRegistry.hpp" namespace OpenVic { diff --git a/src/openvic/map/Map.cpp b/src/openvic/map/Map.cpp index d5cfdd2..95d94fd 100644 --- a/src/openvic/map/Map.cpp +++ b/src/openvic/map/Map.cpp @@ -3,8 +3,8 @@ #include #include -#include "../economy/Good.hpp" -#include "../utility/Logger.hpp" +#include "openvic/economy/Good.hpp" +#include "openvic/utility/Logger.hpp" using namespace OpenVic; @@ -403,9 +403,6 @@ return_t Map::setup(GoodManager const& good_manager, BuildingManager const& buil 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()); if (building_manager.generate_province_buildings(province) != SUCCESS) ret = FAILURE; - // Add some pops to the province (for testing purposes) - if (!province.is_water()) - pop_manager.generate_test_pops(province); } return ret; } diff --git a/src/openvic/map/Map.hpp b/src/openvic/map/Map.hpp index 1b21f5d..26c07c8 100644 --- a/src/openvic/map/Map.hpp +++ b/src/openvic/map/Map.hpp @@ -2,7 +2,7 @@ #include -#include "Region.hpp" +#include "openvic/map/Region.hpp" namespace OpenVic { @@ -21,6 +21,8 @@ namespace OpenVic { 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; }; diff --git a/src/openvic/map/Province.cpp b/src/openvic/map/Province.cpp index 3de7263..76e478f 100644 --- a/src/openvic/map/Province.cpp +++ b/src/openvic/map/Province.cpp @@ -65,11 +65,13 @@ std::string Province::to_string() const { return stream.str(); } -void Province::add_pop(Pop&& pop) { - if (is_water()) { - Logger::error("Trying to add pop to water province ", get_identifier()); - } else { +return_t Province::add_pop(Pop&& pop) { + if (!is_water()) { pops.push_back(std::move(pop)); + return SUCCESS; + } else { + Logger::error("Trying to add pop to water province ", get_identifier()); + return FAILURE; } } diff --git a/src/openvic/map/Province.hpp b/src/openvic/map/Province.hpp index c2a8ac9..3556bcb 100644 --- a/src/openvic/map/Province.hpp +++ b/src/openvic/map/Province.hpp @@ -1,7 +1,7 @@ #pragma once -#include "../pop/Pop.hpp" -#include "Building.hpp" +#include "openvic/pop/Pop.hpp" +#include "openvic/map/Building.hpp" namespace OpenVic { struct Map; @@ -50,7 +50,7 @@ namespace OpenVic { Good const* get_rgo() const; std::string to_string() const; - void add_pop(Pop&& pop); + return_t add_pop(Pop&& pop); void clear_pops(); Pop::pop_size_t get_total_population() const; distribution_t const& get_pop_type_distribution() const; diff --git a/src/openvic/map/Region.cpp b/src/openvic/map/Region.cpp index fc207e3..8ea45f0 100644 --- a/src/openvic/map/Region.cpp +++ b/src/openvic/map/Region.cpp @@ -1,7 +1,5 @@ #include "Region.hpp" -#include - using namespace OpenVic; return_t ProvinceSet::add_province(Province* province) { diff --git a/src/openvic/map/Region.hpp b/src/openvic/map/Region.hpp index beeb76e..9b5b914 100644 --- a/src/openvic/map/Region.hpp +++ b/src/openvic/map/Region.hpp @@ -1,6 +1,6 @@ #pragma once -#include "Province.hpp" +#include "openvic/map/Province.hpp" namespace OpenVic { diff --git a/src/openvic/pop/Culture.cpp b/src/openvic/pop/Culture.cpp index f50bb23..3bfb9b9 100644 --- a/src/openvic/pop/Culture.cpp +++ b/src/openvic/pop/Culture.cpp @@ -1,15 +1,16 @@ #include "Culture.hpp" -#include +#include "openvic/dataloader/NodeTools.hpp" using namespace OpenVic; GraphicalCultureType::GraphicalCultureType(const std::string_view new_identifier) : HasIdentifier { new_identifier } {} CultureGroup::CultureGroup(const std::string_view new_identifier, - GraphicalCultureType const& new_unit_graphical_culture_type) + GraphicalCultureType const& new_unit_graphical_culture_type, bool new_is_overseas) : HasIdentifier { new_identifier }, - unit_graphical_culture_type { new_unit_graphical_culture_type } {} + unit_graphical_culture_type { new_unit_graphical_culture_type }, + is_overseas { new_is_overseas } {} GraphicalCultureType const& CultureGroup::get_unit_graphical_culture_type() const { return unit_graphical_culture_type; @@ -20,8 +21,7 @@ Culture::Culture(const std::string_view new_identifier, colour_t new_colour, Cul : HasIdentifierAndColour { new_identifier, new_colour, true }, group { new_group }, first_names { new_first_names }, - last_names { new_last_names } { -} + last_names { new_last_names } {} CultureGroup const& Culture::get_group() const { return group; @@ -48,7 +48,7 @@ GraphicalCultureType const* CultureManager::get_graphical_culture_type_by_identi return graphical_culture_types.get_item_by_identifier(identifier); } -return_t CultureManager::add_culture_group(const std::string_view identifier, GraphicalCultureType const* graphical_culture_type) { +return_t CultureManager::add_culture_group(const std::string_view identifier, 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 FAILURE; @@ -61,7 +61,7 @@ return_t CultureManager::add_culture_group(const std::string_view identifier, Gr Logger::error("Null graphical culture type for ", identifier); return FAILURE; } - return culture_groups.add_item({ identifier, *graphical_culture_type }); + return culture_groups.add_item({ identifier, *graphical_culture_type, is_overseas }); } void CultureManager::lock_culture_groups() { @@ -89,7 +89,6 @@ return_t CultureManager::add_culture(const std::string_view identifier, colour_t Logger::error("Invalid culture colour for ", identifier, ": ", Culture::colour_to_hex_string(colour)); return FAILURE; } - // TODO - name list sanatisation? return cultures.add_item({ identifier, colour, *group, first_names, last_names }); } @@ -100,3 +99,99 @@ void CultureManager::lock_cultures() { Culture const* CultureManager::get_culture_by_identifier(const std::string_view identifier) const { return cultures.get_item_by_identifier(identifier); } + +return_t CultureManager::load_graphical_culture_type_file(ast::NodeCPtr root) { + const return_t ret = NodeTools::expect_list(root, [this](ast::NodeCPtr node) -> return_t { + return NodeTools::expect_identifier(node, [this](std::string_view identifier) -> return_t { + return add_graphical_culture_type(identifier); + }); + }, 0, true); + lock_graphical_culture_types(); + return ret; +} + +return_t 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 FAILURE; + } + + 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); + } + + return_t ret = NodeTools::expect_dictionary(root, [this, default_unit_graphical_culture_type](std::string_view key, ast::NodeCPtr value) -> return_t { + + GraphicalCultureType const* unit_graphical_culture_type = default_unit_graphical_culture_type; + bool is_overseas = true; + + return_t ret = NodeTools::expect_dictionary_keys(value, { + { "leader", { true, false, NodeTools::success_callback } }, + { "unit", { false, false, [this, &unit_graphical_culture_type](ast::NodeCPtr node) -> return_t { + return NodeTools::expect_identifier(node, [this, &unit_graphical_culture_type](std::string_view identifier) -> return_t { + unit_graphical_culture_type = get_graphical_culture_type_by_identifier(identifier); + if (unit_graphical_culture_type != nullptr) return SUCCESS; + Logger::error("Invalid unit graphical culture type: ", identifier); + return FAILURE; + }); + } } }, + { "union", { false, false, NodeTools::success_callback } }, + { "is_overseas", { false, false, [&is_overseas](ast::NodeCPtr node) -> return_t { + return NodeTools::expect_bool(node, [&is_overseas](bool val) -> return_t { + is_overseas = val; + return SUCCESS; + }); + } } } + }, true); + if (add_culture_group(key, unit_graphical_culture_type, is_overseas) != SUCCESS) ret = FAILURE; + return ret; + }, true); + lock_culture_groups(); + if (NodeTools::expect_dictionary(root, [this](std::string_view culture_group_key, ast::NodeCPtr culture_group_value) -> return_t { + + CultureGroup const* culture_group = get_culture_group_by_identifier(culture_group_key); + + return NodeTools::expect_dictionary(culture_group_value, [this, culture_group](std::string_view key, ast::NodeCPtr value) -> return_t { + if (key == "leader" || key == "unit" || key == "union" || key == "is_overseas") return SUCCESS; + + colour_t colour = NULL_COLOUR; + Culture::name_list_t first_names, last_names; + + static const std::function read_name_list = [](Culture::name_list_t& names, ast::NodeCPtr node) -> return_t { + return NodeTools::expect_list(node, [&names](ast::NodeCPtr val) -> return_t { + return NodeTools::expect_identifier_or_string(val, [&names](std::string_view str) -> return_t { + if (!str.empty()) { + names.push_back(std::string { str }); + return SUCCESS; + } + Logger::error("Empty identifier or string"); + return FAILURE; + }); + }); + }; + + return_t ret = NodeTools::expect_dictionary_keys(value, { + { "color", { true, false, [&colour](ast::NodeCPtr node) -> return_t { + return NodeTools::expect_colour(node, [&colour](colour_t val) -> return_t { + colour = val; + return SUCCESS; + }); + } } }, + { "first_names", { true, false, [&first_names](ast::NodeCPtr node) -> return_t { + return read_name_list(first_names, node); + } } }, + { "last_names", { true, false, [&last_names](ast::NodeCPtr node) -> return_t { + return read_name_list(last_names, node); + } } }, + { "radicalism", { false, false, NodeTools::success_callback } }, + { "primary", { false, false, NodeTools::success_callback } } + }); + if (add_culture(key, colour, culture_group, first_names, last_names) != SUCCESS) ret = FAILURE; + return ret; + }); + }, true) != SUCCESS) ret = FAILURE; + lock_cultures(); + return ret; +} diff --git a/src/openvic/pop/Culture.hpp b/src/openvic/pop/Culture.hpp index 645e226..7012512 100644 --- a/src/openvic/pop/Culture.hpp +++ b/src/openvic/pop/Culture.hpp @@ -1,6 +1,7 @@ #pragma once -#include "../Types.hpp" +#include "openvic/types/IdentifierRegistry.hpp" +#include "openvic/dataloader/NodeTools.hpp" namespace OpenVic { @@ -21,10 +22,11 @@ namespace OpenVic { private: GraphicalCultureType const& unit_graphical_culture_type; + bool is_overseas; // TODO - leader type, union tag - CultureGroup(const std::string_view new_identifier, GraphicalCultureType const& new_unit_graphical_culture_type); + CultureGroup(const std::string_view new_identifier, GraphicalCultureType const& new_unit_graphical_culture_type, bool new_is_overseas); public: CultureGroup(CultureGroup&&) = default; @@ -63,11 +65,14 @@ namespace OpenVic { return_t 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; - return_t add_culture_group(const std::string_view identifier, GraphicalCultureType const* new_graphical_culture_type); + return_t add_culture_group(const std::string_view identifier, 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; return_t add_culture(const std::string_view identifier, colour_t colour, CultureGroup const* group, Culture::name_list_t const& first_names, Culture::name_list_t const& last_names); void lock_cultures(); Culture const* get_culture_by_identifier(const std::string_view identifier) const; + + return_t load_graphical_culture_type_file(ast::NodeCPtr node); + return_t load_culture_file(ast::NodeCPtr node); }; } diff --git a/src/openvic/pop/Pop.cpp b/src/openvic/pop/Pop.cpp index 81915f6..710d3e0 100644 --- a/src/openvic/pop/Pop.cpp +++ b/src/openvic/pop/Pop.cpp @@ -2,8 +2,9 @@ #include -#include "../map/Province.hpp" -#include "../utility/Logger.hpp" +#include "openvic/dataloader/NodeTools.hpp" +#include "openvic/map/Province.hpp" +#include "openvic/utility/Logger.hpp" using namespace OpenVic; @@ -106,18 +107,7 @@ static const std::string test_pop_type_poor = "test_pop_type_poor"; static const std::string test_pop_type_middle = "test_pop_type_middle"; static const std::string test_pop_type_rich = "test_pop_type_rich"; - PopManager::PopManager() : pop_types { "pop types" } { - culture_manager.add_graphical_culture_type(test_graphical_culture_type); - culture_manager.lock_graphical_culture_types(); - - culture_manager.add_culture_group(test_culture_group, culture_manager.get_graphical_culture_type_by_identifier(test_graphical_culture_type)); - culture_manager.lock_culture_groups(); - - culture_manager.add_culture(test_culture, 0x0000FF, culture_manager.get_culture_group_by_identifier(test_culture_group), - { "john" }, { "smith" }); - culture_manager.lock_cultures(); - religion_manager.add_religion_group(test_religion_group); religion_manager.lock_religion_groups(); @@ -163,18 +153,45 @@ PopType const* PopManager::get_pop_type_by_identifier(const std::string_view ide return pop_types.get_item_by_identifier(identifier); } -void PopManager::generate_test_pops(Province& province) const { - if (pop_types.is_locked()) { - static PopType const& type_poor = *get_pop_type_by_identifier(test_pop_type_poor); - static PopType const& type_middle = *get_pop_type_by_identifier(test_pop_type_middle); - static PopType const& type_rich = *get_pop_type_by_identifier(test_pop_type_rich); - static Culture const& culture = *culture_manager.get_culture_by_identifier(test_culture); - static Religion const& religion = *religion_manager.get_religion_by_identifier(test_religion); - - province.add_pop({ type_poor, culture, religion, static_cast(province.get_index() * province.get_index()) * 100 }); - province.add_pop({ type_middle, culture, religion, static_cast(province.get_index() * province.get_index()) * 50 }); - province.add_pop({ type_rich, culture, religion, static_cast(province.get_index()) * 1000 }); +return_t PopManager::load_pop_into_province(Province& province, ast::NodeCPtr root) const { + static PopType const* type = get_pop_type_by_identifier("test_pop_type_poor"); + Culture const* culture = nullptr; + static Religion const* religion = religion_manager.get_religion_by_identifier("test_religion"); + Pop::pop_size_t size = 0; + + return_t ret = NodeTools::expect_assign(root, [this, &culture, &size](std::string_view, ast::NodeCPtr pop_node) -> return_t { + return NodeTools::expect_dictionary_keys(pop_node, { + { "culture", { true, false, [this, &culture](ast::NodeCPtr node) -> return_t { + return NodeTools::expect_identifier(node, [&culture, this](std::string_view identifier) -> return_t { + culture = culture_manager.get_culture_by_identifier(identifier); + if (culture != nullptr) return SUCCESS; + Logger::error("Invalid pop culture: ", identifier); + return FAILURE; + }); + } } }, + { "religion", { true, false, NodeTools::success_callback } }, + { "size", { true, false, [&size](ast::NodeCPtr node) -> return_t { + return NodeTools::expect_uint(node, [&size](uint64_t val) -> return_t { + if (val > 0) { + size = val; + return SUCCESS; + } else { + Logger::error("Invalid pop size: ", val, " (setting to 1 instead)"); + size = 1; + return FAILURE; + } + }); + } } }, + { "militancy", { false, false, NodeTools::success_callback } }, + { "rebel_type", { false, false, NodeTools::success_callback } } + }); + }); + + if (type != nullptr && culture != nullptr && religion != nullptr && size > 0) { + if (province.add_pop({ *type, *culture, *religion, size }) != SUCCESS) ret = FAILURE; } else { - Logger::error("Cannot generate pops before pop types registry is locked!"); + Logger::error("Some pop arguments are missing: type = ", type, ", culture = ", culture, ", religion = ", religion, ", size = ", size); + ret = FAILURE; } + return ret; } diff --git a/src/openvic/pop/Pop.hpp b/src/openvic/pop/Pop.hpp index db9633f..f5a7c4e 100644 --- a/src/openvic/pop/Pop.hpp +++ b/src/openvic/pop/Pop.hpp @@ -1,8 +1,7 @@ #pragma once -#include "../Types.hpp" -#include "Culture.hpp" -#include "Religion.hpp" +#include "openvic/pop/Culture.hpp" +#include "openvic/pop/Religion.hpp" namespace OpenVic { @@ -21,7 +20,7 @@ namespace OpenVic { PopType const& type; Culture const& culture; Religion const& religion; - pop_size_t size, num_migrated, num_promoted, num_demoted, num_migrated; + 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); @@ -82,10 +81,11 @@ namespace OpenVic { struct PopManager { private: IdentifierRegistry pop_types; + + public: CultureManager culture_manager; ReligionManager religion_manager; - public: PopManager(); return_t add_pop_type(const std::string_view identifier, colour_t new_colour, PopType::strata_t strata, PopType::sprite_t sprite, @@ -94,6 +94,6 @@ namespace OpenVic { void lock_pop_types(); PopType const* get_pop_type_by_identifier(const std::string_view identifier) const; - void generate_test_pops(Province& province) const; + return_t load_pop_into_province(Province& province, ast::NodeCPtr root) const; }; } diff --git a/src/openvic/pop/Religion.hpp b/src/openvic/pop/Religion.hpp index 4eb3e4c..e629977 100644 --- a/src/openvic/pop/Religion.hpp +++ b/src/openvic/pop/Religion.hpp @@ -1,6 +1,6 @@ #pragma once -#include "../Types.hpp" +#include "openvic/types/IdentifierRegistry.hpp" namespace OpenVic { diff --git a/src/openvic/types/Colour.hpp b/src/openvic/types/Colour.hpp new file mode 100644 index 0000000..fdac40e --- /dev/null +++ b/src/openvic/types/Colour.hpp @@ -0,0 +1,27 @@ +#pragma once + +#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); + } +} diff --git a/src/openvic/types/Date.cpp b/src/openvic/types/Date.cpp new file mode 100644 index 0000000..b6e1367 --- /dev/null +++ b/src/openvic/types/Date.cpp @@ -0,0 +1,240 @@ +#include "Date.hpp" + +#include +#include +#include +#include + +#include "openvic/utility/Logger.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; +} + +Timespan::operator std::string() const { + return std::to_string(days); +} + +std::ostream& OpenVic::operator<<(std::ostream& out, Timespan const& timespan) { + return out << static_cast(timespan); +} + +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; +} + +Date::operator std::string() const { + std::stringstream ss; + ss << *this; + return ss.str(); +} + +std::ostream& OpenVic::operator<<(std::ostream& out, Date const& date) { + return out << static_cast(date.getYear()) << '.' << static_cast(date.getMonth()) << '.' << static_cast(date.getDay()); +} + +// Parsed from string of the form YYYY.MM.DD +Date Date::from_string(const std::string_view date, bool* successful) { + if (successful != nullptr) *successful = true; + size_t first_pos = 0; + while (first_pos < date.length() && std::isdigit(date[first_pos])) { + first_pos++; + } + + if (first_pos == 0) { + Logger::error("Failed to find year digits in date: ", date); + if (successful != nullptr) *successful = false; + return {}; + } + + int val = 0; + char const* start = date.data(); + char const* end = start + first_pos; + std::from_chars_result result = std::from_chars(start, end, val); + if (result.ec != std::errc{} || result.ptr != end || val < 0 || val >= 1 << (8 * sizeof(year_t))) { + Logger::error("Failed to read year: ", date); + if (successful != nullptr) *successful = false; + return {}; + } + year_t year = val; + month_t month = 1; + day_t day = 1; + if (first_pos < date.length()) { + if (date[first_pos] == '.') { + size_t second_pos = ++first_pos; + while (second_pos < date.length() && std::isdigit(date[second_pos])) { + second_pos++; + } + if (first_pos == second_pos) { + Logger::error("Failed to find month digits in date: ", date); + if (successful != nullptr) *successful = false; + } else { + start = date.data() + first_pos; + end = date.data() + second_pos; + result = std::from_chars(start, end, val); + if (result.ec != std::errc{} || result.ptr != end || val < 1 || val > MONTHS_IN_YEAR) { + Logger::error("Failed to read month: ", date); + if (successful != nullptr) *successful = false; + } else { + month = val; + if (second_pos < date.length()) { + if (date[second_pos] == '.') { + size_t third_pos = ++second_pos; + while (third_pos < date.length() && std::isdigit(date[third_pos])) { + third_pos++; + } + if (second_pos == third_pos) { + Logger::error("Failed to find day digits in date: ", date); + if (successful != nullptr) *successful = false; + } else { + start = date.data() + second_pos; + end = date.data() + third_pos; + result = std::from_chars(start, end, val); + if (result.ec != std::errc{} || result.ptr != end || val < 1 || val > DAYS_IN_MONTH[month - 1]) { + Logger::error("Failed to read day: ", date); + if (successful != nullptr) *successful = false; + } else { + day = val; + if (third_pos < date.length()) { + Logger::error("Unexpected string \"", date.substr(third_pos), "\" at the end of date ", date); + if (successful != nullptr) *successful = false; + } + } + } + } else { + Logger::error("Unexpected character \"", date[second_pos], "\" in month of date ", date); + if (successful != nullptr) *successful = false; + } + } + } + } + } else { + Logger::error("Unexpected character \"", date[first_pos], "\" in year of date ", date); + if (successful != nullptr) *successful = false; + } + } + return _dateToTimespan(year, month, day); +}; diff --git a/src/openvic/types/Date.hpp b/src/openvic/types/Date.hpp new file mode 100644 index 0000000..ca5645c --- /dev/null +++ b/src/openvic/types/Date.hpp @@ -0,0 +1,90 @@ +#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; + 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; + + 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); + + explicit operator std::string() const; + // Parsed from string of the form YYYY.MM.DD + static Date from_string(const std::string_view date, 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 new file mode 100644 index 0000000..97a1ff5 --- /dev/null +++ b/src/openvic/types/IdentifierRegistry.cpp @@ -0,0 +1,37 @@ +#include "IdentifierRegistry.hpp" + +#include +#include +#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(colour_t const colour) { + std::ostringstream stream; + stream << std::hex << std::setfill('0') << std::setw(6) << colour; + return stream.str(); +} + +std::string HasColour::colour_to_hex_string() const { + return 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 } {} diff --git a/src/openvic/types/IdentifierRegistry.hpp b/src/openvic/types/IdentifierRegistry.hpp new file mode 100644 index 0000000..e5ac94b --- /dev/null +++ b/src/openvic/types/IdentifierRegistry.hpp @@ -0,0 +1,142 @@ +#pragma once + +#include +#include +#include + +#include "openvic/types/Colour.hpp" +#include "openvic/types/Return.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; + static std::string colour_to_hex_string(colour_t const colour); + }; + + /* + * 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::unordered_map; + + /* + * 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::value>::type* = nullptr> + 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 } {} + return_t add_item(T&& item) { + if (locked) { + Logger::error("Cannot add item to the ", name, " registry - locked!"); + return FAILURE; + } + 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 FAILURE; + } + identifier_index_map[item.get_identifier()] = items.size(); + items.push_back(std::move(item)); + return SUCCESS; + } + 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 ", get_item_count(), " items"); + } + } + bool is_locked() const { + return locked; + } + void reset() { + identifier_index_map.clear(); + items.clear(); + locked = false; + } + size_t get_item_count() const { + return items.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/Return.hpp b/src/openvic/types/Return.hpp new file mode 100644 index 0000000..04c3734 --- /dev/null +++ b/src/openvic/types/Return.hpp @@ -0,0 +1,8 @@ +#pragma once + +namespace OpenVic { + using return_t = bool; + + // This mirrors godot::Error, where `OK = 0` and `FAILED = 1`. + constexpr return_t SUCCESS = false, FAILURE = true; +} diff --git a/src/openvic/types/fixed_point/FP.hpp b/src/openvic/types/fixed_point/FP.hpp new file mode 100644 index 0000000..8b32906 --- /dev/null +++ b/src/openvic/types/fixed_point/FP.hpp @@ -0,0 +1,563 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "FPLUT.hpp" +#include "openvic/utility/FloatUtils.hpp" +#include "openvic/utility/StringUtils.hpp" +#include "openvic/utility/Logger.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 FP abs() const { + return value >= 0 ? value : -value; + } + + // Not including sign, so -1.1 -> 0.1 + constexpr FP get_frac() const { + return abs().value & ((1 << FPLUT::PRECISION) - 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(value >> FPLUT::PRECISION); + } + + constexpr float to_float() const { + return value / 65536.0F; + } + + constexpr float to_float_rounded() const { + return static_cast(FloatUtils::round_to_int64((value / 65536.0F) * 100000.0F)) / 100000.0F; + } + + constexpr float to_double() const { + return value / 65536.0; + } + + constexpr float to_double_rounded() const { + return FloatUtils::round_to_int64((value / 65536.0) * 100000.0) / 100000.0; + } + + std::string to_string() const { + std::string str = std::to_string(to_int64_t()) + "."; + FP frac_part = get_frac(); + do { + frac_part.value *= 10; + str += std::to_string(frac_part.to_int64_t()); + frac_part = frac_part.get_frac(); + } while (frac_part > 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(const char* str, const char* 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(); + } + + const char* 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(const char* str, size_t length, bool *successful = nullptr) { + return parse(str, 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(const char* 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, const FP& obj) { + return stream << obj.to_string(); + } + + constexpr friend FP operator-(const FP& obj) { + return -obj.value; + } + + constexpr friend FP operator+(const FP& obj) { + return +obj.value; + } + + constexpr friend FP operator+(const FP& lhs, const FP& rhs) { + return lhs.value + rhs.value; + } + + constexpr friend FP operator+(const FP& lhs, const int32_t& rhs) { + return lhs.value + (static_cast(rhs) << FPLUT::PRECISION); + } + + constexpr friend FP operator+(const int32_t& lhs, const FP& rhs) { + return (static_cast(lhs) << FPLUT::PRECISION) + rhs.value; + } + + constexpr FP operator+=(const FP& obj) { + value += obj.value; + return *this; + } + + constexpr FP operator+=(const int32_t& obj) { + value += (static_cast(obj) << FPLUT::PRECISION); + return *this; + } + + constexpr friend FP operator-(const FP& lhs, const FP& rhs) { + return lhs.value - rhs.value; + } + + constexpr friend FP operator-(const FP& lhs, const int32_t& rhs) { + return lhs.value - (static_cast(rhs) << FPLUT::PRECISION); + } + + constexpr friend FP operator-(const int32_t& lhs, const FP& rhs) { + return (static_cast(lhs) << FPLUT::PRECISION) - rhs.value; + } + + constexpr FP operator-=(const FP& obj) { + value -= obj.value; + return *this; + } + + constexpr FP operator-=(const int32_t& obj) { + value -= (static_cast(obj) << FPLUT::PRECISION); + return *this; + } + + constexpr friend FP operator*(const FP& lhs, const FP& rhs) { + return lhs.value * rhs.value >> FPLUT::PRECISION; + } + + constexpr friend FP operator*(const FP& lhs, const int32_t& rhs) { + return lhs.value * rhs; + } + + constexpr friend FP operator*(const int32_t& lhs, const FP& rhs) { + return lhs * rhs.value; + } + + constexpr FP operator*=(const FP& obj) { + value *= obj.value >> FPLUT::PRECISION; + return *this; + } + + constexpr FP operator*=(const int32_t& obj) { + value *= obj; + return *this; + } + + constexpr friend FP operator/(const FP& lhs, const FP& rhs) { + return (lhs.value << FPLUT::PRECISION) / rhs.value; + } + + constexpr friend FP operator/(const FP& lhs, const int32_t& rhs) { + return lhs.value / rhs; + } + + constexpr friend FP operator/(const int32_t& lhs, const FP& rhs) { + return (static_cast(lhs) << (2 / FPLUT::PRECISION)) / rhs.value; + } + + constexpr FP operator/=(const FP& obj) { + value = (value << FPLUT::PRECISION) / obj.value; + return *this; + } + + constexpr FP operator/=(const int32_t& obj) { + value /= obj; + return *this; + } + + constexpr friend FP operator%(const FP& lhs, const FP& rhs) { + return lhs.value % rhs.value; + } + + constexpr friend FP operator%(const FP& lhs, const int32_t& rhs) { + return lhs.value % (static_cast(rhs) << FPLUT::PRECISION); + } + + constexpr friend FP operator%(const int32_t& lhs, const FP& rhs) { + return (static_cast(lhs) << FPLUT::PRECISION) % rhs.value; + } + + constexpr FP operator%=(const FP& obj) { + value %= obj.value; + return *this; + } + + constexpr FP operator%=(const int32_t& obj) { + value %= (static_cast(obj) << FPLUT::PRECISION); + return *this; + } + + constexpr friend bool operator<(const FP& lhs, const FP& rhs) { + return lhs.value < rhs.value; + } + + constexpr friend bool operator<(const FP& lhs, const int32_t& rhs) { + return lhs.value < static_cast(rhs) << FPLUT::PRECISION; + } + + constexpr friend bool operator<(const int32_t& lhs, const FP& rhs) { + return static_cast(lhs) << FPLUT::PRECISION < rhs.value; + } + + constexpr friend bool operator<=(const FP& lhs, const FP& rhs) { + return lhs.value <= rhs.value; + } + + constexpr friend bool operator<=(const FP& lhs, const int32_t& rhs) { + return lhs.value <= static_cast(rhs) << FPLUT::PRECISION; + } + + constexpr friend bool operator<=(const int32_t& lhs, const FP& rhs) { + return static_cast(lhs) << FPLUT::PRECISION <= rhs.value; + } + + constexpr friend bool operator>(const FP& lhs, const FP& rhs) { + return lhs.value > rhs.value; + } + + constexpr friend bool operator>(const FP& lhs, const int32_t& rhs) { + return lhs.value > static_cast(rhs) << FPLUT::PRECISION; + } + + constexpr friend bool operator>(const int32_t& lhs, const FP& rhs) { + return static_cast(lhs) << FPLUT::PRECISION > rhs.value; + } + + constexpr friend bool operator>=(const FP& lhs, const FP& rhs) { + return lhs.value >= rhs.value; + } + + constexpr friend bool operator>=(const FP& lhs, const int32_t& rhs) { + return lhs.value >= static_cast(rhs) << FPLUT::PRECISION; + } + + constexpr friend bool operator>=(const int32_t& lhs, const FP& rhs) { + return static_cast(lhs) << FPLUT::PRECISION >= rhs.value; + } + + constexpr friend bool operator==(const FP& lhs, const FP& rhs) { + return lhs.value == rhs.value; + } + + constexpr friend bool operator==(const FP& lhs, const int32_t& rhs) { + return lhs.value == static_cast(rhs) << FPLUT::PRECISION; + } + + constexpr friend bool operator==(const int32_t& lhs, const FP& rhs) { + return static_cast(lhs) << FPLUT::PRECISION == rhs.value; + } + + constexpr friend bool operator!=(const FP& lhs, const FP& rhs) { + return lhs.value != rhs.value; + } + + constexpr friend bool operator!=(const FP& lhs, const int32_t& rhs) { + return lhs.value != static_cast(rhs) << FPLUT::PRECISION; + } + + constexpr friend bool operator!=(const int32_t& lhs, const FP& rhs) { + return static_cast(lhs) << FPLUT::PRECISION != rhs.value; + } + + + private: + int64_t value; + + static constexpr FP parse_integer(const char* str, const char* end, bool* successful) { + int64_t parsed_value = StringUtils::string_to_int64(str, end, successful, 10); + return parse(parsed_value); + } + + static constexpr FP parse_fraction(const char* str, const char* end, bool* successful) { + constexpr size_t READ_SIZE = 5; + if (str + READ_SIZE < end) { + Logger::error("Fixed point fraction parse will only use the first ", READ_SIZE, + " characters of ", std::string_view { str, static_cast(end - str) }); + end = str + READ_SIZE; + } + int64_t parsed_value = StringUtils::string_to_int64(str, end, successful, 10); + return parse_raw(parsed_value * 65536 / 100000); + } + }; + + 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 new file mode 100644 index 0000000..f9b21ab --- /dev/null +++ b/src/openvic/types/fixed_point/FPLUT.hpp @@ -0,0 +1,33 @@ +#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 new file mode 100644 index 0000000..1af0380 --- /dev/null +++ b/src/openvic/types/fixed_point/FPLUT_sin_512.hpp @@ -0,0 +1,58 @@ +#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 new file mode 100644 index 0000000..965d63c --- /dev/null +++ b/src/openvic/types/fixed_point/FPMath.hpp @@ -0,0 +1,11 @@ +#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 new file mode 100644 index 0000000..5c5ba8b --- /dev/null +++ b/src/openvic/types/fixed_point/lut_generator/lut_generator.py @@ -0,0 +1,39 @@ +#!/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 index 9a90d68..6859411 100644 --- a/src/openvic/utility/BMP.cpp +++ b/src/openvic/utility/BMP.cpp @@ -3,7 +3,7 @@ #include #include -#include "Logger.hpp" +#include "openvic/utility/Logger.hpp" using namespace OpenVic; diff --git a/src/openvic/utility/BMP.hpp b/src/openvic/utility/BMP.hpp index c6f5815..50b333b 100644 --- a/src/openvic/utility/BMP.hpp +++ b/src/openvic/utility/BMP.hpp @@ -1,6 +1,10 @@ #pragma once -#include "../Types.hpp" +#include +#include + +#include "openvic/types/Colour.hpp" +#include "openvic/types/Return.hpp" namespace OpenVic { class BMP { diff --git a/src/openvic/utility/FloatUtils.hpp b/src/openvic/utility/FloatUtils.hpp new file mode 100644 index 0000000..4fc83fd --- /dev/null +++ b/src/openvic/utility/FloatUtils.hpp @@ -0,0 +1,11 @@ +#include + +namespace OpenVic::FloatUtils { + constexpr int round_to_int(double num) { + return (num > 0.0) ? (num + 0.5) : (num - 0.5); + } + + constexpr int64_t round_to_int64(double num) { + return (num > 0.0) ? (num + 0.5) : (num - 0.5); + } +} diff --git a/src/openvic/utility/StringUtils.hpp b/src/openvic/utility/StringUtils.hpp new file mode 100644 index 0000000..72f4038 --- /dev/null +++ b/src/openvic/utility/StringUtils.hpp @@ -0,0 +1,93 @@ +#include +#include + +namespace OpenVic::StringUtils { + /* The constexpr function 'string_to_int64' will convert a string into a int64 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 int64_t string_to_int64(char const* str, const char* end, bool* successful, 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. + int64_t result = 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; + } + + // 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 is_negative ? std::numeric_limits::min() : std::numeric_limits::max(); + } + + result *= base; + + // Check for overflow on addition + if (result > std::numeric_limits::max() - digit) { + return is_negative ? std::numeric_limits::min() : 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 the result. If the number was negative, the result is negated. + return is_negative ? -result : result; + } +} -- cgit v1.2.3-56-ga3b1