From 96a122f932f6c958db7408ac3817f1a6748e971c Mon Sep 17 00:00:00 2001 From: Asuro Date: Fri, 7 Mar 2025 00:37:44 +0100 Subject: [PATCH] minidef --- src/CMakeLists.txt | 8 +- src/dependency/minidef/CMakeLists.txt | 9 + src/dependency/minidef/mini.def | 21 + src/dependency/minidef/src/Gen/Def.h | 2 + src/dependency/minidef/src/Gen/Generated.h | 58 +++ src/dependency/minidef/src/MiniDef.cpp | 503 +++++++++++++++++++++ src/dependency/minidef/src/MiniDef.h | 160 +++++++ src/game/Instance.h | 10 +- src/game/Level.cpp | 4 +- src/game/Puzzle.cpp | 45 ++ src/game/Puzzle.h | 9 + src/game/Serial.cpp | 32 ++ src/game/Serial.h | 32 ++ src/game/mini.def | 8 + src/game/rendering/Rendering.cpp | 43 +- src/gen/Def.h | 2 + src/gen/Generated.h | 14 + 17 files changed, 944 insertions(+), 16 deletions(-) create mode 100644 src/dependency/minidef/CMakeLists.txt create mode 100644 src/dependency/minidef/mini.def create mode 100644 src/dependency/minidef/src/Gen/Def.h create mode 100644 src/dependency/minidef/src/Gen/Generated.h create mode 100644 src/dependency/minidef/src/MiniDef.cpp create mode 100644 src/dependency/minidef/src/MiniDef.h create mode 100644 src/game/Serial.cpp create mode 100644 src/game/Serial.h create mode 100644 src/game/mini.def create mode 100644 src/gen/Def.h create mode 100644 src/gen/Generated.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 37a8903..ce61a52 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -19,18 +19,16 @@ file(GLOB imgui_backend_sdl dependency/imgui/backends/imgui_impl_sdl3.h dependency/imgui/backends/imgui_impl_sdl3.cpp) +add_subdirectory("${CMAKE_SOURCE_DIR}/dependency/minidef") + # Engine file(GLOB_RECURSE sources_engine engine/*.cpp engine/*.h) add_executable(PuzGameEngine ${sources_engine}) set_property(TARGET PuzGameEngine PROPERTY CXX_STANDARD 17) target_include_directories(PuzGameEngine PUBLIC) -#file(COPY ${data} DESTINATION resources) - -#target_compile_options(PuzGameEngine PUBLIC xyz) -#file(GLOB_RECURSE data resources/xyz) # Game -file(GLOB_RECURSE sources_game game/*.cpp game/*.h) +file(GLOB_RECURSE sources_game game/*.cpp game/*.h gen/*.cpp gen/*.h) file(GLOB source_singleheader dependency/tinygltf/stb_image.h dependency/tinygltf/stb_image_write.h dependency/tinygltf/json.hpp dependency/tinygltf/tiny_gltf.h) add_library(PuzGame SHARED ${sources_game} ${source_singleheader} ${imgui_sources} ${imgui_backend_sdl}) set_property(TARGET PuzGame PROPERTY CXX_STANDARD 17) diff --git a/src/dependency/minidef/CMakeLists.txt b/src/dependency/minidef/CMakeLists.txt new file mode 100644 index 0000000..c136c5b --- /dev/null +++ b/src/dependency/minidef/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.10) +project(MiniDefProj) +add_executable(minidef "${CMAKE_CURRENT_SOURCE_DIR}/src/MiniDef.cpp") +target_include_directories(minidef PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/../bgfx.cmake/bx/include/" + "${CMAKE_CURRENT_SOURCE_DIR}/../bgfx.cmake/bx/include/compat/msvc/" +) +target_link_libraries(minidef bx) +target_compile_definitions(minidef PRIVATE "$<$:BX_CONFIG_DEBUG=1>$<$:BX_CONFIG_DEBUG=0>") diff --git a/src/dependency/minidef/mini.def b/src/dependency/minidef/mini.def new file mode 100644 index 0000000..0df4fe5 --- /dev/null +++ b/src/dependency/minidef/mini.def @@ -0,0 +1,21 @@ +type Texture +{ + u32 Width + u32 Height + str Test Arr(3) +} + +enum KnownType +{ + i8 CName("int8_t") + i16 CName("int16_t") + i32 CName("int32_t") + i64 CName("int64_t") + u8 CName("uint8_t") + u16 CName("uint16_t") + u32 CName("uint32_t") + u64 CName("uint64_t") + f32 CName("float") + f64 CName("double") + str CName("char") +} diff --git a/src/dependency/minidef/src/Gen/Def.h b/src/dependency/minidef/src/Gen/Def.h new file mode 100644 index 0000000..d94094d --- /dev/null +++ b/src/dependency/minidef/src/Gen/Def.h @@ -0,0 +1,2 @@ +#pragma once +#include \ No newline at end of file diff --git a/src/dependency/minidef/src/Gen/Generated.h b/src/dependency/minidef/src/Gen/Generated.h new file mode 100644 index 0000000..8883568 --- /dev/null +++ b/src/dependency/minidef/src/Gen/Generated.h @@ -0,0 +1,58 @@ +#pragma once +#include "Def.h" + +namespace Generated +{ + struct Texture + { + uint32_t Width = {}; + uint32_t Height = {}; + char Test[3] = {}; + }; + struct KnownType + { + static constexpr int32_t EntryCount = 11; + enum class Enum + { + i8, + i16, + i32, + i64, + u8, + u16, + u32, + u64, + f32, + f64, + str, + }; + static constexpr char EntryNames[EntryCount][64] + { + "i8", + "i16", + "i32", + "i64", + "u8", + "u16", + "u32", + "u64", + "f32", + "f64", + "str", + }; + static constexpr char CName[EntryCount][64] + { + "int8_t", + "int16_t", + "int32_t", + "int64_t", + "uint8_t", + "uint16_t", + "uint32_t", + "uint64_t", + "float", + "double", + "char", + }; + }; +} diff --git a/src/dependency/minidef/src/MiniDef.cpp b/src/dependency/minidef/src/MiniDef.cpp new file mode 100644 index 0000000..55e2001 --- /dev/null +++ b/src/dependency/minidef/src/MiniDef.cpp @@ -0,0 +1,503 @@ +#include "MiniDef.h" +#include "bx/string.h" +#include +#include +#include + +namespace WriteTemplates +{ + constexpr char FileHeader[] = + R"END(#pragma once +#include "Def.h" + +namespace Generated +{ +)END"; + + constexpr char StructHeader1[] = + R"END( struct %s + { +)END"; + + constexpr char StructField4[] = + R"END( %s %s%s = %s; +)END"; + + constexpr char StructEnd[] = + R"END( }; +)END"; + + constexpr char EnumHeader2[] = + R"END( struct %s + { + static constexpr int32_t EntryCount = %u; + enum class Enum + { +)END"; + + constexpr char EnumField1[] = + R"END( %s, +)END"; + + constexpr char EnumNamesStart2[] = + R"END( }; + static constexpr char %s[EntryCount][%u] + { +)END"; + + constexpr char EnumNamesEntry1[] = + R"END( "%s", +)END"; + + constexpr char EnumNamesEnd[] = + R"END( }; + }; +)END"; + + constexpr char FileEnd[] = + R"END(} +)END"; +} // namespace WriteTemplates + +void DefinitionFile::PrintTypeName(char* buf, int32_t bufSize, Def::TypeUnion type) +{ + if (int32_t(type.Known) < Generated::KnownType::EntryCount) + { + bx::strCopy(buf, bufSize, Generated::KnownType::CName[size_t(type.Known)]); + } +} + +void DefinitionFile::GenerateCpp(const bx::FilePath& outDir) +{ + bx::Error error; + bx::FileWriter writer; + bx::FilePath writePath = outDir; + + std::filesystem::create_directory(outDir.getCPtr()); + writePath.join("Generated.h"); + writer.open(writePath, false, &error); + + TemplateWriter Template{writer, error}; + Template.Write(WriteTemplates::FileHeader); + + for (int32_t typeIdx = 0; typeIdx < TypeCount; ++typeIdx) + { + Def::Type& t = Types[typeIdx]; + + Template.Write(WriteTemplates::StructHeader1, t.Name); + for (int32_t fieldIdx = 0; fieldIdx < t.FieldCount; ++fieldIdx) + { + char Type[64]{0}; + PrintTypeName(Type, sizeof(Type), t.FieldTypes[fieldIdx]); + char Array[32]{0}; + uint32_t ArraySize = t.FieldArraySizes[fieldIdx]; + if (ArraySize > 0) + { + bx::snprintf(Array, sizeof(Array), "[%u]", ArraySize); + } + Template.Write(WriteTemplates::StructField4, Type, t.FieldNames[fieldIdx], Array, "{}"); + } + Template.Write(WriteTemplates::StructEnd); + } + + for (int32_t enumIdx = 0; enumIdx < EnumCount; ++enumIdx) + { + Def::Enum& e = Enums[enumIdx]; + + Template.Write(WriteTemplates::EnumHeader2, e.Name, e.EntryCount); + for (int32_t entryIdx = 0; entryIdx < e.EntryCount; ++entryIdx) + { + Template.Write(WriteTemplates::EnumField1, e.EntryNames[entryIdx]); + } + + Template.Write(WriteTemplates::EnumNamesStart2, "EntryNames", Def::MaxNameLength); + for (int32_t entryIdx = 0; entryIdx < e.EntryCount; ++entryIdx) + { + Template.Write(WriteTemplates::EnumNamesEntry1, e.EntryNames[entryIdx]); + } + + for (int32_t extraIdx = 0; extraIdx < e.ExtraStringFieldCount; ++extraIdx) + { + Template.Write(WriteTemplates::EnumNamesStart2, e.ExtraStringFieldNames[extraIdx], Def::MaxNameLength); + for (int32_t entryIdx = 0; entryIdx < e.EntryCount; ++entryIdx) + { + Template.Write(WriteTemplates::EnumNamesEntry1, e.ExtraStringFields[entryIdx][extraIdx]); + } + } + + Template.Write(WriteTemplates::EnumNamesEnd); + } + + Template.Write(WriteTemplates::FileEnd); + writer.close(); +} + +bool Parser::CmpAdvance(bx::StringView expect, Result& Res) +{ + uint32_t size = expect.getLength(); + if (size > GetRemaining()) + { + std::cout << "Failed to match " << expect.getPtr() << ", " << size << "<" << GetRemaining() << std::endl; + return false; + } + int32_t i = 0; + for (; i < size; ++i) + { + if (expect.getPtr()[i] == 0) + { + i--; + break; + } + if (expect.getPtr()[i] != ReadPtr[i]) return false; + } + Res = Advance(i); + return true; +} + +Parser::Result Parser::ExpectChar(bx::StringView expect) +{ + Result Res = OK; + if (!CmpAdvance(expect, Res)) + { + std::cout << "Expected " << expect.getPtr() << std::endl; + return Error; + } + return Res; +} + +Parser::Result Parser::SkipWhitespace(bool* SkippedNewLine) +{ + int32_t Remaining = GetRemaining(); + for (int32_t i = 0; i < Remaining; ++i) + { + if (*ReadPtr == 0) return EndOfFile; + if (SkippedNewLine != nullptr && (*ReadPtr == '\r' || *ReadPtr == '\n')) *SkippedNewLine = true; + if (isgraph(*ReadPtr)) return OK; + ReadPtr++; + } + return EndOfFile; +} + +Parser::Result Parser::SkipLine() +{ + int32_t Remaining = GetRemaining(); + for (int32_t i = 0; i < Remaining; ++i) + { + if (*ReadPtr == 0) return EndOfFile; + if (*ReadPtr == '\n' && i < Remaining - 1) + { + ReadPtr++; + if (*ReadPtr == 0) return EndOfFile; + return OK; + } + ReadPtr++; + } + return EndOfFile; +} + +Parser::Result Parser::Advance(int32_t Amount) +{ + int32_t Remaining = GetRemaining(); + if (Amount > Remaining) + { + ReadPtr += Remaining; + return EndOfFile; + } + ReadPtr += Amount; + return OK; +} + +#define CHECK(x) \ + Res = x; \ + if (Res != OK) return Res; + +Parser::Result Parser::Parse() +{ + ReadPtr = &Buffer[0]; + Parser::Result Res = Parser::OK; + while (Res == Parser::OK) + { + Res = SkipWhitespace(); + if (Res == Parser::OK) + { + // Start of Line + if (*ReadPtr == '#') + { + Res = SkipLine(); + continue; + } + + Res = HandleFileStart(); + } + } + + return Res; +} + +Parser::Result Parser::HandleFileStart() +{ + Result Res = OK; + if (CmpAdvance("type", Res) && Res == OK) + { + return HandleType(); + } + if (Res != OK) return Res; + + if (CmpAdvance("enum", Res) && Res == OK) + { + return HandleEnum(); + } + if (Res != OK) return Res; + + std::cout << "Only valid here: type or enum!"; + return Error; +} + +Parser::Result Parser::HandleType() +{ + Result Res = OK; + if (!Stack.Push(TokenType::TypeKeyword)) return Error; + CHECK(SkipWhitespace()); + + if (Definitions.TypeCount >= DefinitionFile::MaxTypes) + { + std::cout << "Too many types!" << std::endl; + return Error; + } + Def::Type& t = Definitions.Types[Definitions.TypeCount]; + CHECK(ReadName(t.Name)); + + CHECK(SkipWhitespace()); + CHECK(ExpectChar("{")); + CHECK(SkipWhitespace()); + + while (!CmpAdvance("}", Res)) + { + if (t.FieldCount >= Def::MaxFields) + { + std::cout << "Too many fields in type!" << std::endl; + return Error; + } + CHECK(ReadTypeToken()); + CHECK(SkipWhitespace()); + CHECK(ReadName(t.FieldNames[t.FieldCount])); + + bool NewLine = false; + CHECK(SkipWhitespace(&NewLine)); + + if (!NewLine) + { + Result Res; + if (CmpAdvance("Arr", Res) && Res == Result::OK) + { + CHECK(ExpectChar("(")); + CHECK(ReadUint(t.FieldArraySizes[t.FieldCount])); + CHECK(ExpectChar(")")); + } + } + + CHECK(SkipWhitespace()); + t.FieldCount++; + } + Definitions.TypeCount++; + return Res; +} + +Parser::Result Parser::HandleEnum() +{ + Result Res = OK; + if (!Stack.Push(TokenType::EnumKeyword)) return Error; + CHECK(SkipWhitespace()); + + if (Definitions.EnumCount >= DefinitionFile::MaxTypes) + { + std::cout << "Too many enums!" << std::endl; + return Error; + } + Def::Enum& e = Definitions.Enums[Definitions.EnumCount]; + CHECK(ReadName(e.Name)); + + CHECK(SkipWhitespace()); + CHECK(ExpectChar("{")); + CHECK(SkipWhitespace()); + + while (!CmpAdvance("}", Res)) + { + if (e.EntryCount >= Def::MaxFields) + { + std::cout << "Too many fields in enum!" << std::endl; + return Error; + } + CHECK(ReadName(e.EntryNames[e.EntryCount])); + CHECK(ReadOptionalEnumValues(e, e.EntryCount)); + CHECK(SkipWhitespace()); + e.EntryCount++; + } + Definitions.EnumCount++; + return Res; +} + +Parser::Result Parser::ReadName(char* Target) +{ + int32_t Remaining = GetRemaining(); + for (int32_t i = 0; i < Remaining; ++i) + { + if (!IsValidNameCharacter(*ReadPtr)) + { + if (i == 0) + { + std::cout << "Empty names are not allowed!" << std::endl; + return Error; + } + return OK; + } + if (i < Def::MaxNameLength) + { + Target[i] = *ReadPtr; + } + else + { + std::cout << "Name too long!" << std::endl; + return Error; + } + ReadPtr++; + } + return EndOfFile; +} + +Parser::Result Parser::ReadUint(uint32_t& Out) +{ + char DigitBuf[32]{0}; + int32_t Remaining = GetRemaining(); + if (Remaining == 0) return Result::EndOfFile; + int32_t Written = 0; + + for (int32_t i = 0; i < Remaining; ++i) + { + if (*ReadPtr < '0' || *ReadPtr > '9') break; + + DigitBuf[i] = *ReadPtr; + ++ReadPtr; + ++Written; + } + if (Written == 0) + { + std::cout << "No digits for uint: " << PrintLine() << std::endl; + return Result::Error; + } + if (!bx::fromString(&Out, DigitBuf)) + { + std::cout << "Failed to parse uint: " << PrintLine() << std::endl; + return Result::Error; + } + return Result::OK; +} + +Parser::Result Parser::ReadTypeToken() +{ + Result Res = OK; + for (int32_t i = 0; i < Generated::KnownType::EntryCount; ++i) + { + if (CmpAdvance(Generated::KnownType::EntryNames[i], Res) && Res == OK) + { + Def::Type& t = Definitions.Types[Definitions.TypeCount]; + t.FieldTypes[t.FieldCount].Known = Generated::KnownType::Enum(i); + return OK; + } + } + std::cout << "Unknown type token:" << std::endl << PrintLine() << std::endl; + return Error; +} + +Parser::Result Parser::ReadOptionalEnumValues(Def::Enum& Enum, int32_t EntryIdx) +{ + Result Res = OK; + + bool SkippedNewLine = false; + CHECK(SkipWhitespace(&SkippedNewLine)); + if (SkippedNewLine) return Res; + + for (int32_t extraIdx = 0; extraIdx < Def::MaxExtraEnumFields; ++extraIdx) + { + CHECK(ReadName(Enum.ExtraStringFieldNames[extraIdx])); + CHECK(SkipWhitespace()); + CHECK(ExpectChar("(")); + CHECK(SkipWhitespace()); + CHECK(ExpectChar("\"")); + + int32_t Remaining = GetRemaining(); + for (int32_t i = 0; i < Remaining; ++i) + { + + if (CmpAdvance("\"", Res)) break; + Enum.ExtraStringFields[extraIdx][EntryIdx][i] = *ReadPtr; + ReadPtr++; + } + if (Res != OK) return Res; + + CHECK(SkipWhitespace()); + CHECK(ExpectChar(")")); + CHECK(SkipWhitespace(&SkippedNewLine)); + Enum.ExtraStringFieldCount = bx::max(Enum.ExtraStringFieldCount, extraIdx + 1); + + if (SkippedNewLine) return Res; + } + std::cout << "Too many extra enum fields!" << std::endl; + return Error; +} + +int32_t Parser::GetRemaining() +{ + return &Buffer[BufferSize] - ReadPtr; +} + +bool Log::Assert(bool Condition, const char* Text) +{ + if (!Condition) + { + std::cout << "Assertion failed: " << Text << std::endl; + bx::debugBreak(); + } + return !Condition; +} + +char WriteBuffer[1024 * 1024]{0}; +void TemplateWriter::Write(const char* Template, ...) +{ + va_list ArgList; + va_start(ArgList, Template); + int32_t Count = bx::vsnprintf(WriteBuffer, sizeof(WriteBuffer), Template, ArgList); + va_end(ArgList); + bx::write(&Writer, WriteBuffer, Count, &Error); +} + +Parser TestParser; +int main(int argc, const char** argv) +{ + if (argc != 3) + { + std::cout << "invalid number of arguments!" << std::endl; + return 1; + } + bx::FilePath defPath(argv[1]); + bx::FilePath outPath(argv[2]); + + bx::FileReader reader; + bx::Error error; + if (!reader.open(defPath, &error)) + { + std::cout << "failed to find def file at " << error.getMessage().getCPtr() << std::endl; + return 1; + } + + TestParser.BufferSize = bx::read(&reader, TestParser.Buffer, Parser::MaxBufferSize, &error); + Parser::Result Res = TestParser.Parse(); + + if (Res == Parser::EndOfFile) + { + std::cout << "Success!" << std::endl; + TestParser.Definitions.GenerateCpp(outPath); + } + + return 0; +} diff --git a/src/dependency/minidef/src/MiniDef.h b/src/dependency/minidef/src/MiniDef.h new file mode 100644 index 0000000..828ba57 --- /dev/null +++ b/src/dependency/minidef/src/MiniDef.h @@ -0,0 +1,160 @@ +#pragma once +#include +#include +#include + +#include "Gen/Generated.h" +#include "bx/string.h" + +struct Log +{ + static bool Assert(bool Condition, const char* Text); +}; + +enum class TokenType : uint8_t +{ + StartOfFile, + TypeKeyword, + EnumKeyword, + Name, + ScopeStart, + ScopeEnd, +}; + +struct ParseStack +{ + static constexpr size_t MaxSize = 32; + TokenType Stack[MaxSize] = {TokenType::StartOfFile}; + int32_t StackIdx = 0; + + TokenType GetCurrent() + { + return Stack[StackIdx]; + } + bool Push(TokenType T) + { + if (Log::Assert(StackIdx < MaxSize - 1, "Inbalanced Push/Pop!")) return false; + StackIdx++; + Stack[StackIdx] = T; + return true; + } + TokenType Pop() + { + if (Log::Assert(StackIdx == 0, "Inbalanced Push/Pop!")) return TokenType::StartOfFile; + TokenType T = Stack[StackIdx]; + StackIdx--; + return T; + } +}; + +namespace Def +{ + constexpr int32_t MaxNameLength = 64; + constexpr int32_t MaxFields = 64; + constexpr int32_t MaxExtraEnumFields = 1; + union TypeUnion + { + Generated::KnownType::Enum Known; + uint32_t TypeIdx; + }; + + struct Type + { + int32_t FieldCount = 0; + TypeUnion FieldTypes[MaxFields]; + char FieldNames[MaxFields][MaxNameLength]; + uint32_t FieldArraySizes[MaxFields]{0}; + char Name[MaxNameLength]{0}; + }; + + struct Enum + { + int32_t EntryCount = 0; + char EntryNames[MaxFields][MaxNameLength]; + int32_t ExtraStringFieldCount = 0; + char ExtraStringFieldNames[MaxExtraEnumFields][MaxNameLength]; + char ExtraStringFields[MaxFields][MaxExtraEnumFields][MaxNameLength]; + char Name[MaxNameLength]{0}; + }; +} // namespace Def + +struct DefinitionFile +{ + static constexpr int32_t MaxTypes = 256; + void PrintTypeName(char* buf, int32_t bufSize, Def::TypeUnion type); + void GenerateCpp(const bx::FilePath& outDir); + + Def::Type Types[MaxTypes]; + int32_t TypeCount = 0; + Def::Enum Enums[MaxTypes]; + int32_t EnumCount = 0; +}; + +class Parser +{ + public: + enum Result : uint8_t + { + OK, + Error, + EndOfFile, + }; + + static constexpr int32_t MaxBufferSize = 1024 * 1024; + static constexpr int32_t MaxTokens = UINT16_MAX; + static bool IsValidNameCharacter(char c) + { + return isgraph(c) && c != '"' && c != '(' && c != ')'; + } + + int32_t BufferSize = 0; + char Buffer[MaxBufferSize]{0}; + char* ReadPtr = &Buffer[0]; + + DefinitionFile Definitions; + ParseStack Stack; + + public: + Result Parse(); + + private: + int32_t GetRemaining(); + Result SkipWhitespace(bool* SkippedNewLine = nullptr); + Result SkipLine(); + bool CmpAdvance(bx::StringView expect, Result& Res); + Result ExpectChar(bx::StringView expect); + Result Advance(int32_t Amount); + Result HandleFileStart(); + Result HandleType(); + Result HandleEnum(); + Result ReadName(char* Target); + Result ReadUint(uint32_t& Out); + Result ReadTypeToken(); + Result ReadOptionalEnumValues(Def::Enum& Enum, int32_t EntryIdx); + + std::string PrintLine() + { + char line[64]{0}; + for (int32_t i = 0; i < sizeof(line) - 1; ++i) + { + line[i] = ReadPtr[0]; + if (Advance(1) != Result::OK) break; + if (*ReadPtr == '\n' || *ReadPtr == '\r') break; + } + return std::string{line}; + } +}; + +class TemplateWriter +{ + private: + bx::WriterI& Writer; + bx::Error& Error; + + public: + TemplateWriter(bx::WriterI& W, bx::Error& E) : Writer(W), Error(E) + { + } + + void Write(const char* Template, ...); +}; diff --git a/src/game/Instance.h b/src/game/Instance.h index 923584a..c5fed09 100644 --- a/src/game/Instance.h +++ b/src/game/Instance.h @@ -38,6 +38,13 @@ namespace Game InputMode InputM = InputMode::Game; }; + struct InstanceDebugData + { + uint16_t SelectedDebugLevel = UINT16_MAX; + uint64_t ImguiIniSize = 0; + char ImguiIni[4096]{0}; + }; + struct GameInstance { bool IsInitialized = false; @@ -46,7 +53,6 @@ namespace Game Time Time; PlayerData Player; Level GameLevel; - uint64_t ImguiIniSize = 0; - char ImguiIni[4096]{0}; + InstanceDebugData DebugData; }; } // namespace Game diff --git a/src/game/Level.cpp b/src/game/Level.cpp index 9a164db..fef01a5 100644 --- a/src/game/Level.cpp +++ b/src/game/Level.cpp @@ -205,7 +205,7 @@ namespace Game // Cubes.Render(models, materials); Tests.Render(models, materials); - // PuzzleTiles.Render(models, materials); + PuzzleTiles.Render(models, materials); UIQuads.Render(models, materials); } @@ -271,7 +271,7 @@ namespace Game UIQuadEntity& quad = level.UIQuads.Get(UIPlacedCards[cardI]); quad.EData.MaterialHandle = 0; - quad.EData.ModelHandle = GameRendering::Get().GetModelHandleFromPath("models/cube.glb"); + quad.EData.ModelHandle = GameRendering::Get().GetModelHandleFromPath("models/plane.glb"); } } diff --git a/src/game/Puzzle.cpp b/src/game/Puzzle.cpp index 371f9a7..f6b2585 100644 --- a/src/game/Puzzle.cpp +++ b/src/game/Puzzle.cpp @@ -1,5 +1,7 @@ #include "Log.h" #include "Puzzle.h" +#include "bx/string.h" +#include "imgui.h" #include "rendering/Rendering.h" #include "bx/bx.h" @@ -154,4 +156,47 @@ namespace Puzzle return (sourceType == PuzzleElementType::WaterIn && goalType == PuzzleElementType::WaterGoal) || (sourceType == PuzzleElementType::ElectricIn && goalType == PuzzleElementType::ElectricGoal); } + + bool PuzzleData::RenderDebugUI() + { + bool dataChanged = false; + + bool isVisible = true; + if (ImGui::Begin("Puzzle", &isVisible)) + { + ImGui::Text("%s", PuzzleName); + + int32_t W = S.WidthTiles; + int32_t H = S.HeightTiles; + ImGui::PushID("width"); + ImGui::SetNextItemWidth(40); + if (ImGui::DragInt("", &W, 0.3f, 0, Config::MaxPuzzleSizeTiles)) + { + S.WidthTiles = uint8_t(W); + dataChanged = true; + } + ImGui::PopID(); + ImGui::SameLine(); + ImGui::Text("x"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(40); + ImGui::PushID("height"); + if (ImGui::DragInt("", &H, 0.3f, 0, Config::MaxPuzzleSizeTiles)) + { + S.HeightTiles = uint8_t(H); + dataChanged = true; + } + ImGui::PopID(); + } + ImGui::End(); + + if (dataChanged) + { + char path[128]{0}; + bx::snprintf(path, sizeof(path), "game/data/%s.pzl", PuzzleName); + SerializeStruct(&S, sizeof(S), path); + } + + return isVisible; + } } // namespace Puzzle diff --git a/src/game/Puzzle.h b/src/game/Puzzle.h index c776fe1..7803c68 100644 --- a/src/game/Puzzle.h +++ b/src/game/Puzzle.h @@ -1,6 +1,9 @@ #pragma once +#include "Serial.h" #include +#include "../../gen/Generated.h" + namespace Puzzle { struct Config @@ -104,6 +107,9 @@ namespace Puzzle struct PuzzleData { + SER_HEADER(1, "PZZL"); + Generated::PuzzleData S; + uint32_t AvailableCardCount = 0; PuzzleCardStack AvailableCards[Config::MaxAvailableStacks]; uint32_t PlacedCardCount = 0; @@ -117,6 +123,9 @@ namespace Puzzle const PuzzleNode& GetNodeAt(PuzPos pos) const; PuzzleElementType GetElementAt(ElemPos pos) const; + + char PuzzleName[32]{"Unnamed"}; + bool RenderDebugUI(); }; struct PuzzleSolver diff --git a/src/game/Serial.cpp b/src/game/Serial.cpp new file mode 100644 index 0000000..fc97eb1 --- /dev/null +++ b/src/game/Serial.cpp @@ -0,0 +1,32 @@ +#include "Global.h" +#include "Log.h" +#include "Serial.h" +#include "bx/bx.h" +#include "bx/file.h" +#include "bx/filepath.h" + +void SerializeStruct(void* data, uint64_t size, const char* path) +{ + bx::Error err; + bx::FilePath filePath{path}; + bx::FileWriter writer; + if (writer.open(filePath, false, &err)) + { + if (!writer.write(data, size, &err)) + { + LOG_ERROR("Failed to write to file %s: %s", path, err.getMessage().getCPtr()); + writer.close(); + return; + } + } + else + { + LOG_ERROR("Failed to open file %s: %s", path, err.getMessage().getCPtr()); + return; + } + LOG("Successful serialization"); +} + +void DeserializeStruct(void* data, SerializationHeader& expectedHeader, const char* path) +{ +} diff --git a/src/game/Serial.h b/src/game/Serial.h new file mode 100644 index 0000000..49899b4 --- /dev/null +++ b/src/game/Serial.h @@ -0,0 +1,32 @@ +#include + +struct SerializationHeader +{ + uint8_t _ID0 = 0; + uint8_t _ID1 = 0; + uint8_t _ID2 = 0; + uint8_t _ID3 = 0; + uint8_t HeaderVersion = 0; + uint8_t VersionNum = 0; + uint8_t Reserved0 = 0; + uint8_t Reserved1 = 0; +}; + +#define SER_HEADER(Version, FCC) \ + SerializationHeader __Header{ \ + FCC[0], \ + FCC[1], \ + FCC[2], \ + FCC[3], \ + 1, \ + Version, \ + 0, \ + 0, \ + }; \ + bool VersionMatches(uint8_t headerVersion, uint8_t versionNum) \ + { \ + return headerVersion == 1 && versionNum == Version; \ + } + +void SerializeStruct(void* data, uint64_t size, const char* path); +void DeserializeStruct(void* data, SerializationHeader& expectedHeader, const char* path); diff --git a/src/game/mini.def b/src/game/mini.def new file mode 100644 index 0000000..f3b60b9 --- /dev/null +++ b/src/game/mini.def @@ -0,0 +1,8 @@ +type PuzzleData +{ + u8 WidthTiles + u8 HeightTiles + u32 AvailableCardCount + u32 PlacedCardCount + u32 GoalPositionCount +} diff --git a/src/game/rendering/Rendering.cpp b/src/game/rendering/Rendering.cpp index e5ea765..2412779 100644 --- a/src/game/rendering/Rendering.cpp +++ b/src/game/rendering/Rendering.cpp @@ -390,9 +390,9 @@ namespace Game inst.Time.StartTime = bx::getHPCounter(); } - if (inst.ImguiIniSize > 0) + if (inst.DebugData.ImguiIniSize > 0) { - ImGui::LoadIniSettingsFromMemory(inst.ImguiIni, inst.ImguiIniSize); + ImGui::LoadIniSettingsFromMemory(inst.DebugData.ImguiIni, inst.DebugData.ImguiIniSize); } else { @@ -453,6 +453,8 @@ namespace Game ImGui_ImplSDL3_NewFrame(); ImGui::DockSpaceOverViewport(0, 0, ImGuiDockNodeFlags_PassthruCentralNode); + auto& level = GetInstance().GameLevel; + auto& debug = GetInstance().DebugData; if (UIVisible == UIVisibilityState::Debug) { if (ImGui::Begin("Rendering")) @@ -500,12 +502,39 @@ namespace Game { ImGui::Image(DitherTextures.RampTex.idx, {BX_COUNTOF(DitherTextures.BrightnessRamp), 8}); } - Vec3 quadPos = GetInstance().GameLevel.UIQuads.Get({0}).EData.Transform.GetPosition(); + Vec3 quadPos = level.UIQuads.Get({0}).EData.Transform.GetPosition(); ImGui::Text("%f %f %f", quadPos.x, quadPos.y, quadPos.z); ImGui::Text("Shader log:"); ImGui::TextWrapped("%s", GetShared().Dev.ShaderLog); } ImGui::End(); + if (ImGui::Begin("Puzzles")) + { + ImGui::Text("List"); + for (int32_t i = 0; i < BX_COUNTOF(level.Puzzles); ++i) + { + auto& puzzleData = level.Puzzles[i].Data; + bool isSelected = debug.SelectedDebugLevel == i; + ImGui::PushID("selectable"); + if (ImGui::Selectable(puzzleData.PuzzleName, isSelected)) + { + debug.SelectedDebugLevel = isSelected ? UINT16_MAX : i; + } + ImGui::PopID(); + if (isSelected) + { + ImGui::PushID("edit field"); + ImGui::InputText("", puzzleData.PuzzleName, sizeof(Puzzle::PuzzleData::PuzzleName)); + ImGui::PopID(); + + if (!puzzleData.RenderDebugUI()) + { + debug.SelectedDebugLevel = UINT16_MAX; + } + } + } + } + ImGui::End(); } GetInstance().GameLevel.Update(); @@ -536,10 +565,10 @@ namespace Game { LOG("--- RENDERING_SHUTDOWN ---"); ImGui::SaveIniSettingsToDisk("imgui.ini"); - const char* iniData = ImGui::SaveIniSettingsToMemory(reinterpret_cast(&GetInstance().ImguiIniSize)); - assert(GetInstance().ImguiIniSize <= BX_COUNTOF(GameInstance::ImguiIni)); - bx::memCopy( - GetInstance().ImguiIni, iniData, bx::min(GetInstance().ImguiIniSize, BX_COUNTOF(GameInstance::ImguiIni))); + auto& debug = GetInstance().DebugData; + const char* iniData = ImGui::SaveIniSettingsToMemory(reinterpret_cast(&debug.ImguiIniSize)); + assert(debug.ImguiIniSize <= BX_COUNTOF(InstanceDebugData::ImguiIni)); + bx::memCopy(debug.ImguiIni, iniData, bx::min(debug.ImguiIniSize, BX_COUNTOF(InstanceDebugData::ImguiIni))); ImGui_ImplSDL3_Shutdown(); imguiDestroy(); bgfx::shutdown(); diff --git a/src/gen/Def.h b/src/gen/Def.h new file mode 100644 index 0000000..8b14fbe --- /dev/null +++ b/src/gen/Def.h @@ -0,0 +1,2 @@ +#pragma once +#include diff --git a/src/gen/Generated.h b/src/gen/Generated.h new file mode 100644 index 0000000..99f0ef5 --- /dev/null +++ b/src/gen/Generated.h @@ -0,0 +1,14 @@ +#pragma once +#include "Def.h" + +namespace Generated +{ + struct PuzzleData + { + uint8_t WidthTiles = {}; + uint8_t HeightTiles = {}; + uint32_t AvailableCardCount = {}; + uint32_t PlacedCardCount = {}; + uint32_t GoalPositionCount = {}; + }; +}