diff --git a/.editorconfig b/.editorconfig index 967d629..bd208c9 100644 --- a/.editorconfig +++ b/.editorconfig @@ -15,6 +15,18 @@ indent_size = 4 cpp_indent_access_specifiers = false end_of_line = lf +[*.py] +charset = utf-8 +indent_style = tab +indent_size = 4 +end_of_line = lf + +[*.json] +charset = utf-8 +indent_style = space +indent_size = 4 +end_of_line = lf + [*.yaml] charset = utf-8 indent_style = space diff --git a/.gitignore b/.gitignore index 5ed5df5..98591ad 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .vs build out +__pycache__ diff --git a/CMakeLists.txt b/CMakeLists.txt index f88acca..de7e872 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,8 @@ project(ProjectDelta LANGUAGES CXX) get_filename_component(DLT_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}" ABSOLUTE) get_filename_component(DLT_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}" ABSOLUTE) +include(cmake/DeltaCodegen.cmake) + set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED True) set(CMAKE_CXX_EXTENSIONS False) diff --git a/CodeGen/emitters/buffer.py b/CodeGen/emitters/buffer.py new file mode 100644 index 0000000..4c7155c --- /dev/null +++ b/CodeGen/emitters/buffer.py @@ -0,0 +1,30 @@ +class CodeBuffer: + def __init__(self, indent_string=" "): + self.lines = [] + self._indent_string = indent_string + self._current_indent = 0 + + def indent(self): + self._current_indent += 1 + + def dedent(self): + self._current_indent = max(0, self._current_indent - 1) + + def write_line(self, text=""): + if (text.strip() == ""): + self.lines.append("") + else: + spacing = self._indent_string * self._current_indent + self.lines.append(f"{spacing}{text}") + + def open_block(self, prefix_text=""): + if prefix_text: + self.write_line(prefix_text) + self.indent() + + def close_block(self, suffix_text="};"): + self.dedent() + self.write_line(suffix_text) + + def render(self) -> str: + return "\n".join(self.lines) \ No newline at end of file diff --git a/CodeGen/emitters/engine_emitters.py b/CodeGen/emitters/engine_emitters.py new file mode 100644 index 0000000..f4f22b4 --- /dev/null +++ b/CodeGen/emitters/engine_emitters.py @@ -0,0 +1,124 @@ +from .buffer import CodeBuffer + +class EngineEmitter: + def __init__(self, structures, primitives_registry, namespace=None): + self.structures = structures + self.primitives = primitives_registry + self.namespace = namespace + self.buf = CodeBuffer() + + def emit_all(self, output_filepath): + self.buf.write_line("// AUTOMATICALLY GENERATED FILE - DO NOT MODIFY") + self.buf.write_line("#pragma once") + self.buf.write_line("#include ") + self.buf.write_line() + + if self.namespace: + self.buf.open_block(f"namespace {self.namespace} {{") + self.buf.write_line() + + for struct in self.structures: + self._emit_aos(struct) + self.buf.write_line() + + for struct in self.structures: + self._emit_soa(struct) + self.buf.write_line() + + for struct in self.structures: + self._emit_view(struct) + self.buf.write_line() + + if self.namespace: + self.buf.close_block(f"}}; // namespace {self.namespace}") + + new_content = self.buf.render() + self._write_if_changed(output_filepath, new_content) + + def _get_type_name(self, field, target_track): + if target_track == "aos": + suffix = "AoS" + elif target_track == "soa": + suffix = "SoA" + else: + suffix = "View" + + return f"{field.type_name}{suffix}" + + def _get_field_type(self, field, target_track): + if field.is_primitive: + return self.primitives[field.type_name][target_track] + else: + if target_track == "aos": + suffix = "AoS" + elif target_track == "soa": + suffix = "SoA" + else: + suffix = "View" + + return f"{field.type_name}{suffix}" + + def _emit_aos(self, struct): + align_prefix = f"alignas({struct.alignment}) " if struct.alignment else "" + + self.buf.open_block(f"struct {align_prefix}{struct.name}AoS {{") + for field in struct.fields: + ftype = self._get_field_type(field, "aos") + self.buf.write_line(f"{ftype} {field.name};") + self.buf.close_block() + + def _emit_soa(self, struct): + align_prefix = f"alignas({struct.alignment}) " if struct.alignment else "" + + self.buf.open_block(f"struct {align_prefix}{struct.name}SoA {{") + for field in struct.fields: + ftype = self._get_field_type(field, "soa") + self.buf.write_line(f"{ftype} {field.name};") + self.buf.close_block() + + def _emit_view(self, struct): + self.buf.open_block(f"struct {struct.name}View {{") + + # Write references + for field in struct.fields: + ftype = self._get_field_type(field, "view") + self.buf.write_line(f"{ftype} {field.name};") + self.buf.write_line() + + # FromAoS factory initialization + self.buf.open_block(f"[[nodiscard]] static inline {struct.name}View FromAoS({struct.name}AoS& instance) noexcept {{") + self.buf.open_block(f"return {struct.name}View {{") + + for field in struct.fields: + if field.is_primitive: + self.buf.write_line(f".{field.name} = instance.{field.name},") + else: + self.buf.write_line(f".{field.name} = {field.type_name}View::FromAoS(instance.{field.name}),") + + self.buf.close_block("};") + self.buf.close_block("}") + + # FromSoA factory initialization + self.buf.open_block(f"[[nodiscard]] static inline {struct.name}View FromSoA({struct.name}SoA& instance, uint32_t ix) noexcept {{") + self.buf.open_block(f"return {struct.name}View {{") + + for field in struct.fields: + if field.is_primitive: + self.buf.write_line(f".{field.name} = instance.{field.name}[ix],") + else: + self.buf.write_line(f".{field.name} = {field.type_name}View::FromSoA(instance.{field.name}, ix),") + + self.buf.close_block("};") + self.buf.close_block("}") + + # end structure + self.buf.close_block() + + def _write_if_changed(self, filepath, content): + import os + if os.path.exists(filepath): + with open(filepath, "r") as f: + if f.read() == content: + return + with open(filepath, "w") as f: + f.write(content) diff --git a/CodeGen/main.py b/CodeGen/main.py new file mode 100644 index 0000000..9143fa6 --- /dev/null +++ b/CodeGen/main.py @@ -0,0 +1,43 @@ +import os +import sys +import json +from model import load_schemas_tree +from emitters.engine_emitters import EngineEmitter + +def main(): + if len(sys.argv) < 3: + print("Error: Missing output file path argument from CMake.", file=sys.stderr) + sys.exit(1) + + input_schema_path = sys.argv[1] + output_filepath = sys.argv[2] + + script_dir = os.path.dirname(os.path.abspath(__file__)) + core_types_path = os.path.join(script_dir, "schemas", "core_types.json") + output_directory, _ = os.path.split(output_filepath) + + print(output_directory) + os.makedirs(output_directory, exist_ok=True) + + if not os.path.exists(core_types_path): + print(f"Error: Missing core types registry at {core_types_path}", file=sys.stderr) + sys.exit(1) + + with open(core_types_path, "r") as f: + core_data = json.load(f) + + primitives_map = {} + for p in core_data.get("primitives", []) + core_data.get("opaque_engine_types", []): + primitives_map[p["name"]] = p + + if not os.path.exists(input_schema_path): + print(f"Error: Structural schema missing at {input_schema_path}", file=sys.stderr) + sys.exit(1) + + parsed_structures, target_namespace = load_schemas_tree([input_schema_path], primitives_map) + + emitter = EngineEmitter(parsed_structures, primitives_map, target_namespace) + emitter.emit_all(output_filepath) + +if __name__ == "__main__": + main() diff --git a/CodeGen/model.py b/CodeGen/model.py new file mode 100644 index 0000000..23cf405 --- /dev/null +++ b/CodeGen/model.py @@ -0,0 +1,40 @@ +import json + +class Field: + def __init__(self, type_name, name, is_primitive): + self.type_name = type_name + self.name = name + self.is_primitive = is_primitive + +class Structure: + def __init__(self, name, raw_fields, primitives_registry, custom_types, alignment = None): + self.name = name + self.fields = [] + self.alignment = int(alignment) if alignment is not None else None + + for f in raw_fields: + is_prim = f["type"] in primitives_registry + self.fields.append(Field(f["type"], f["name"], is_prim)) + +def load_schemas_tree(schema_files, primitives_registry): + custom_types = set() + raw_structs = [] + target_namespace = None + + for file_path in schema_files: + with open(file_path, "r") as f: + data = json.load(f) + + if "namespace" in data: + target_namespace = data["namespace"] + + for s in data["structures"]: + custom_types.add(s["name"]) + raw_structs.append(s) + + parsed_structures = [ + Structure(s["name"], s["fields"], primitives_registry, custom_types, s.get("alignment")) + for s in raw_structs + ] + + return parsed_structures, target_namespace diff --git a/CodeGen/schemas/core_types.json b/CodeGen/schemas/core_types.json new file mode 100644 index 0000000..7193a62 --- /dev/null +++ b/CodeGen/schemas/core_types.json @@ -0,0 +1,46 @@ +{ + "primitives": [ + { + "name": "uint8_t", + "alignment": 1, + "aos": "uint8_t", + "soa": "uint8_t*", + "view": "uint8_t&" + }, + { + "name": "uint16_t", + "alignment": 2, + "aos": "uint16_t", + "soa": "uint16_t*", + "view": "uint16_t&" + }, + { + "name": "uint32_t", + "alignment": 4, + "aos": "uint32_t", + "soa": "uint32_t*", + "view": "uint32_t&" + }, + { + "name": "uint64_t", + "alignment": 8, + "aos": "uint64_t", + "soa": "uint64_t*", + "view": "uint64_t&" + }, + { + "name": "size_t", + "alignment": 8, + "aos": "size_t", + "soa": "size_t*", + "view": "size_t&" + }, + { + "name": "uintptr_t", + "alignment": 8, + "aos": "uintptr_t", + "soa": "uintptr_t*", + "view": "uintptr_t&" + } + ] +} \ No newline at end of file diff --git a/Engine/CMakeLists.txt b/Engine/CMakeLists.txt index f4d6d90..5594dc1 100644 --- a/Engine/CMakeLists.txt +++ b/Engine/CMakeLists.txt @@ -12,35 +12,65 @@ # See the License for the specific language governing permissions and # limitations under the License. -add_library(ProjectDelta SHARED - "src/delta/platform/compiler.h" +add_subdirectory(codegen) + +set(DLT_CORE_SOURCES "src/delta/core/engine.cpp" "include/delta/core/engine.h" "include/delta/core/memory.h" - "include/delta/definitions.h" - "src/delta/platform/os_internal.h" - "src/delta/platform/os_win32.cpp" - "include/delta/platform/os.h" - "include/delta/pch.h" - "src/delta/pch.h" - "src/delta/pch.cpp" "src/delta/core/EngineTypes.h" - "src/delta/core/ThreadContext.h" - "src/delta/core/ThreadContext.cpp" "src/delta/core/MemoryConfig.h" "src/delta/core/MemoryConfig.cpp" - "src/delta/core/Worker.h" - "src/delta/core/Worker.cpp" + "include/delta/core/core_types.h" + "src/delta/core/ThreadContextManager.h" + "src/delta/core/ThreadContextManager.cpp" + + "include/delta/core/pch.h" + "src/delta/core/pch.h" + "src/delta/core/pch.cpp" +) + +set(DLT_PLATFORM_SOURCES + "include/delta/platform/compiler.h" + "src/delta/platform/os_internal.h" + "src/delta/platform/os_win32.cpp" + "include/delta/platform/os_types.h" + "include/delta/platform/os.h" + "src/delta/platform/os_internal_types.h" + + "include/delta/platform/pch.h" + "src/delta/platform/pch.h" + "src/delta/platform/pch.cpp" +) + +set(DLT_UTILS_SOURCES "include/delta/utils/StaticArray.h" + "include/delta/utils/CTMath.h" + "include/delta/utils/string.h" + "include/delta/utils/assert.h" + + "include/delta/utils/pch.h" + "src/delta/utils/pch.cpp" +) + +add_library(ProjectDelta SHARED + ${DLT_CORE_SOURCES} + ${DLT_PLATFORM_SOURCES} + ${DLT_UTILS_SOURCES} + "include/delta/definitions.h" ) target_compile_definitions(ProjectDelta PRIVATE DLT_EXPORT_SYMBOLS ) +if (CMAKE_BUILD_TYPE STREQUAL "Debug") + target_compile_definitions(ProjectDelta PRIVATE DLT_DEBUG) +endif() + target_precompile_headers(ProjectDelta - PUBLIC "include/delta/pch.h" - PRIVATE "src/delta/pch.h" + PUBLIC "include/delta/pch.h" "include/delta/core/pch.h" "include/delta/platform/pch.h" "include/delta/utils/pch.h" + PRIVATE "src/delta/pch.h" "src/delta/core/pch.h" "src/delta/platform/pch.h" ) target_include_directories(ProjectDelta @@ -48,6 +78,10 @@ target_include_directories(ProjectDelta PRIVATE "src" ) +target_link_libraries(ProjectDelta + PRIVATE DeltaEngine_Generated_Internal +) + if (MSVC) target_compile_options(ProjectDelta PRIVATE /GR-) else() diff --git a/Engine/codegen/CMakeLists.txt b/Engine/codegen/CMakeLists.txt new file mode 100644 index 0000000..3fdd0c1 --- /dev/null +++ b/Engine/codegen/CMakeLists.txt @@ -0,0 +1 @@ +delta_create_codegen_target(DeltaEngine_Generated_Internal ThreadContext.json) diff --git a/Engine/codegen/ThreadContext.json b/Engine/codegen/ThreadContext.json new file mode 100644 index 0000000..7cc0cf7 --- /dev/null +++ b/Engine/codegen/ThreadContext.json @@ -0,0 +1,85 @@ +{ + "namespace": "delta::core", + "structures": [ + { + "name": "PageCoordinator", + "alignemnt": 8, + "fields": [ + { + "type": "uintptr_t", + "name": "base" + }, + { + "type": "size_t", + "name": "size" + }, + { + "type": "size_t", + "name": "pageSize" + } + ] + }, + { + "name": "ArenaAllocator", + "alignment": 8, + "fields": [ + { + "type": "uintptr_t", + "name": "base" + }, + { + "type": "size_t", + "name": "offset" + }, + { + "type": "size_t", + "name": "reserved" + }, + { + "type": "size_t", + "name": "size" + } + ] + }, + { + "name": "MasterThreadContext", + "alignment": 64, + "fields": [ + { + "type": "uint32_t", + "name": "threadId" + }, + { + "type": "PageCoordinator", + "name": "pagecrd" + }, + { + "type": "ArenaAllocator", + "name": "transientStorage" + }, + { + "type": "ArenaAllocator", + "name": "persistentStorage" + } + ] + }, + { + "name": "WorkerThreadContext", + "alignment": 64, + "fields": [ + { + "type": "uint32_t", + "name": "threadId" + }, + { + "type": "PageCoordinator", + "name": "pagecrd" + }, + { + "type": "ArenaAllocator", + "name": "transientStorage" + } + ] + } + ] +} diff --git a/Engine/include/delta/core/core_types.h b/Engine/include/delta/core/core_types.h new file mode 100644 index 0000000..3d331a8 --- /dev/null +++ b/Engine/include/delta/core/core_types.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +namespace delta::core +{ + struct Context + { + delta::platform::WindowHandle window; + bool isRunning; + }; + + enum class AllocationType : uint8_t { TRANSIENT, PERSISTENT }; + + typedef void (*GameInitFunc)(Context*); + typedef void (*GameUpdateFunc)(Context*); + typedef void (*GameShutdownFunc)(Context*); +} diff --git a/Engine/include/delta/core/engine.h b/Engine/include/delta/core/engine.h index 40fc429..3fe4bef 100644 --- a/Engine/include/delta/core/engine.h +++ b/Engine/include/delta/core/engine.h @@ -15,18 +15,11 @@ */ #include +#include +#include -namespace delta::Engine +namespace delta::core { - struct Context - { - bool isRunning; - }; - - typedef void (*GameInitFunc)(Context*); - typedef void (*GameUpdateFunc)(Context*); - typedef void (*GameShutdownFunc)(Context*); - DLT_API void Initialize(Context& context); DLT_API void Update(Context& context); DLT_API void Shutdown(Context& context); diff --git a/Engine/include/delta/core/memory.h b/Engine/include/delta/core/memory.h index 0c1ba98..45ef8ac 100644 --- a/Engine/include/delta/core/memory.h +++ b/Engine/include/delta/core/memory.h @@ -17,21 +17,20 @@ #pragma once #include +#include -namespace delta::Engine +namespace delta::core { - enum class AllocationType : uint8_t { TRANSIENT, PERSISTENT }; - [[nodiscard]] DLT_API void* Allocate(size_t size, AllocationType type, size_t alignment = 8) noexcept; DLT_API void Free(void* ptr) noexcept; } -[[nodiscard]] inline void* operator new(size_t size) { return delta::Engine::Allocate(size, delta::Engine::AllocationType::PERSISTENT); } -[[nodiscard]] inline void* operator new(size_t size, delta::Engine::AllocationType type) { return delta::Engine::Allocate(size, type); } -[[nodiscard]] inline void* operator new[](size_t size) { return delta::Engine::Allocate(size, delta::Engine::AllocationType::PERSISTENT); } -[[nodiscard]] inline void* operator new[](size_t size, delta::Engine::AllocationType type) { return delta::Engine::Allocate(size, type); } +[[nodiscard]] inline void* operator new(size_t size) { return delta::core::Allocate(size, delta::core::AllocationType::PERSISTENT); } +[[nodiscard]] inline void* operator new(size_t size, delta::core::AllocationType type) { return delta::core::Allocate(size, type); } +[[nodiscard]] inline void* operator new[](size_t size) { return delta::core::Allocate(size, delta::core::AllocationType::PERSISTENT); } +[[nodiscard]] inline void* operator new[](size_t size, delta::core::AllocationType type) { return delta::core::Allocate(size, type); } -inline void operator delete(void* ptr) { return delta::Engine::Free(ptr); } -inline void operator delete(void* ptr, delta::Engine::AllocationType) { return delta::Engine::Free(ptr); } -inline void operator delete[](void* ptr) { return delta::Engine::Free(ptr); } -inline void operator delete[](void* ptr, delta::Engine::AllocationType) { return delta::Engine::Free(ptr); } +inline void operator delete(void* ptr) { return delta::core::Free(ptr); } +inline void operator delete(void* ptr, delta::core::AllocationType) { return delta::core::Free(ptr); } +inline void operator delete[](void* ptr) { return delta::core::Free(ptr); } +inline void operator delete[](void* ptr, delta::core::AllocationType) { return delta::core::Free(ptr); } diff --git a/Engine/src/delta/core/Worker.h b/Engine/include/delta/core/pch.h similarity index 82% rename from Engine/src/delta/core/Worker.h rename to Engine/include/delta/core/pch.h index 6ec2a8b..05fd1bc 100644 --- a/Engine/src/delta/core/Worker.h +++ b/Engine/include/delta/core/pch.h @@ -16,10 +16,6 @@ #pragma once -#include - -namespace delta::core -{ - void Worker_Init(uint32_t count); - void Worker_Shutdown(); -} +#include "core_types.h" +#include "engine.h" +#include "memory.h" diff --git a/Engine/src/delta/platform/compiler.h b/Engine/include/delta/platform/compiler.h similarity index 100% rename from Engine/src/delta/platform/compiler.h rename to Engine/include/delta/platform/compiler.h diff --git a/Engine/include/delta/platform/os.h b/Engine/include/delta/platform/os.h index 75e1525..7a29f01 100644 --- a/Engine/include/delta/platform/os.h +++ b/Engine/include/delta/platform/os.h @@ -15,34 +15,21 @@ */ #pragma once +#include namespace delta::platform { - struct OSInfo - { - const char* cpuArchitecture; - - uint32_t cpuPhysicalCoreCount; - uint32_t cpuLogicalProcessorCount; - uint32_t osPageSize; - - char cpuBrandString[sizeof(int) * 12 + 1]; - char cpuManufacturerId[13]; - - bool cpuHasSMT; - bool cpuHasAVX2; - bool cpuHasAVX512f; - bool cpuHasAVX512cd; - bool cpuHasAVX512er; - bool cpuHasAVX512pf; - }; - - struct MemoryStatus - { - uint64_t physicalInstalled; - uint64_t physicalFree; - }; - + // General + // TODO: Change names to the adequate ones DLT_API const OSInfo* getOSInfo() noexcept; DLT_API MemoryStatus getMemoryStatus(); + + // Window API + DLT_API void Window_SetTitle(WindowHandle window, const char* newTitle); + DLT_API void Window_Show(WindowHandle window); + DLT_API void Window_Hide(WindowHandle window); + DLT_API void Window_SetSize(WindowHandle window, uint32_t w, uint32_t h); + DLT_API void Window_SetPos(WindowHandle window, uint32_t x, uint32_t y); + DLT_API void Window_ShowCursor(WindowHandle window); + DLT_API void Window_HideCursor(WindowHandle window); } diff --git a/Engine/include/delta/platform/os_types.h b/Engine/include/delta/platform/os_types.h new file mode 100644 index 0000000..151f302 --- /dev/null +++ b/Engine/include/delta/platform/os_types.h @@ -0,0 +1,36 @@ +#pragma once + +#define DLT_DEFINE_HANDLE(name)\ + struct name;\ + using name##Handle = name*;\ + inline constexpr name##Handle INVALID_##name##_HANDLE = nullptr + +namespace delta::platform +{ + DLT_DEFINE_HANDLE(Window); + + struct OSInfo + { + const char* cpuArchitecture; + + uint32_t cpuPhysicalCoreCount; + uint32_t cpuLogicalProcessorCount; + uint32_t osPageSize; + + char cpuBrandString[sizeof(int) * 12 + 1]; + char cpuManufacturerId[13]; + + bool cpuHasSMT; + bool cpuHasAVX2; + bool cpuHasAVX512f; + bool cpuHasAVX512cd; + bool cpuHasAVX512er; + bool cpuHasAVX512pf; + }; + + struct MemoryStatus + { + uint64_t physicalInstalled; + uint64_t physicalFree; + }; +} diff --git a/Engine/include/delta/platform/pch.h b/Engine/include/delta/platform/pch.h new file mode 100644 index 0000000..ba3d202 --- /dev/null +++ b/Engine/include/delta/platform/pch.h @@ -0,0 +1,21 @@ +/* + * Copyright 2026 Jakub Bączyk + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "compiler.h" +#include "os.h" +#include "os_types.h" diff --git a/Engine/include/delta/utils/CTMath.h b/Engine/include/delta/utils/CTMath.h new file mode 100644 index 0000000..2e1e228 --- /dev/null +++ b/Engine/include/delta/utils/CTMath.h @@ -0,0 +1,57 @@ +/* + * Copyright 2026 Jakub Bączyk + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +namespace delta::utils +{ + template + inline constexpr bool is_power_of_two(T value) noexcept + { + return (value & (value - 1)) == 0; + } + + template + inline constexpr T align_up_pow2(T value, T alignment) noexcept + { + DLT_ASSERT(is_power_of_two(alignment)); + const T mask = alignment - 1; + return (value + mask) & ~mask; + } + + template + inline constexpr T align_down_pow2(T value, T alignment) noexcept + { + DLT_ASSERT(is_power_of_two(alignment)); + return value & ~(alignment - 1); + } + + template + inline constexpr T align_up(T value, T alignment) noexcept + { + const T mask = alignment - 1; + return ((value + mask) / alignment) * alignment; + } + + template + inline constexpr T align_down(T value, T alignment) noexcept + { + return (value / alignment) * alignment; + } +} diff --git a/Engine/include/delta/utils/String.h b/Engine/include/delta/utils/String.h new file mode 100644 index 0000000..059e614 --- /dev/null +++ b/Engine/include/delta/utils/String.h @@ -0,0 +1,37 @@ +/* + * Copyright 2026 Jakub Bączyk + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +namespace delta::utils +{ + template + [[nodiscard]] constexpr inline T pack_string(std::string_view str) noexcept + { + DLT_ASSERT(str.size() <= sizeof(T)); + + T result{}; + const size_t bytesToCopy = std::min(str.size(), sizeof(T)); + + for (size_t i = 0; i < bytesToCopy; i++) + result |= (static_cast(static_cast(str[i])) << (i * 8)); + + return result; + } +} diff --git a/Engine/include/delta/utils/assert.h b/Engine/include/delta/utils/assert.h new file mode 100644 index 0000000..d17abda --- /dev/null +++ b/Engine/include/delta/utils/assert.h @@ -0,0 +1,36 @@ +#include +#include +#include +#include + +namespace delta::utils +{ + inline int compile_time_fail_trigger() noexcept { return 0; } + + inline void runtime_assert_failed( + const char* conditionStr, + std::source_location loc) noexcept + { + #ifdef DLT_DEBUG + std::cerr << "ASSERTION FAILED: " << conditionStr << "\n" + << "File: " << loc.file_name() << ":" << loc.line() << "\n" + << "Function: " << loc.function_name() << "\n"; + #endif + } +} + +#define DLT_ASSERT(expr) \ + do \ + { \ + if (std::is_constant_evaluated()) \ + { \ + if (!(expr)) \ + { \ + [[maybe_unused]] int _ct_trap = delta::utils::compile_time_fail_trigger(); \ + } \ + } \ + else if (!(expr)) [[unlikely]]\ + { \ + delta::utils::runtime_assert_failed(#expr, std::source_location::current()); \ + } \ + } while(0) diff --git a/Engine/include/delta/utils/pch.h b/Engine/include/delta/utils/pch.h new file mode 100644 index 0000000..4159e34 --- /dev/null +++ b/Engine/include/delta/utils/pch.h @@ -0,0 +1,22 @@ +/* + * Copyright 2026 Jakub Bączyk + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "assert.h" +#include "CTMath.h" +#include "StaticArray.h" +#include "string.h" diff --git a/Engine/src/delta/core/EngineTypes.h b/Engine/src/delta/core/EngineTypes.h index 98db880..3baa5f8 100644 --- a/Engine/src/delta/core/EngineTypes.h +++ b/Engine/src/delta/core/EngineTypes.h @@ -17,95 +17,9 @@ #pragma once #include +#include namespace delta::core { - // TODO: revise this structure and its purpose - struct ThreadPageCoordinator - { - uint8_t* virtualAddressBase; - size_t commitedOffset; - size_t reservedCapacity; - size_t pageSize; - }; - - struct ThreadArena - { - uint8_t* backingMemory; - size_t capacity; - size_t offset; - size_t maxCapacity; - }; - - struct DependencyCounter - { - std::atomic count; - }; - - using task_t = void (*)(void*); - using payload_t = void*; - using queue_index_t = int64_t; - struct TaskQueue // SoA structure, Chase-Lev queue - { - alignas(64) std::atomic top; - alignas(64) std::atomic bottom; - - alignas(64) queue_index_t size; - queue_index_t mask; - - task_t* tasks; - payload_t* payloads; - DependencyCounter* depCounterPtr; - - static inline constexpr size_t FIELD_SIZE = sizeof(task_t) + sizeof(payload_t); - }; - - enum class ThreadType : uint8_t { MAIN, WORKER }; - - struct alignas(64) GenericExecutionContext - { - // SHARED TRAITS - ThreadType type; - uint32_t threadIx; - delta::platform::ThreadHandle threadHandle; - - ThreadPageCoordinator pageCoordinator; - ThreadArena transientArena; - delta::platform::Timer perThreadTimer; - }; - - struct alignas(64) MainExecutionContext - { - // SHARED TRAITS - GenericExecutionContext generic; - - // ROLE TRAITS - ThreadArena persistentStorage; - DependencyCounter depCounter; - }; - - struct alignas(64) WorkerExecutionContext - { - GenericExecutionContext generic; - - // ROLE TRAITS - TaskQueue taskQueue; - delta::platform::SemaphoreHandle sleepSemaphore; - std::atomic isAsleep; - std::atomic shouldClose; - }; - - template - concept ExecutionContext = - std::is_same_v, GenericExecutionContext> || - std::is_same_v, MainExecutionContext> || - std::is_same_v, WorkerExecutionContext>; - - // COMPILE TIME CONSTANTS - inline constexpr size_t THREAD_EXECUTION_CONTEXT_STRIDE = std::max(sizeof(MainExecutionContext), sizeof(WorkerExecutionContext)); - inline constexpr size_t THREAD_EXECUTION_CONTEXT_SIZE = (THREAD_EXECUTION_CONTEXT_STRIDE + 63) & ~(63); - - // EXTERN VARIABLES & FUNCTIONS - extern uintptr_t g_MasterSlabStart; - extern uintptr_t g_MasterSlabEnd; + inline constexpr size_t THREADCTX_ALIGNMENT = 64ull; } diff --git a/Engine/src/delta/core/MemoryConfig.cpp b/Engine/src/delta/core/MemoryConfig.cpp index a985ecc..1251a7e 100644 --- a/Engine/src/delta/core/MemoryConfig.cpp +++ b/Engine/src/delta/core/MemoryConfig.cpp @@ -17,6 +17,8 @@ #include "MemoryConfig.h" #include "EngineTypes.h" #include + +#include #include namespace delta::core @@ -28,11 +30,11 @@ namespace delta::core g_MemoryConfig.totalPhysicalRam = physicalRamInstalled; g_MemoryConfig.maxAllowedPhysical = (physicalRamInstalled / 10) * 7; // 70% of total capacity - size_t baseline = MemoryMap::VIRT_MAIN_BASELINE + (static_cast(totalThreads) - 1) * MemoryMap::VIRT_WORKER_BASELINE; - g_MemoryConfig.activeLockAllocation = (baseline + pageSize - 1) & ~(pageSize - 1); + size_t baseline = MemoryMap::VIRT_MASTER_BASELINE + (static_cast(totalThreads) - 1) * MemoryMap::VIRT_WORKER_BASELINE; + g_MemoryConfig.activeLockAllocation = delta::utils::align_up_pow2(baseline, pageSize); g_MemoryConfig.globalPoolSize = g_MemoryConfig.maxAllowedPhysical - g_MemoryConfig.activeLockAllocation; - assert(g_MemoryConfig.activeLockAllocation <= g_MemoryConfig.globalPoolSize); + DLT_ASSERT(g_MemoryConfig.activeLockAllocation <= g_MemoryConfig.globalPoolSize); bool result = delta::platform::Memory_ElevateLockLimit(g_MemoryConfig.activeLockAllocation); if (!result) diff --git a/Engine/src/delta/core/MemoryConfig.h b/Engine/src/delta/core/MemoryConfig.h index 87ad05b..c3f0b03 100644 --- a/Engine/src/delta/core/MemoryConfig.h +++ b/Engine/src/delta/core/MemoryConfig.h @@ -41,45 +41,33 @@ namespace delta::core size_t baseline; }; - // Generic inline constexpr size_t VIRT_ZONE_SPACE_LENGTH = (1ull << 35); - inline constexpr PoolDescriptor generic_transientArena(0ull, (1ull << 30), (1ull << 26)); - inline constexpr PoolDescriptor generic_eventBuffer(generic_transientArena, (1ull << 27), (1ull << 17)); - // Index Registry - inline constexpr uint32_t VIRT_ZONE_GENERIC_TA_INDEX = 0u; - inline constexpr uint32_t VIRT_ZONE_GENERIC_EB_INDEX = 1u; + inline constexpr uint32_t VIRT_ZONE_GENERIC_TS_INDEX = 0u; - // -- MAIN -- - inline constexpr uint32_t VIRT_ZONE_MAIN_PS_INDEX = 2u; + // -- MASTER -- + inline constexpr uint32_t VIRT_ZONE_MASTER_TS_INDEX = 0u; + inline constexpr uint32_t VIRT_ZONE_MASTER_PS_INDEX = 1u; // -- WORKER -- - inline constexpr uint32_t VIRT_ZONE_WORKER_TQ_INDEX = 2u; - inline constexpr uint32_t VIRT_ZONE_WORKER_CP_INDEX = 3u; - inline constexpr uint32_t VIRT_ZONE_WORKER_IO_INDEX = 4u; + inline constexpr uint32_t VIRT_ZONE_WORKER_TS_INDEX = 0u; - inline constexpr auto VIRT_MAP_MAIN = []() consteval { - auto permaStorage = PoolDescriptor(generic_eventBuffer, (1ull << 31), (1ull << 26)); + inline constexpr auto VIRT_MAP_MASTER = []() consteval { + auto transientStorage = PoolDescriptor(0ull, (1ull << 30), (1ull << 26)); + auto permaStorage = PoolDescriptor(transientStorage, (1ull << 31), (1ull << 27)); // 2gb max size, 128mb baseline return delta::utils::StaticArray{ - generic_transientArena, - generic_eventBuffer, + transientStorage, permaStorage }; }(); inline constexpr auto VIRT_MAP_WORKER = []() consteval { - auto taskQueue = PoolDescriptor(generic_eventBuffer, (1ull << 16)); - auto componentPool = PoolDescriptor(taskQueue, (1ull << 33), (1ull << 27)); - auto io = PoolDescriptor(componentPool, (1ull << 30)); + auto transientStorage = PoolDescriptor(0ull, (1ull << 30), (1ull << 26)); return delta::utils::StaticArray{ - generic_transientArena, - generic_eventBuffer, - taskQueue, - componentPool, - io + transientStorage }; }(); @@ -95,7 +83,7 @@ namespace delta::core return sum; } - inline constexpr auto VIRT_MAIN_BASELINE = total_baseline(VIRT_MAP_MAIN); + inline constexpr auto VIRT_MASTER_BASELINE = total_baseline(VIRT_MAP_MASTER); inline constexpr auto VIRT_WORKER_BASELINE = total_baseline(VIRT_MAP_WORKER); } diff --git a/Engine/src/delta/core/ThreadContext.cpp b/Engine/src/delta/core/ThreadContext.cpp deleted file mode 100644 index 159896a..0000000 --- a/Engine/src/delta/core/ThreadContext.cpp +++ /dev/null @@ -1,359 +0,0 @@ -/* - * Copyright 2026 Jakub Bączyk - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include "ThreadContext.h" -#include "MemoryConfig.h" - -#define ALIGN(size, alignment) ((size + (alignment - 1)) & ~(alignment - 1)) - -namespace delta::core -{ - using PoolDescriptor = MemoryMap::PoolDescriptor; - - uintptr_t g_MasterSlabStart = 0; - uintptr_t g_MasterSlabEnd = 0; - - static uint32_t g_ThreadCount = 0; - static uint32_t g_WorkerCount = 0; - static GenericExecutionContext* g_ThreadContexts = nullptr; - static thread_local GenericExecutionContext* tl_CurrentThreadContext = nullptr; - - DLT_FORCE_INLINE static void InitializePageCoordinator(ThreadPageCoordinator& pageCoord, size_t pageSize, uint8_t* baseAddress) - { - pageCoord.pageSize = pageSize; - pageCoord.virtualAddressBase = baseAddress; - pageCoord.commitedOffset = 0; - pageCoord.reservedCapacity = MemoryMap::VIRT_ZONE_SPACE_LENGTH; - } - - DLT_FORCE_INLINE static void InitializeQueue( - const ThreadPageCoordinator& pageCoord, - TaskQueue& queue, - DependencyCounter* depCounter, - const PoolDescriptor& pd) - { - queue.size = pd.size / TaskQueue::FIELD_SIZE; - queue.mask = queue.size - 1; - queue.top.store(0, std::memory_order_relaxed); - queue.bottom.store(0, std::memory_order_relaxed); - - // a queue is commited in whole - uint8_t* pTarget = pageCoord.virtualAddressBase + pd.offset; - void* p = delta::platform::Memory_Commit(pTarget, pd.size); - if (!delta::platform::Memory_Lock(pTarget, pd.size)) - std::cout << "[DeltaEngine-Warning] Failed to lock task queue memory. You may experience stuttering.\n"; - assert(p != nullptr); - - uint8_t* tasksArrayPtr = pTarget; - uint8_t* payloadsArrayPtr = pTarget + queue.size; - queue.tasks = reinterpret_cast(tasksArrayPtr); - queue.payloads = reinterpret_cast(payloadsArrayPtr); - } - - DLT_FORCE_INLINE static void InitializeArena( - const ThreadPageCoordinator& pageCoord, - ThreadArena& arena, - const PoolDescriptor& pd) - { - uint8_t* pTarget = pageCoord.virtualAddressBase + pd.offset; - void* res = delta::platform::Memory_Commit(pTarget, pd.baseline); - assert(res != nullptr); - - if (!delta::platform::Memory_Lock(pTarget, pd.baseline)) - std::cout << "[DeltaEngine-Warning] Failed to lock arena memory. You may experience stuttering.\n"; - - arena.backingMemory = pTarget; - arena.capacity = pd.baseline; - arena.offset = 0; - arena.maxCapacity = pd.size; - } - - template - DLT_FORCE_INLINE ContextType& GetExecutionContext(size_t i) - { - return reinterpret_cast(*(reinterpret_cast(g_ThreadContexts) + i * THREAD_EXECUTION_CONTEXT_SIZE)); - } - - void ThreadContext_Initialize(uint32_t threadCount, size_t pageSize) - { - g_ThreadCount = threadCount; - g_WorkerCount = threadCount - 1; - - // assign 32Gb of address space per thread - // master pool size is rounded up to page size - constexpr size_t ADDR_SLICE_PER_THREAD = (1ull << 35); - size_t contextArraySize = THREAD_EXECUTION_CONTEXT_SIZE * threadCount; - size_t alignedContextArraySize = ALIGN(contextArraySize, pageSize); - size_t masterPoolSize = (ADDR_SLICE_PER_THREAD * threadCount) + alignedContextArraySize; - - uint8_t* masterPoolBase = reinterpret_cast( - delta::platform::Memory_Reserve(masterPoolSize) - ); - assert(masterPoolBase != nullptr && "Failed to reserve master pool"); - - g_MasterSlabStart = reinterpret_cast(masterPoolBase); - g_MasterSlabEnd = g_MasterSlabStart + masterPoolSize; - - g_ThreadContexts = reinterpret_cast( - delta::platform::Memory_Commit(masterPoolBase, alignedContextArraySize) - ); - assert(g_ThreadContexts != nullptr && "Failed to commit thread context array"); - - if (!delta::platform::Memory_Lock(masterPoolBase, alignedContextArraySize)) - std::cout << "[DeltaEngine-Warning] Failed to lock memory resource: Master Thread Context Pool\n"; - - // pre-initialize generics - uint8_t* runwayCursor = masterPoolBase + alignedContextArraySize; - for (uint32_t i = 0; i < threadCount; i++) - { - GenericExecutionContext& ctx = GetExecutionContext(i); - delta::platform::Timer_Initialize(&ctx.perThreadTimer); - InitializePageCoordinator(ctx.pageCoordinator, pageSize, runwayCursor); - InitializeArena( - ctx.pageCoordinator, - ctx.transientArena, - MemoryMap::VIRT_MAP_MAIN[MemoryMap::VIRT_ZONE_GENERIC_TA_INDEX] - ); - - runwayCursor += MemoryMap::VIRT_ZONE_SPACE_LENGTH; - } - - // Finish initializing main thread context - DependencyCounter* depCounterPtr = nullptr; - { - MainExecutionContext& ctx = GetExecutionContext(0); - ctx.generic.type = ThreadType::MAIN; - ctx.generic.threadIx = 0; - ctx.generic.threadHandle = delta::platform::Thread_GetCurrentHandle(); - ctx.depCounter.count.store(0, std::memory_order_relaxed); - depCounterPtr = &ctx.depCounter; - - InitializeArena( - ctx.generic.pageCoordinator, - ctx.persistentStorage, - MemoryMap::VIRT_MAP_MAIN[MemoryMap::VIRT_ZONE_MAIN_PS_INDEX] - ); - } - - for (uint32_t i = 1; i < threadCount; i++) - { - WorkerExecutionContext& ctx = GetExecutionContext(i); - ctx.generic.type = ThreadType::WORKER; - ctx.generic.threadIx = i; - ctx.generic.threadHandle = delta::platform::INVALID_THREAD_HANDLE; // Initialized when thread starts - ctx.isAsleep.store(false, std::memory_order_relaxed); - ctx.shouldClose.store(false, std::memory_order_relaxed); - ctx.sleepSemaphore = delta::platform::Sync_CreateSemaphore(); - - InitializeQueue( - ctx.generic.pageCoordinator, - ctx.taskQueue, - depCounterPtr, - MemoryMap::VIRT_MAP_WORKER[MemoryMap::VIRT_ZONE_WORKER_TQ_INDEX] - ); - } - - tl_CurrentThreadContext = &g_ThreadContexts[0]; - } - - void ThreadContext_Shutdown() - { - delta::platform::Memory_Release(g_ThreadContexts); - } - - GenericExecutionContext* ThreadContext_GetCurrent() noexcept - { - return tl_CurrentThreadContext; - } - - GenericExecutionContext* ThreadContext_GetForIndex(uint32_t i) noexcept - { - return &GetExecutionContext(i); - } - - void ThreadContext_SetCurrent(GenericExecutionContext* ctx) noexcept - { - tl_CurrentThreadContext = ctx; - } - - void TaskQueue_Push(TaskQueue* queue, task_t task, payload_t payload) - { - queue_index_t b = queue->bottom.load(std::memory_order_relaxed); - queue_index_t t = queue->top.load(std::memory_order_acquire); - - if (b - t > queue->size) - { - // execute immadietely if the queue is full - task(payload); - return; - } - - queue_index_t ix = b & queue->mask; - queue->tasks[ix] = task; - queue->payloads[ix] = payload; - - std::atomic_thread_fence(std::memory_order_release); - queue->bottom.store(b + 1, std::memory_order_relaxed); - } - - bool TaskQueue_Pop(TaskQueue* queue, task_t* outTask, payload_t* outPayload) - { - queue_index_t b = queue->bottom.load(std::memory_order_relaxed) - 1; - queue->bottom.store(b, std::memory_order_relaxed); - - std::atomic_thread_fence(std::memory_order_seq_cst); - queue_index_t t = queue->top.load(std::memory_order_relaxed); - - if (t > b) - { - queue->bottom.store(b + 1, std::memory_order_relaxed); - return false; - } - - queue_index_t ix = b & queue->mask; - *outTask = queue->tasks[ix]; - *outPayload = queue->payloads[ix]; - - if (t == b) - { - queue_index_t expectedTop = t; - if (!queue->top.compare_exchange_strong(expectedTop, expectedTop + 1, std::memory_order_seq_cst, std::memory_order_relaxed)) - { - queue->bottom.store(b + 1, std::memory_order_relaxed); - return false; - } - - queue->bottom.store(b + 1, std::memory_order_relaxed); - } - - return true; - } - - bool TaskQueue_Steal(TaskQueue* queue, task_t* outTask, payload_t* outPayload) - { - queue_index_t t = queue->top.load(std::memory_order_acquire); - - std::atomic_thread_fence(std::memory_order_seq_cst); - queue_index_t b = queue->bottom.load(std::memory_order_acquire); - - if (t >= b) - return false; - - queue_index_t ix = t & queue->mask; - *outTask = queue->tasks[ix]; - *outPayload = queue->payloads[ix]; - - if (!queue->top.compare_exchange_strong(t, t + 1, std::memory_order_seq_cst, std::memory_order_relaxed)) - return false; - - return true; - } - - void Scheduler_ProcessTaskBatch(task_t* tasks, payload_t* payloads, size_t length) - { - assert(IsMainThread()); // CAN BE EXECUTED ONLY ON MAIN THREAD! - GetMainContext().depCounter.count.store(length, std::memory_order_relaxed); - - for (size_t i = 0; i < length; i++) - { - size_t targetIx = 1 + (i % g_WorkerCount); - WorkerExecutionContext& ctx = GetExecutionContext(targetIx); - TaskQueue_Push(&ctx.taskQueue, tasks[i], payloads[i]); - - if (ctx.isAsleep.load(std::memory_order_acquire)) - delta::platform::Sync_SignalSemaphore(ctx.sleepSemaphore); - } - } - - void Scheduler_Sync() - { - assert(tl_CurrentThreadContext->threadIx == 0); // CAN BE EXECUTED ONLY ON MAIN THREAD! - - uint32_t workerIx = 1; - uint32_t consecutiveEmptyQueues = 0; - - while (consecutiveEmptyQueues < g_ThreadCount) - { - WorkerExecutionContext& ctx = GetExecutionContext(workerIx); - task_t task = nullptr; - payload_t payload = nullptr; - - if (TaskQueue_Steal(&ctx.taskQueue, &task, &payload)) - { - consecutiveEmptyQueues = 0; - assert(task && payload); - - size_t snapshot = tl_CurrentThreadContext->transientArena.offset; - task(payload); - tl_CurrentThreadContext->transientArena.offset = snapshot; - } - else - { - consecutiveEmptyQueues++; - } - - workerIx = 1 + (workerIx % g_WorkerCount); - } - } - - ThreadArena* GetTransientArena() noexcept - { - auto* ctx = tl_CurrentThreadContext; - assert(ctx); - return &ctx->transientArena; - } - - void* ThreadArena_Allocate(ThreadArena* arena, size_t size, size_t alignment) - { - uintptr_t currentAddress = reinterpret_cast(arena->backingMemory) + arena->offset; - uintptr_t alignedAddress = ALIGN(currentAddress, alignment); - size_t padding = alignedAddress - currentAddress; - size_t totalSpace = padding + size; - - if (arena->offset + totalSpace > arena->maxCapacity) - { - // TODO: Handle it better. I don't know how yet, but I will know soon. - assert(false); // arena overflown - } - - if (totalSpace > arena->capacity) - { - // the slow path: assign more pages - // assigning twice 2 up front to avoid further slower-path-taking - size_t bytesNeeded = totalSpace - arena->capacity; - size_t spaceInPages = ALIGN(bytesNeeded, tl_CurrentThreadContext->pageCoordinator.pageSize) * 2; - uint8_t* targetAddress = arena->backingMemory + arena->capacity; - void* p = delta::platform::Memory_Commit(targetAddress, spaceInPages); - if (p && delta::platform::Memory_Lock(p, spaceInPages)) - { - return nullptr; - } - - arena->capacity += spaceInPages; - } - - uint8_t* ptr = reinterpret_cast(alignedAddress); - arena->offset += totalSpace; - - return ptr; - } - - void ThreadArena_Reset(ThreadArena* arena) - { - arena->offset = 0; - } -} diff --git a/Engine/src/delta/core/ThreadContext.h b/Engine/src/delta/core/ThreadContext.h deleted file mode 100644 index a4aa6a9..0000000 --- a/Engine/src/delta/core/ThreadContext.h +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2026 Jakub Bączyk - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include "EngineTypes.h" - -namespace delta::core -{ - // Lifecycle - void ThreadContext_Initialize(uint32_t workerCount, size_t pageSize); - void ThreadContext_Shutdown(); - - // Getters - GenericExecutionContext* ThreadContext_GetCurrent() noexcept; - GenericExecutionContext* ThreadContext_GetForIndex(uint32_t ix) noexcept; - void ThreadContext_SetCurrent(GenericExecutionContext* ctx) noexcept; - - // Thread Task Queue API - void TaskQueue_Push(TaskQueue* queue, task_t task, payload_t payload); - bool TaskQueue_Pop(TaskQueue* queue, task_t* outTask, payload_t* outPayload); - bool TaskQueue_Steal(TaskQueue* queue, task_t* outTask, payload_t* outPayload); - - // Scheduler API - void Scheduler_ProcessTaskBatch(task_t* tasks, payload_t* payloads, size_t length); - void Scheduler_Sync(); - - // Engine Arena API - ThreadArena* GetTransientArena() noexcept; - void* ThreadArena_Allocate(ThreadArena* arena, size_t size, size_t alignment = 8); - void ThreadArena_Reset(ThreadArena* arena); - void ThreadArena_Reset(ThreadArena* arena); - - // Inline Helpers - template - [[deprecated]] [[nodiscard]] inline TargetType* ThreadContextCast(GenericExecutionContext* ctx) - { - if (!ctx) return nullptr; - - if constexpr (std::is_same_v) - { - if (ctx->type == ThreadType::MAIN) - return reinterpret_cast(ctx); - } - else if constexpr (std::is_same_v) - { - if (ctx->type == ThreadType::WORKER) - return reinterpret_cast(ctx); - } - - assert(false); // CRITICAL: Casting to invalid type - return nullptr; - } - - inline bool IsCustomAllocated(void* ptr) - { - // range scan - return g_MasterSlabStart <= reinterpret_cast(ptr) && reinterpret_cast(ptr) <= g_MasterSlabEnd; - } - - inline bool IsMainThread() - { - auto* ctx = ThreadContext_GetCurrent(); - return ctx && ctx->type == ThreadType::MAIN; - } - - inline bool IsWorkerThread() - { - auto* ctx = ThreadContext_GetCurrent(); - return ctx && ctx->type == ThreadType::WORKER; - } - - DLT_DISABLE_WARNING_PUSH - DLT_DISABLE_MISSING_RETURN - inline MainExecutionContext& GetMainContext() - { - auto* ctx = ThreadContext_GetCurrent(); - if (ctx->type == ThreadType::MAIN) - return reinterpret_cast(*ctx); - - assert(false); // this shouldn't ever happen - DLT_UNREACHABLE; - } - - inline WorkerExecutionContext& GetWorkerContext() - { - auto* ctx = ThreadContext_GetCurrent(); - if (ctx->type == ThreadType::WORKER) - return reinterpret_cast(*ctx); - - assert(false); - DLT_UNREACHABLE; - } - DLT_DISABLE_WARNING_POP -} diff --git a/Engine/src/delta/core/ThreadContextManager.cpp b/Engine/src/delta/core/ThreadContextManager.cpp new file mode 100644 index 0000000..3035155 --- /dev/null +++ b/Engine/src/delta/core/ThreadContextManager.cpp @@ -0,0 +1,261 @@ +#include "ThreadContextManager.h" +#include "MemoryConfig.h" + +#include +#include + +namespace delta::core +{ + static constinit uintptr_t g_AddressSpaceStart = 0ll; + static constinit uintptr_t g_AddressSpaceEnd = 0ll; + static MasterThreadContextAoS g_MasterThreadCtx; + static WorkerThreadContextSoA g_WorkerThreadCtx; + + static thread_local constinit tca_index_t tl_arrayIndex = TCA_INDEX_INVALID; + + static DLT_FORCE_INLINE bool InitializeContextArray(tca_cursor_t& cursor, size_t ctxArraySize, uint32_t workers) + { + void* p = delta::platform::Memory_Commit(cursor, ctxArraySize); + if (!p || !delta::platform::Memory_Lock(cursor, ctxArraySize)) + return false; + + // set array pointers + g_WorkerThreadCtx.threadId = cursor; + cursor += workers * sizeof(WorkerThreadContextAoS::threadId); + + g_WorkerThreadCtx.pagecrd.base = cursor; + cursor += workers * sizeof(WorkerThreadContextAoS::pagecrd.base); + + g_WorkerThreadCtx.pagecrd.size = cursor; + cursor += workers * sizeof(WorkerThreadContextAoS::pagecrd.size); + + g_WorkerThreadCtx.transientStorage.base = cursor; + cursor += workers * sizeof(WorkerThreadContextAoS::transientStorage.base); + + g_WorkerThreadCtx.transientStorage.offset = cursor; + cursor += workers * sizeof(WorkerThreadContextAoS::transientStorage.offset); + + g_WorkerThreadCtx.transientStorage.reserved = cursor; + cursor += workers * sizeof(WorkerThreadContextAoS::transientStorage.reserved); + + g_WorkerThreadCtx.transientStorage.size = cursor; + cursor += workers * sizeof(WorkerThreadContextAoS::transientStorage.size); + + #ifdef DLT_DEBUG + size_t total = cursor.ptr - reinterpret_cast(g_WorkerThreadCtx.threadId); + DLT_ASSERT(total <= ctxArraySize); + #endif + + return true; + } + + void ThreadContextManager_Initialize(uint32_t totalThreads, uint32_t pageSize) + { + tl_arrayIndex = TCA_INDEX_MASTER; + + const uint32_t workers = totalThreads - 1; + const size_t addrSpaceSize = delta::utils::align_up(totalThreads * MemoryMap::VIRT_ZONE_SPACE_LENGTH, pageSize); + const size_t ctxArraySize = delta::utils::align_up(workers * sizeof(WorkerThreadContextAoS), pageSize); + const size_t reservationSize = ctxArraySize + addrSpaceSize; + + g_AddressSpaceStart = reinterpret_cast(delta::platform::Memory_Reserve(reservationSize)); + g_AddressSpaceEnd = g_AddressSpaceStart + reservationSize; + const uintptr_t ctxArray = g_AddressSpaceStart; + const uintptr_t addrSpace = ctxArray + ctxArraySize; + + { + tca_cursor_t cursor(ctxArray); + if (!InitializeContextArray(cursor, ctxArraySize, workers)) + { + std::cout << "[DeltaEngine-Critical] Failed to setup thread context array\n"; + std::abort(); + } + + DLT_ASSERT(cursor.ptr - ctxArray <= ctxArraySize); + } + + // initialize contexts (starting from master) + uintptr_t cursor = addrSpace; + + constexpr auto MASTER_TS = MemoryMap::VIRT_MAP_MASTER[MemoryMap::VIRT_ZONE_GENERIC_TS_INDEX]; + constexpr auto MASTER_PS = MemoryMap::VIRT_MAP_MASTER[MemoryMap::VIRT_ZONE_MASTER_PS_INDEX]; + g_MasterThreadCtx.threadId = delta::platform::Thread_GetCurrentId(); + g_MasterThreadCtx.pagecrd.base = cursor; + g_MasterThreadCtx.pagecrd.size = MemoryMap::VIRT_ZONE_SPACE_LENGTH; + + g_MasterThreadCtx.transientStorage.base = cursor + MASTER_TS.offset; + g_MasterThreadCtx.transientStorage.offset = 0ull; + g_MasterThreadCtx.transientStorage.reserved = MASTER_TS.baseline; + g_MasterThreadCtx.transientStorage.size = MASTER_TS.size; + + void* p = reinterpret_cast(g_MasterThreadCtx.transientStorage.base); + p = delta::platform::Memory_Commit(p, g_MasterThreadCtx.transientStorage.reserved); + if (!p || !delta::platform::Memory_Lock(p, g_MasterThreadCtx.transientStorage.reserved)) + { + std::cout << "Failed to reserve memory for master thread's transient storage\n"; + std::abort(); + } + + g_MasterThreadCtx.persistentStorage.base = cursor + MASTER_PS.offset; + g_MasterThreadCtx.persistentStorage.offset = 0ull; + g_MasterThreadCtx.persistentStorage.reserved = MASTER_PS.baseline; + g_MasterThreadCtx.persistentStorage.size = MASTER_PS.size; + + p = reinterpret_cast(g_MasterThreadCtx.persistentStorage.base); + p = delta::platform::Memory_Commit(p, g_MasterThreadCtx.persistentStorage.reserved); + if (!p || !delta::platform::Memory_Lock(p, g_MasterThreadCtx.persistentStorage.reserved)) + { + std::cout << "Failed to reserve memory for master thread's persistent storage\n"; + std::abort(); + } + + cursor += MemoryMap::VIRT_ZONE_SPACE_LENGTH; + + // now workers + // we don't really care about the performance in the initialization sequence + constexpr auto WORKER_TS = MemoryMap::VIRT_MAP_WORKER[MemoryMap::VIRT_ZONE_GENERIC_TS_INDEX]; + for (uint32_t i = 0; i < workers; i++) + { + WorkerThreadContextView view = WorkerThreadContextView::FromSoA(g_WorkerThreadCtx, i); + + view.pagecrd.base = cursor; + view.pagecrd.size = MemoryMap::VIRT_ZONE_SPACE_LENGTH; + + view.transientStorage.base = cursor + WORKER_TS.offset; + view.transientStorage.offset = 0ull; + view.transientStorage.reserved = WORKER_TS.baseline; + view.transientStorage.size = WORKER_TS.size; + + void* p = reinterpret_cast(view.transientStorage.base); + p = delta::platform::Memory_Commit(p, view.transientStorage.reserved); + if (!p || !delta::platform::Memory_Lock(p, view.transientStorage.reserved)) + { + std::cout << "Failed to reserve memory for worker thread's persistent storage\n"; + std::abort(); + } + } + } + + void ThreadContextManager_Shutdown() + { + delta::platform::Memory_Release(reinterpret_cast(g_AddressSpaceStart)); + } + + tca_index_t ThreadContextManager_GetContextIx() noexcept + { + return tl_arrayIndex; + } + + bool _ThreadContextManager_IsExternallyAllocated_(uintptr_t ptr) noexcept + { + return ptr < g_AddressSpaceStart || ptr > g_AddressSpaceEnd; + } + + // -------------- MASTER THREAD API -------------- + void* MasterThread_AllocateTransient(size_t size, size_t alignment) noexcept + { + ArenaAllocatorAoS& ts = g_MasterThreadCtx.transientStorage; + const uintptr_t currentAddr = ts.base + ts.offset; + const uintptr_t alignedAddr = delta::utils::align_up(currentAddr, alignment); + const size_t padding = alignedAddr - currentAddr; + const size_t totalSize = size + padding; + + if (ts.offset + totalSize > ts.size) + return nullptr; + + if (ts.offset + totalSize > ts.reserved) + { + const uintptr_t commitStart = ts.base + ts.reserved; + const uintptr_t commitEnd = commitStart + totalSize; + + const uintptr_t pageStart = delta::utils::align_down_pow2(commitStart, g_MasterThreadCtx.pagecrd.pageSize); + const uintptr_t pageEnd = delta::utils::align_up_pow2(commitStart, g_MasterThreadCtx.pagecrd.pageSize); + const size_t commitSize = pageEnd - pageStart; + + void* ptr = reinterpret_cast(pageStart); + if (!delta::platform::Memory_Commit(ptr, commitSize)) + return nullptr; + + ts.reserved = pageEnd - ts.base; + } + + ts.offset += totalSize; + return reinterpret_cast(alignedAddr); + } + + void MasterThread_ResetTransient() noexcept + { + g_MasterThreadCtx.transientStorage.offset = 0ull; + } + + void* MasterThread_AllocatePersistent(size_t size, size_t alignment) noexcept + { + ArenaAllocatorAoS& ps = g_MasterThreadCtx.persistentStorage; + const uintptr_t currentAddr = ps.base + ps.offset; + const uintptr_t alignedAddr = delta::utils::align_up(currentAddr, alignment); + const size_t padding = alignedAddr - currentAddr; + const size_t totalSize = size + padding; + + if (ps.offset + totalSize > ps.size) + return nullptr; + + if (ps.offset + totalSize > ps.reserved) + { + const uintptr_t commitStart = ps.base + ps.reserved; + const uintptr_t commitEnd = commitStart + totalSize; + + const uintptr_t pageStart = delta::utils::align_down_pow2(commitStart, g_MasterThreadCtx.pagecrd.pageSize); + const uintptr_t pageEnd = delta::utils::align_up_pow2(commitStart, g_MasterThreadCtx.pagecrd.pageSize); + const size_t commitSize = pageEnd - pageStart; + + void* ptr = reinterpret_cast(pageStart); + if (!delta::platform::Memory_Commit(ptr, commitSize)) + return nullptr; + + ps.reserved = pageEnd - ps.base; + } + + ps.offset += totalSize; + return reinterpret_cast(alignedAddr); + } + + // -------------- WORKER THREAD API -------------- + void* WorkerThread_AllocateTransient(tca_index_t ix, size_t size, size_t alignment) noexcept + { + DLT_ASSERT(ix >= 0); + + ArenaAllocatorView ts = ArenaAllocatorView::FromSoA(g_WorkerThreadCtx.transientStorage, ix); + const uintptr_t currentAddr = ts.base + ts.offset; + const uintptr_t alignedAddr = delta::utils::align_up(currentAddr, alignment); + const size_t padding = alignedAddr - currentAddr; + const size_t totalSize = size + padding; + + if (ts.offset + totalSize > ts.size) + return nullptr; + + if (ts.offset + totalSize > ts.reserved) + { + const uintptr_t commitStart = ts.base + ts.reserved; + const uintptr_t commitEnd = commitStart + totalSize; + + const uintptr_t pageStart = delta::utils::align_down_pow2(commitStart, g_MasterThreadCtx.pagecrd.pageSize); + const uintptr_t pageEnd = delta::utils::align_up_pow2(commitStart, g_MasterThreadCtx.pagecrd.pageSize); + const size_t commitSize = pageEnd - pageStart; + + void* ptr = reinterpret_cast(pageStart); + if (!delta::platform::Memory_Commit(ptr, commitSize)) + return nullptr; + + ts.reserved = pageEnd - ts.base; + } + + ts.offset += totalSize; + return reinterpret_cast(alignedAddr); + } + + void WorkerThread_ResetTransient(tca_index_t ix) noexcept + { + DLT_ASSERT(ix >= 0); + g_WorkerThreadCtx.transientStorage.offset[ix] = 0ull; + } +} diff --git a/Engine/src/delta/core/ThreadContextManager.h b/Engine/src/delta/core/ThreadContextManager.h new file mode 100644 index 0000000..1a7b93c --- /dev/null +++ b/Engine/src/delta/core/ThreadContextManager.h @@ -0,0 +1,70 @@ +#pragma once + +#include "EngineTypes.h" + +#include +#include + +namespace delta::core +{ + template + struct Cursor + { + Cursor() = delete; + + inline Cursor(uintptr_t _ptr) + : ptr(_ptr) + {} + + uintptr_t ptr; + + template + inline operator T* () const noexcept + { + return reinterpret_cast(ptr); + } + + template + inline T cast() noexcept + { + return reinterpret_cast(ptr); + } + + template + inline Cursor& operator+=(const T& rhs) noexcept + { + ptr = delta::utils::align_up(ptr + rhs, Alignment); + return *this; + } + }; + + using tca_index_t = int64_t; + inline constexpr tca_index_t TCA_INDEX_INVALID = delta::utils::pack_string("uninit"); + inline constexpr tca_index_t TCA_INDEX_MASTER = -1; + + using tca_cursor_t = Cursor; + + void ThreadContextManager_Initialize(uint32_t totalThreads, uint32_t pageSize); + void ThreadContextManager_Shutdown(); + + // Generic + tca_index_t ThreadContextManager_GetContextIx() noexcept; + + bool _ThreadContextManager_IsExternallyAllocated_(uintptr_t ptr) noexcept; + + template + inline bool ThreadContextManager_IsExternallyAllocated(T* ptr) noexcept + { + return _ThreadContextManager_IsExternallyAllocated_(reinterpret_cast(ptr)); + } + + // --------------- MASTER THREAD --------------- + void* MasterThread_AllocateTransient(size_t size, size_t alignment = 8) noexcept; + void MasterThread_ResetTransient() noexcept; + + void* MasterThread_AllocatePersistent(size_t size, size_t alignment = 8) noexcept; + + // --------------- WORKER THREAD --------------- + void* WorkerThread_AllocateTransient(tca_index_t ix, size_t size, size_t alignment = 8) noexcept; + void WorkerThread_ResetTransient(tca_index_t ix) noexcept; +} diff --git a/Engine/src/delta/core/Worker.cpp b/Engine/src/delta/core/Worker.cpp deleted file mode 100644 index 7cded61..0000000 --- a/Engine/src/delta/core/Worker.cpp +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2026 Jakub Bączyk - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "Worker.h" -#include -#include - -namespace delta::core -{ - using ThreadHandle = delta::platform::ThreadHandle; - using ThreadCreateInfo = delta::platform::ThreadCreateInfo; - - using SemaphoreHandle = delta::platform::SemaphoreHandle; - - static uint32_t s_WorkerCount; - static ThreadHandle* s_Threads; - - static void WorkerProc(void* args) - { - WorkerExecutionContext* ctx = reinterpret_cast(args); - ThreadContext_SetCurrent((GenericExecutionContext*)ctx); - - task_t task; - payload_t payload; - DependencyCounter& depCounter = reinterpret_cast(*ctx->taskQueue.depCounterPtr); - while (!ctx->shouldClose.load(std::memory_order_acquire)) - { - // TODO: Figure out how to select a worker to steal some work from. - // I'll probably utilize modulo n cyclic groups. (algebra - group theory) - - if (TaskQueue_Pop(&ctx->taskQueue, &task, &payload)) - { - task(payload); - depCounter.count.fetch_sub(1, std::memory_order_release); - continue; - } - - ThreadArena_Reset(GetTransientArena()); - ctx->isAsleep.store(true, std::memory_order_release); - if (!ctx->shouldClose.load(std::memory_order_acquire)) - { - delta::platform::Sync_WaitSemaphore(ctx->sleepSemaphore); - } - - ctx->isAsleep.store(false, std::memory_order_release); - } - } - - void Worker_Init(uint32_t count) - { - s_WorkerCount = count; - - // TODO: Take a look at native thread api and figure out how this could be done better - // TODO: Set thread affinity mask - s_Threads = new(delta::Engine::AllocationType::PERSISTENT) ThreadHandle[count]; - ThreadCreateInfo* cs = new(delta::Engine::AllocationType::TRANSIENT) ThreadCreateInfo[count]; - - for (uint32_t i = 0; i < count; i++) - { - // looping through thread indices, workers start from ix=1 - ThreadCreateInfo& info = cs[i]; - ThreadHandle& handle = s_Threads[i]; - auto* ctx = ThreadContext_GetForIndex(i + 1); - info.fn = WorkerProc; - info.args = (void*)ctx; - handle = delta::platform::Thread_Create(&info); - ctx->threadHandle = handle; - delta::platform::Thread_AssignPhysicalCore(handle, i + 1); - delta::platform::Thread_SetName(handle, "Worker"); - } - } - - void Worker_Shutdown() - { - delta::platform::Thread_JoinMultiple(s_Threads, s_WorkerCount); - } -} diff --git a/Engine/src/delta/core/engine.cpp b/Engine/src/delta/core/engine.cpp index bfdcdc7..ca8f473 100644 --- a/Engine/src/delta/core/engine.cpp +++ b/Engine/src/delta/core/engine.cpp @@ -21,13 +21,10 @@ #include "EngineTypes.h" #include "MemoryConfig.h" -#include "ThreadContext.h" -#include "Worker.h" +#include "ThreadContextManager.h" -namespace delta::Engine +namespace delta::core { - using GenericExecutionContext = delta::core::GenericExecutionContext; - void Initialize(Context& context) { context.isRunning = true; @@ -38,54 +35,55 @@ namespace delta::Engine uint32_t totalThreads = osInfo->cpuPhysicalCoreCount; uint32_t pageSize = osInfo->osPageSize; delta::core::MemoryConfig_Initialize(memStatus.physicalInstalled, pageSize, totalThreads); - delta::core::ThreadContext_Initialize(totalThreads, pageSize); + delta::core::ThreadContextManager_Initialize(totalThreads, pageSize); - delta::platform::ThreadHandle th = delta::platform::Thread_GetCurrentHandle(); - delta::platform::Thread_AssignPhysicalCore(th, 0); - delta::core::Worker_Init(totalThreads-1); + context.window = delta::platform::Window_Create(); + delta::platform::Window_Show(context.window); + delta::platform::Window_Win32_Update(context.window); } void Update(Context& context) { // blah blah blah // do something - delta::platform::Sync_Sleep(100); - delta::core::ThreadArena_Reset(delta::core::GetTransientArena()); + + delta::platform::Window_ProcessEvents(); + delta::platform::Sync_Sleep(10); } void Shutdown(Context& context) { - delta::core::Worker_Shutdown(); - delta::core::ThreadContext_Shutdown(); + delta::core::Free(context.window); + delta::core::ThreadContextManager_Shutdown(); delta::core::MemoryConfig_Shutdown(); } [[nodiscard]] void* Allocate(size_t size, AllocationType type, size_t alignment) noexcept { - GenericExecutionContext* threadContext = delta::core::ThreadContext_GetCurrent(); + tca_index_t tca_ix = ThreadContextManager_GetContextIx(); - if (!threadContext) + if (tca_ix == TCA_INDEX_INVALID) { return ::malloc(size); } if (type == AllocationType::TRANSIENT) { - return core::ThreadArena_Allocate(&threadContext->transientArena, size, alignment); - } - else if (type == AllocationType::PERSISTENT && core::IsMainThread()) - { - core::MainExecutionContext* ctx = reinterpret_cast(threadContext); - return core::ThreadArena_Allocate(&ctx->persistentStorage, size, alignment); + if (tca_ix == TCA_INDEX_MASTER) + return MasterThread_AllocateTransient(size, alignment); + else + return WorkerThread_AllocateTransient(tca_ix, size, alignment); } + else if (type == AllocationType::PERSISTENT && tca_ix == TCA_INDEX_MASTER) + return MasterThread_AllocatePersistent(size, alignment); - assert(false); // CRITICAL FAILURE: Workers cannot allocate persistent memory! + DLT_ASSERT(false); // CRITICAL FAILURE: Workers cannot allocate persistent memory! return nullptr; } void Free(void* ptr) noexcept { - if (!core::IsCustomAllocated(ptr)) - ::free(ptr); + if (delta::core::ThreadContextManager_IsExternallyAllocated(ptr)) + return free(ptr); } } diff --git a/Engine/src/delta/core/pch.cpp b/Engine/src/delta/core/pch.cpp new file mode 100644 index 0000000..96fc755 --- /dev/null +++ b/Engine/src/delta/core/pch.cpp @@ -0,0 +1,18 @@ +/* + * Copyright 2026 Jakub Bączyk + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "pch.h" diff --git a/Engine/src/delta/core/pch.h b/Engine/src/delta/core/pch.h new file mode 100644 index 0000000..80c9227 --- /dev/null +++ b/Engine/src/delta/core/pch.h @@ -0,0 +1,21 @@ +/* + * Copyright 2026 Jakub Bączyk + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "EngineTypes.h" +#include "MemoryConfig.h" +#include "ThreadContextManager.h" diff --git a/Engine/src/delta/pch.cpp b/Engine/src/delta/pch.cpp index 5324409..4d901d7 100644 --- a/Engine/src/delta/pch.cpp +++ b/Engine/src/delta/pch.cpp @@ -15,4 +15,5 @@ */ #include +#include #include "pch.h" diff --git a/Engine/src/delta/pch.h b/Engine/src/delta/pch.h index 8a0d470..f3d91f5 100644 --- a/Engine/src/delta/pch.h +++ b/Engine/src/delta/pch.h @@ -19,7 +19,6 @@ #include #include #include -#include #include #include #include diff --git a/Engine/src/delta/platform/os_internal.h b/Engine/src/delta/platform/os_internal.h index 366dad2..1bd4976 100644 --- a/Engine/src/delta/platform/os_internal.h +++ b/Engine/src/delta/platform/os_internal.h @@ -15,6 +15,7 @@ */ #pragma once +#include namespace delta::platform { @@ -31,28 +32,12 @@ namespace delta::platform bool Memory_ElevateLockLimit(size_t maxBytesToLock); // Timer API - struct Timer_Internal; - struct Timer - { - alignas(8) uint8_t opaqueData[32]; - }; - void Timer_Initialize(Timer* timer); int64_t Timer_GetTimestamp(); double Timer_TicksToMilliseconds(const Timer* timer, int64_t startTicks, int64_t endTicks); double Timer_TicksToMicroseconds(const Timer* timer, int64_t startTicks, int64_t endTicks); // Thread API - struct Thread; - using ThreadHandle = Thread*; - inline constexpr ThreadHandle INVALID_THREAD_HANDLE = nullptr; // random number 696767 - - struct ThreadCreateInfo - { - void (*fn)(void*); - void* args; - }; - uint32_t Thread_GetCurrentId(); uint32_t Thread_GetId(ThreadHandle thread); ThreadHandle Thread_GetCurrentHandle(); @@ -63,15 +48,18 @@ namespace delta::platform void Thread_JoinMultiple(ThreadHandle* threads, uint32_t count); // Sync API - struct Semaphore; - using SemaphoreHandle = Semaphore*; - SemaphoreHandle Sync_CreateSemaphore(); void Sync_DestroySemaphore(SemaphoreHandle sem); void Sync_SignalSemaphore(SemaphoreHandle sem); void Sync_WaitSemaphore(SemaphoreHandle sem); void Sync_Sleep(uint32_t milliseconds); + // Window API + WindowHandle Window_Create(); + void Window_Win32_Update(WindowHandle window); + void Window_ProcessEvents(); + void Window_Destroy(WindowHandle window); + // Misc uint32_t Misc_GetLastError() noexcept; } diff --git a/Engine/src/delta/platform/os_internal_types.h b/Engine/src/delta/platform/os_internal_types.h new file mode 100644 index 0000000..864a8ef --- /dev/null +++ b/Engine/src/delta/platform/os_internal_types.h @@ -0,0 +1,27 @@ +#pragma once +#include + +namespace delta::platform +{ + // Timer API + struct Timer_Internal; + struct Timer + { + alignas(8) uint8_t opaqueData[32]; + }; + + // Thread API + DLT_DEFINE_HANDLE(Thread); + + struct ThreadCreateInfo + { + void (*fn)(void*); + void* args; + }; + + // Sync API + DLT_DEFINE_HANDLE(Semaphore); + + // Window API + // Window defined in the public header +} diff --git a/Engine/src/delta/platform/os_win32.cpp b/Engine/src/delta/platform/os_win32.cpp index dc1010d..4856cf2 100644 --- a/Engine/src/delta/platform/os_win32.cpp +++ b/Engine/src/delta/platform/os_win32.cpp @@ -25,6 +25,9 @@ #define CHECK_CPUID_FLAG(register, flag) ((register & (1 << flag)) != 0) +_declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; +_declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; + namespace delta::platform { static OSInfo g_osInfo; @@ -152,11 +155,18 @@ namespace delta::platform return true; } + // TODO: Move this variables to the reasonable place + static HINSTANCE s_hInstance = nullptr; + static STARTUPINFOA s_StartupInfo = { sizeof(STARTUPINFOA) }; + void Initialize() { memset(&g_osInfo, 0u, sizeof(OSInfo)); fetchCpuidValues(); + s_hInstance = GetModuleHandleA(nullptr); + GetStartupInfoA(&s_StartupInfo); + SYSTEM_INFO info; GetSystemInfo(&info); @@ -276,7 +286,7 @@ namespace delta::platform LARGE_INTEGER freq; BOOL freqResult = QueryPerformanceFrequency(&freq); - assert(freqResult && "Hardware high-performance counter is not supported by this system"); + DLT_ASSERT(freqResult && "Hardware high-performance counter is not supported by this system"); internal->freq = freq.QuadPart; LARGE_INTEGER start; @@ -326,7 +336,7 @@ namespace delta::platform ThreadHandle Thread_GetCurrentHandle() { - Thread* t = new(delta::Engine::AllocationType::PERSISTENT) Thread(); + Thread* t = new(delta::core::AllocationType::PERSISTENT) Thread(); t->hThread = GetCurrentThread(); if (!t->hThread) return nullptr; @@ -363,7 +373,7 @@ namespace delta::platform ThreadHandle Thread_Create(ThreadCreateInfo* createInfo) { - Thread* thread = new(delta::Engine::AllocationType::PERSISTENT) Thread{}; + Thread* thread = new(delta::core::AllocationType::PERSISTENT) Thread{}; thread->hThread = CreateThread(nullptr, 0, DeltaThreadProc, (void*)createInfo, 0, 0); if (thread->hThread == 0) return nullptr; @@ -390,7 +400,7 @@ namespace delta::platform SemaphoreHandle Sync_CreateSemaphore() { - Semaphore* s = new(delta::Engine::AllocationType::PERSISTENT) Semaphore{}; + Semaphore* s = new(delta::core::AllocationType::PERSISTENT) Semaphore{}; s->hSemaphore = CreateSemaphoreA(nullptr, 0, 1000000, nullptr); if (s->hSemaphore == nullptr) return nullptr; @@ -421,6 +431,146 @@ namespace delta::platform Sleep(milliseconds); } + // ------------------------------------------ WINDOW API ------------------------------------------ + + struct Window + { + HWND hwnd; + }; + + static constexpr const char MAIN_WND_CLASS_NAME[] = "DltWindow"; + + static LRESULT CALLBACK DltWindowProc(HWND hwnd, UINT umesg, WPARAM wparam, LPARAM lparam) + { + switch (umesg) + { + case WM_CLOSE: + // DestroyWindow(hwnd); + return 0; + default: + return DefWindowProcA(hwnd, umesg, wparam, lparam); + } + } + + WindowHandle Window_Create() + { + DLT_ASSERT(s_hInstance); + WindowHandle window = new(delta::core::AllocationType::PERSISTENT) Window(); + + int nCmdShow = (s_StartupInfo.dwFlags & STARTF_USESHOWWINDOW) ? s_StartupInfo.wShowWindow : SW_SHOWDEFAULT; + + const WNDCLASSEXA wc = + { + .cbSize = sizeof(WNDCLASSEXA), + .style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC, + .lpfnWndProc = DltWindowProc, + .cbClsExtra = 0, + .cbWndExtra = 0, + .hInstance = s_hInstance, + .hCursor = LoadCursorA(nullptr, IDC_ARROW), + .hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH), + .lpszClassName = MAIN_WND_CLASS_NAME + }; + + if (!RegisterClassExA(&wc)) + { + [[maybe_unused]] DWORD e = GetLastError(); + return nullptr; + } + + uint32_t clientWidth = 1280; + uint32_t clientHeight = 720; + + RECT wr = { 0, 0, static_cast(clientWidth), static_cast(clientHeight) }; + static constexpr DWORD windowStyle = WS_OVERLAPPEDWINDOW; + + AdjustWindowRect(&wr, windowStyle, FALSE); + + window->hwnd = CreateWindowExA( + 0, + MAIN_WND_CLASS_NAME, + "Delta Engine", + windowStyle, + CW_USEDEFAULT, CW_USEDEFAULT, + wr.right - wr.left, + wr.bottom - wr.top, + nullptr, + nullptr, + s_hInstance, + nullptr + ); + + if (!window->hwnd) + { + return nullptr; + } + + return window; + } + + void Window_Win32_Update(WindowHandle window) + { + UpdateWindow(window->hwnd); + } + + void Window_ProcessEvents() + { + MSG msg = {}; + while (PeekMessageA(&msg, nullptr, 0, 0, PM_REMOVE)) + { + if (msg.message == WM_QUIT) + { + // TODO: Handle this + } + + TranslateMessage(&msg); + DispatchMessageA(&msg); + } + } + + void Window_Destroy(WindowHandle window) + { + DestroyWindow(window->hwnd); + UnregisterClassA(MAIN_WND_CLASS_NAME, s_hInstance); + } + + void Window_SetTitle(WindowHandle window, const char* newTitle) + { + SetWindowTextA(window->hwnd, newTitle); + } + + void Window_Show(WindowHandle window) + { + ShowWindow(window->hwnd, SW_SHOW); + } + + void Window_Hide(WindowHandle window) + { + ShowWindow(window->hwnd, SW_HIDE); + } + + void Window_SetSize(WindowHandle window, uint32_t w, uint32_t h) + { + static constexpr UINT FLAGS = SWP_NOMOVE | SWP_NOZORDER | SWP_SHOWWINDOW; + SetWindowPos(window->hwnd, nullptr, 0, 0, w, h, FLAGS); + } + + void Window_SetPos(WindowHandle window, uint32_t x, uint32_t y) + { + static constexpr UINT FLAGS = SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW; + SetWindowPos(window->hwnd, nullptr, x, y, 0, 0, FLAGS); + } + + void Window_ShowCursor(WindowHandle window) + { + ShowCursor(TRUE); + } + + void Window_HideCursor(WindowHandle window) + { + ShowCursor(FALSE); + } + uint32_t Misc_GetLastError() noexcept { return static_cast(GetLastError()); diff --git a/Engine/src/delta/platform/pch.cpp b/Engine/src/delta/platform/pch.cpp new file mode 100644 index 0000000..5aa0fe2 --- /dev/null +++ b/Engine/src/delta/platform/pch.cpp @@ -0,0 +1,18 @@ +/* + * Copyright 2026 Jakub Bączyk + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "pch.h" +#include diff --git a/Engine/src/delta/platform/pch.h b/Engine/src/delta/platform/pch.h new file mode 100644 index 0000000..f341570 --- /dev/null +++ b/Engine/src/delta/platform/pch.h @@ -0,0 +1,20 @@ +/* + * Copyright 2026 Jakub Bączyk + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "os_internal.h" +#include "os_internal_types.h" diff --git a/Engine/src/delta/utils/pch.cpp b/Engine/src/delta/utils/pch.cpp new file mode 100644 index 0000000..446392a --- /dev/null +++ b/Engine/src/delta/utils/pch.cpp @@ -0,0 +1,17 @@ +/* + * Copyright 2026 Jakub Bączyk + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include diff --git a/Examples/HelloWorldGame/game.cpp b/Examples/HelloWorldGame/game.cpp index 44bb162..39db5d1 100644 --- a/Examples/HelloWorldGame/game.cpp +++ b/Examples/HelloWorldGame/game.cpp @@ -35,33 +35,18 @@ using OSInfo = delta::platform::OSInfo; extern "C" { - void GAME_API Game_OnInit(delta::Engine::Context* context) + void GAME_API Game_OnInit(delta::core::Context* context) { const OSInfo* info = delta::platform::getOSInfo(); - - std::cout << "Initializing game\n"; - std::cout << "System Info:\n"; - std::cout << - "\tOS Page Size: " << info->osPageSize << " bytes\n" << - "\tCPU Architecture: " << info->cpuArchitecture << "\n" << - "\tCPU Manufacturer: " << info->cpuManufacturerId << "\n" << - "\tCPU Model: " << info->cpuBrandString << "\n" << - "\tCPU Physical Cores: " << info->cpuPhysicalCoreCount << "\n" << - "\tCPU Logical Cores: " << info->cpuLogicalProcessorCount << "\n" << - "\tCPU Has SMT: " << STDOUT_BOOL_FORMAT(info->cpuHasSMT) << "\n" << - "\tAVX2 Available: " << STDOUT_BOOL_FORMAT(info->cpuHasAVX2) << "\n" << - "\tAVX512 Foundation Available: " << STDOUT_BOOL_FORMAT(info->cpuHasAVX512f) << "\n" << - "\tAVX512 Conflict Detection Available: " << STDOUT_BOOL_FORMAT(info->cpuHasAVX512cd) << "\n" << - "\tAVX512 Exponential and Reciprocal Available: " << STDOUT_BOOL_FORMAT(info->cpuHasAVX512er) << "\n" << - "\tAVX512 Prefetch Available: " << STDOUT_BOOL_FORMAT(info->cpuHasAVX512pf) << "\n"; + delta::platform::Window_SetTitle(context->window, "Hello World Game"); } - void GAME_API Game_OnUpdate(delta::Engine::Context* context) + void GAME_API Game_OnUpdate(delta::core::Context* context) { } - void GAME_API Game_OnShutdown(delta::Engine::Context* context) + void GAME_API Game_OnShutdown(delta::core::Context* context) { } diff --git a/Launcher/main.cpp b/Launcher/main.cpp index d1525ae..1e38e37 100644 --- a/Launcher/main.cpp +++ b/Launcher/main.cpp @@ -40,9 +40,9 @@ namespace fs = std::filesystem; -using InitFunc = delta::Engine::GameInitFunc; -using UpdateFunc = delta::Engine::GameUpdateFunc; -using ShutdownFunc = delta::Engine::GameShutdownFunc; +using InitFunc = delta::core::GameInitFunc; +using UpdateFunc = delta::core::GameUpdateFunc; +using ShutdownFunc = delta::core::GameShutdownFunc; template inline T loadSymbol(const char* symName, DynamicLibrary lib) @@ -129,7 +129,7 @@ struct Game } }; -static delta::Engine::Context g_context; +static delta::core::Context g_context; static trigger::SignalSocket g_reloadSocket; int main(int argc, char** argv) @@ -148,7 +148,7 @@ int main(int argc, char** argv) } - delta::Engine::Initialize(g_context); + delta::core::Initialize(g_context); const char* libPath = argv[1]; Game game(libPath); @@ -172,12 +172,12 @@ int main(int argc, char** argv) continue; } - delta::Engine::Update(g_context); + delta::core::Update(g_context); game.updateFn(&g_context); } game.shutdownFn(&g_context); - delta::Engine::Shutdown(g_context); + delta::core::Shutdown(g_context); return 0; } diff --git a/cmake/DeltaCodegen.cmake b/cmake/DeltaCodegen.cmake new file mode 100644 index 0000000..865cf3b --- /dev/null +++ b/cmake/DeltaCodegen.cmake @@ -0,0 +1,52 @@ +find_package(Python3 COMPONENTS Interpreter REQUIRED) + +file(GLOB_RECURSE ALL_CODEGEN_SCRIPTS "${DLT_ROOT_DIR}/CodeGen/*.py") +file(GLOB_RECURSE ALL_CODEGEN_SCHEMAS "${DLT_ROOT_DIR}/CodeGen/schemas/*.json") +set_property(DIRECTORY APPEND PROPERTY + CMAKE_CONFIGURE_DEPENDS ${ALL_CODEGEN_SCRIPTS} ${ALL_CODEGEN_SCHEMAS} +) + +set(DELTA_CODEGEN_SCRIPT "${DLT_ROOT_DIR}/CodeGen/main.py") + +function(delta_create_codegen_target TARGET_NAME) + set(SCHEMA_FILES ${ARGN}) + if (NOT SCHEMA_FILES) + message(FATAL_ERROR "delta_create_codegen_target requires at least one schema file layout description!") + endif() + + set(CODEGEN_TARGET_DIR_PREFIX "${DLT_BUILD_DIR}/generated/${TARGET_NAME}") + set(CODEGEN_TARGET_DIR "${CODEGEN_TARGET_DIR_PREFIX}/delta/generated") + set(GENERATED_HEADERS "") + + foreach(SCHEMA_FILE IN LISTS SCHEMA_FILES) + get_filename_component(SCHEMA_ABS_PATH "${SCHEMA_FILE}" ABSOLUTE) + get_filename_component(SCHEMA_NAME "${SCHEMA_FILE}" NAME_WE) + + set(OUTPUT_HEADER "${CODEGEN_TARGET_DIR}/${SCHEMA_NAME}.h") + list(APPEND GENERATED_HEADERS "${OUTPUT_HEADER}") + + set_property(DIRECTORY APPEND PROPERTY + CMAKE_CONFIGURE_DEPENDS ${SCHEMA_ABS_PATH} + ) + + execute_process( + COMMAND ${Python3_EXECUTABLE} "${DELTA_CODEGEN_SCRIPT}" "${SCHEMA_ABS_PATH}" "${OUTPUT_HEADER}" + RESULT_VARIABLE CODEGEN_EXIT_STATUS + OUTPUT_VARIABLE CODEGEN_STDOUT + ERROR_VARIABLE CODEGEN_STDERR + ) + + if (NOT CODEGEN_EXIT_STATUS EQUAL 0) + message(FATAL_ERROR + "CodeGen failed processing: ${SCHEMA_FILE}\n" + "Exit Code: ${CODEGEN_EXIT_STATUS}\n" + "Log Output:\n${CODEGEN_STDOUT}\n" + "Error Trace:\n${CODEGEN_STDERR}\n" + ) + endif() + endforeach() + + add_library(${TARGET_NAME} INTERFACE) + target_include_directories(${TARGET_NAME} INTERFACE "${CODEGEN_TARGET_DIR_PREFIX}") + target_sources(${TARGET_NAME} INTERFACE ${GENERATED_HEADERS}) +endfunction()