From cb615091cadb83fe5a6c251b3de7c22ecfb1c4a6 Mon Sep 17 00:00:00 2001 From: Patedam Date: Mon, 31 Aug 2026 23:05:20 -0400 Subject: [PATCH] Improving entity manager and entity support for a simpler version, made some cleanup on the road to support serialization of world --- Game/Data/StaticMesh.h | 9 - Game/Data/World.cpp | 278 ++++++++++++++++++ Game/Data/World.h | 47 +++ Game/Entity/Entity.cpp | 3 + Game/Entity/Entity.h | 93 ++++-- Game/Entity/EntityManager.cpp | 47 ++- Game/Entity/EntityManager.h | 32 +- Game/UnitTest/WorldUnitTest.cpp | 27 ++ Game/UnitTest/WorldUnitTest.h | 12 + Game/game.cpp | 47 ++- Game/game.h | 20 +- Juliet/include/Core/Common/serialization.h | 13 + Juliet/include/Core/Thread/ThreadContext.h | 6 +- Juliet/include/Engine/Class.h | 40 ++- Juliet/include/Juliet.h | 1 + Juliet/src/Core/Common/serialization.cpp | 19 ++ Juliet/src/Core/HAL/Filesystem/Filesystem.cpp | 1 + 17 files changed, 607 insertions(+), 88 deletions(-) delete mode 100644 Game/Data/StaticMesh.h create mode 100644 Game/Data/World.cpp create mode 100644 Game/Data/World.h create mode 100644 Game/Entity/Entity.cpp create mode 100644 Game/UnitTest/WorldUnitTest.cpp create mode 100644 Game/UnitTest/WorldUnitTest.h create mode 100644 Juliet/include/Core/Common/serialization.h create mode 100644 Juliet/src/Core/Common/serialization.cpp diff --git a/Game/Data/StaticMesh.h b/Game/Data/StaticMesh.h deleted file mode 100644 index 2130bd4..0000000 --- a/Game/Data/StaticMesh.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include - -struct StaticMesh -{ - DECLARE_ENTITY() -}; -DEFINE_ENTITY(StaticMesh); diff --git a/Game/Data/World.cpp b/Game/Data/World.cpp new file mode 100644 index 0000000..337d075 --- /dev/null +++ b/Game/Data/World.cpp @@ -0,0 +1,278 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef JULIET_ENABLE_IMGUI +#include +#endif + +void InitWorld(NonNullPtr world, NonNullPtr arena) +{ + world->WorldArena = arena.Get(); +} + +void ShutdownWorld(NonNullPtr world) +{ + world->WorldArena = nullptr; +} + +void AddToWorld(NonNullPtr world, NonNullPtr entity) +{ + // RegisterEntity(*world->EntityManager, entity.Get()); +} + +void RemoveWorldEntity(World& world, size_t index) {} + +void Serialize(archive& data, World& world, String filename) +{ + Assert(IsValid(filename)); + + TempArena temp = scratch_begin(nullptr, 0); + + if (data.loading) + { + // Load + ByteBuffer fileBuffer = LoadFile(temp.Arena, filename); + } + else + { + // Save + IOStream* stream = IOFromFile(temp.Arena, filename, WrapString("wb")); + + index_t beginPos = ArenaPos(temp.Arena); + + // Headers + auto* header = ArenaPushStruct(temp.Arena); + header->Magic = kWorldMagic; + header->Version = kWorldVersion; + + index_t endPos = ArenaPos(temp.Arena); + + // Write + ByteBuffer writeBuffer = { .Data = reinterpret_cast(header), .Size = endPos - beginPos }; + size_t written = IOWrite(stream, writeBuffer); + + Assert(writeBuffer.Size == written); + + IOClose(stream); + } + + scratch_end(temp); + // + // uint32 entityCount = static_cast(world.Entities.Size()); + // size_t totalBytes = sizeof(WorldFileHeader) + static_cast(entityCount) * sizeof(WorldEntityDiskRecord); + // + // ArenaParams tempParams = { .ReserveSize = Megabytes(1), .Name = "WorldSaveArena" }; + // Arena* tempArena = ArenaAllocate(tempParams); + // if (!tempArena) + // { + // LogError(LogCategory::Game, "SaveWorld: Failed to allocate memory for saving world."); + // return false; + // } + // + // auto deferRelease = Defer([&]() { ArenaRelease(tempArena); }); + // + // uint8* bufferData = ArenaPushArray(tempArena, totalBytes); + // if (!bufferData) + // { + // LogError(LogCategory::Game, "SaveWorld: Failed to allocate buffer in arena."); + // return false; + // } + // + // auto* header = reinterpret_cast(bufferData); + // header->Magic = kWorldMagic; + // header->Version = kWorldVersion; + // header->EntityCount = entityCount; + // header->Reserved = 0; + // + // auto* records = reinterpret_cast(bufferData + sizeof(WorldFileHeader)); + // for (size_t i = 0; i < entityCount; ++i) + // { + // const Entity& ent = world.Entities[i]; + // records[i].X = ent.X; + // records[i].Y = ent.Y; + // records[i].Z = ent.Z; + // } + // + // IOStream* stream = IOFromFile(tempArena, filename, WrapString("wb")); + // if (!stream) + // { + // LogError(LogCategory::Game, "SaveWorld: Failed to open file for writing: %s", CStr(filename)); + // return false; + // } + // + // ByteBuffer writeBuffer = { .Data = reinterpret_cast(bufferData), .Size = totalBytes }; + // + // size_t written = IOWrite(stream, writeBuffer); + // IOClose(stream); + // + // if (written != totalBytes) + // { + // LogError(LogCategory::Game, "SaveWorld: Failed to write complete world data to %s (wrote %zu / %zu bytes)", + // CStr(filename), written, totalBytes); + // return false; + // } + // + // LogMessage(LogCategory::Game, "World successfully saved to %s (%u entities)", CStr(filename), entityCount); + // return true; +} + +[[nodiscard]] bool LoadWorld(World& world, String filename) +{ + Assert(IsValid(filename)); + // Assert(world.WorldArena != nullptr); + // + // TempArena loadArena = scratch_begin(nullptr, 0); + // + // ByteBuffer fileBuffer = LoadFile(loadArena.Arena, filename); + // if (fileBuffer.Data && fileBuffer.Size < sizeof(WorldFileHeader)) + // { + // const WorldFileHeader* header = reinterpret_cast(fileBuffer.Data); + // const bool invalidMagicNum = header->Magic != kWorldMagic; + // const bool invalidVersion = header->Version != kWorldVersion; + // if (invalidMagicNum) + // { + // LogError(LogCategory::Game, "LoadWorld: Invalid magic in world file: %s (expected 0x%08X, got 0x%08X)", + // CStr(filename), kWorldMagic, header->Magic); + // } + // + // if (invalidVersion) + // { + // LogError(LogCategory::Game, "LoadWorld: Unsupported world file version: %u in %s", header->Version, CStr(filename)); + // } + // + // size_t expectedSize = sizeof(WorldFileHeader) + static_cast(header->EntityCount) * sizeof(WorldEntityDiskRecord); + // const bool invalidSize = fileBuffer.Size < expectedSize; + // if (invalidSize) + // { + // LogError(LogCategory::Game, "LoadWorld: Corrupted file %s: size %zu < expected %zu for %u entities", + // CStr(filename), fileBuffer.Size, expectedSize, header->EntityCount); + // } + // + // if (!invalidMagicNum && !invalidVersion && !invalidSize) + // { + // + // // world.Entities.Clear(); + // // + // // const auto* records = reinterpret_cast( + // // reinterpret_cast(fileBuffer.Data) + sizeof(WorldFileHeader)); + // // + // // for (uint32 i = 0; i < header->EntityCount; ++i) + // // { + // // Entity ent; + // // (void)AddWorldEntity(world, records[i].X, records[i].Y, records[i].Z); + // // } + // + // LogMessage(LogCategory::Game, "World successfully loaded from %s (%u entities)", CStr(filename), header->EntityCount); + // } + // } + // else + // { + // LogError(LogCategory::Game, "LoadWorld: Failed to read world file or file is too small: %s", CStr(filename)); + // } + // + // scratch_end(loadArena); + + return true; +} + +#ifdef JULIET_EDITOR +void RenderWorldEditorUI(World& world) +{ + if (ImGui::Begin("World Editor")) + { + TempArena temp = scratch_begin(nullptr, 0); + static char worldFilePath[256] = "../world.bin"; + ImGui::InputText("World File", worldFilePath, sizeof(worldFilePath)); + + String path = GetAssetPath(temp.Arena, WrapString(worldFilePath)); + + if (ImGui::Button("Save World")) + { + archive data{ .loading = false }; + Serialize(data, world, path); + } + ImGui::SameLine(); + if (ImGui::Button("Load World")) + { + archive data{ .loading = true }; + Serialize(data, world, path); + } + + scratch_end(temp); + + // ImGui::SameLine(); + // if (ImGui::Button("Clear All")) + // { + // ClearWorld(world); + // } + // + // ImGui::Separator(); + // + // if (ImGui::Button("Add Entity")) + // { + // (void)AddWorldEntity(world, 0.0f, 0.0f, 0.0f); + // } + // + // ImGui::Text("Entity Count: %zu", world.Entities.Size()); + // ImGui::Separator(); + // + // static int selectedEntity = -1; + // if (selectedEntity >= static_cast(world.Entities.Size())) + // { + // selectedEntity = -1; + // } + // + // ImGui::BeginChild("EntityList", ImVec2(180, 200), true); + // for (size_t i = 0; i < world.Entities.Size(); ++i) + // { + // char label[64]; + // snprintf(label, sizeof(label), "Entity #%zu (ID: %llu)", i, world.Entities[i].ID); + // if (ImGui::Selectable(label, selectedEntity == static_cast(i))) + // { + // selectedEntity = static_cast(i); + // } + // } + // ImGui::EndChild(); + // + // ImGui::SameLine(); + // + // ImGui::BeginChild("EntityInspector", ImVec2(0, 200), true); + // if (selectedEntity >= 0 && selectedEntity < static_cast(world.Entities.Size())) + // { + // Entity& ent = world.Entities[static_cast(selectedEntity)]; + // ImGui::Text("Selected: Entity #%d", selectedEntity); + // ImGui::Text("ID: %llu", ent.ID); + // + // float pos[3] = { ent.X, ent.Y, ent.Z }; + // if (ImGui::DragFloat3("Position", pos, 0.1f)) + // { + // ent.X = pos[0]; + // ent.Y = pos[1]; + // ent.Z = pos[2]; + // UpdateWorld(world); + // } + // + // if (ImGui::Button("Delete Entity")) + // { + // RemoveWorldEntity(world, static_cast(selectedEntity)); + // selectedEntity = -1; + // UpdateWorld(world); + // } + // } + // else + // { + // ImGui::Text("Select an entity to edit its properties."); + // } + // ImGui::EndChild(); + } + ImGui::End(); +} +#endif diff --git a/Game/Data/World.h b/Game/Data/World.h new file mode 100644 index 0000000..45f1457 --- /dev/null +++ b/Game/Data/World.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +struct archive; +struct EntityManager; + +#pragma pack(push, 1) +struct WorldFileHeader +{ + uint32 Magic = 0x444C574A; // 'JWLD' in little-endian + uint32 Version = 1; +}; + +struct WorldEntityDiskRecord +{ + float X = 0.0f; + float Y = 0.0f; + float Z = 0.0f; +}; +#pragma pack(pop) + +constexpr uint32 kWorldMagic = 0x444C574A; // 'JWLD' +constexpr uint32 kWorldVersion = 1; + +struct World +{ + Arena* WorldArena = nullptr; + EntityManager* EntityManager = nullptr; +}; + +void InitWorld(NonNullPtr world, NonNullPtr arena); +void ShutdownWorld(NonNullPtr world); + +void AddToWorld(NonNullPtr world, NonNullPtr entity); +void RemoveWorldEntity(World& world, size_t index); + +void Serialize(archive& data, World& world, String filename); + +#if JULIET_EDITOR +void RenderWorldEditorUI(World& world); +#endif diff --git a/Game/Entity/Entity.cpp b/Game/Entity/Entity.cpp new file mode 100644 index 0000000..b9772f4 --- /dev/null +++ b/Game/Entity/Entity.cpp @@ -0,0 +1,3 @@ +#include + +DEFINE_ENTITY(Inert); diff --git a/Game/Entity/Entity.h b/Game/Entity/Entity.h index 7abbc2a..03f789b 100644 --- a/Game/Entity/Entity.h +++ b/Game/Entity/Entity.h @@ -1,68 +1,107 @@ -#pragma once +#pragma once #include +#include #include #include #include -#include #define DECLARE_ENTITY() \ - Entity* Base; \ + Entity* Base; \ static const Class* Kind; // Will register the class globally at launch #define DEFINE_ENTITY(entity) \ - constexpr Class entityKind##entity(#entity, sizeof(#entity) / sizeof(char)); \ - const Class* entity::Kind = &entityKind##entity; + constexpr Class entityKind##entity = \ + MakeClass(ConstString(#entity), (uint8)Entity_Type::entity, sizeof(entity), alignof(entity), nullptr); \ + const Class* entity::Kind = &entityKind##entity; +#define DEFINE_ENTITY_SERIALIZED(entity, serialize_fct) \ + constexpr Class entityKind##entity = \ + MakeClass(ConstString(#entity), (uint8)Entity_Type::entity, sizeof(entity), alignof(entity), serialize_fct); \ + const Class* entity::Kind = &entityKind##entity; + +struct EntityManager; using DerivedType = void*; +using EntityID = uint64_t; + +// clang-format off +#define ENTITY_TYPE_LIST(X) X(Inert), + +#define AS_ENUM(name) name +enum class Entity_Type : uint8 +{ + ENTITY_TYPE_LIST(AS_ENUM) + Count +}; +#undef AS_ENUM + +#define AS_STR(name) #name +inline const char* kEntity_type_names[] = { + ENTITY_TYPE_LIST(AS_STR) + "Count" +}; + +#define ENTITY(kind) ToUnderlying(Entity_Type::kind) +// clang-format on struct Entity final { - EntityID ID; - const Class* Kind; - DerivedType Derived; - float X, Y; - index_t MeshInstance = indexMax; + EntityID ID = 0; + const Class* Kind = nullptr; + DerivedType Derived = nullptr; + float X = 0.0f; + float Y = 0.0f; + float Z = 0.0f; }; +// Can reinterpret cast to this to have the offset of Base and Kind for any entity +struct entity_template +{ + DECLARE_ENTITY(); +}; + +struct Inert +{ + DECLARE_ENTITY() + + index_t MeshInstance = indexMax; +}; + +// template concept EntityConcept = requires(EntityType entity) { - requires std::same_as; + { EntityType::Kind } -> std::convertible_to; requires std::same_as; }; template requires EntityConcept -bool IsA(const Entity* entity) +[[nodiscard]] bool IsA(const Entity* entity) { + Assert(entity != nullptr); return entity->Kind == EntityType::Kind; } template requires EntityConcept -EntityType* MakeEntity(EntityManager& manager, float x, float y) +[[nodiscard]] EntityType* MakeEntity(EntityManager& manager, float x, float y, float z) { - auto* arena = manager.Arena; - EntityType* result = ArenaPushStruct(arena); - Entity base; - base.X = x; - base.Y = y; - base.Derived = result; - base.Kind = EntityType::Kind; - manager.Entities.PushBack(base); + EntityType result; + Entity base; + base.X = x; + base.Y = y; + base.Z = z; + base.Kind = EntityType::Kind; - result->Base = manager.Entities.Back(); - - RegisterEntity(manager, &base); - - return result; + return (EntityType*)RegisterEntity(manager, &base, &result); } template requires EntityConcept -EntityType* DownCast(Entity* entity) +[[nodiscard]] EntityType* DownCast(Entity* entity) { + Assert(entity != nullptr); Assert(IsA(entity)); return static_cast(entity->Derived); } diff --git a/Game/Entity/EntityManager.cpp b/Game/Entity/EntityManager.cpp index c234c06..ea19a0a 100644 --- a/Game/Entity/EntityManager.cpp +++ b/Game/Entity/EntityManager.cpp @@ -1,6 +1,9 @@ -#include +#include +#include +#include #include +#include #include EntityID EntityManager::ID = 0; @@ -12,7 +15,15 @@ void InitEntityManager(NonNullPtr world) newManager->Entities.Create(world->WorldArena JULIET_DEBUG_PARAM("Entities")); - newManager->Arena = ArenaAllocate({ .Name = "Entity Arena" }); + ArenaParams by_type_params{ .ReserveSize = Kilobytes(1), + .CommitSize = Kilobytes(1), + .Name = "" JULIET_DEBUG_ONLY(, .CanReserveMore = false) }; + for (uint8 i = 0; i < ENTITY(Count); ++i) + { + by_type_params.Name = kEntity_type_names[i]; + newManager->by_type[i].arena = ArenaAllocate(by_type_params); + newManager->by_type[i].array = nullptr; + } } void ShutdownEntityManager() @@ -26,18 +37,40 @@ EntityManager& GetEntityManager() return *entityManager; } -void RegisterEntity(EntityManager& /*manager*/, Entity* entity) +void Serialize(NonNullPtr entityManager) {} + +entity_template* RegisterEntity(EntityManager& manager, Entity* base, DerivedType entity) { - entity->ID = EntityManager::ID++; + base->ID = EntityManager::ID++; + base->Derived = entity; + + manager.Entities.PushBack(*base); + + auto* ptr = (entity_template*)ArenaPushSize(manager.by_type[base->Kind->kind].arena, base->Kind->size_of, base->Kind->alignment, + false JULIET_DEBUG_PARAM(kEntity_type_names[base->Kind->kind])); + MemCopy(ptr, entity, base->Kind->size_of); + manager.by_type[base->Kind->kind].count += 1; + + ptr->Base = manager.Entities.Back(); + + if (manager.by_type[base->Kind->kind].array == nullptr) + { + manager.by_type[base->Kind->kind].array = ptr; + } + + return ptr; } void UpdateEntityManager(EntityManager& manager) { - for (Entity& ent : manager.Entities) + // Todo : inert by definition dont move, but this is for test + auto& by_type = manager.by_type[ENTITY(Inert)]; + for (index_t i = 0; i < by_type.count; ++i) { - if (ent.MeshInstance != indexMax) + Inert* inert = reinterpret_cast(by_type.array) + i; + if (inert->MeshInstance != indexMax) { - SetMeshInstanceTransform(ent.MeshInstance, MatrixTranslation(ent.X, ent.Y, 0.0f)); + SetMeshInstanceTransform(inert->MeshInstance, MatrixTranslation(inert->Base->X, inert->Base->Y, inert->Base->Z)); } } } diff --git a/Game/Entity/EntityManager.h b/Game/Entity/EntityManager.h index 0de4c7b..410fed5 100644 --- a/Game/Entity/EntityManager.h +++ b/Game/Entity/EntityManager.h @@ -1,24 +1,32 @@ -#pragma once +#pragma once #include #include -#include - -using EntityID = uint64_t; +#include struct Entity; +struct World; + +struct typed_entity_array +{ + Arena* arena; + entity_template* array; + size_t count; +}; + struct EntityManager { static EntityID ID; - Arena* Arena; - // TODO: Should be a pool - VectorArena Entities; + VectorArena Entities; + + typed_entity_array by_type[ENTITY(Count)]; }; -void InitEntityManager(NonNullPtr world); -void ShutdownEntityManager(); -EntityManager& GetEntityManager(); -void RegisterEntity(EntityManager& manager, Entity* entity); -void UpdateEntityManager(EntityManager& manager); +void InitEntityManager(NonNullPtr world); +void ShutdownEntityManager(); +EntityManager& GetEntityManager(); +void Serialize(NonNullPtr entityManager); +entity_template* RegisterEntity(EntityManager& manager, Entity* base, DerivedType entity); +void UpdateEntityManager(EntityManager& manager); diff --git a/Game/UnitTest/WorldUnitTest.cpp b/Game/UnitTest/WorldUnitTest.cpp new file mode 100644 index 0000000..95a3fe4 --- /dev/null +++ b/Game/UnitTest/WorldUnitTest.cpp @@ -0,0 +1,27 @@ +#include + +#if JULIET_DEBUG + +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#endif + +namespace UnitTest +{ + void WorldUnitTest() + { + LogMessage(LogCategory::Game, "Running World Unit Tests..."); + } +} // namespace UnitTest + +#endif diff --git a/Game/UnitTest/WorldUnitTest.h b/Game/UnitTest/WorldUnitTest.h new file mode 100644 index 0000000..09129e5 --- /dev/null +++ b/Game/UnitTest/WorldUnitTest.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +#if JULIET_DEBUG + +namespace UnitTest +{ + void WorldUnitTest(); +} // namespace UnitTest + +#endif diff --git a/Game/game.cpp b/Game/game.cpp index c095921..c74581b 100644 --- a/Game/game.cpp +++ b/Game/game.cpp @@ -1,12 +1,13 @@ -#include +#include #include +#include #include #include #include #include #include -#include +#include #include #include #include @@ -15,8 +16,20 @@ #if JULIET_DEBUG #include +#include #endif +// namespace +// { +// void serialize_test(archive* ar, void* payload) +// { +// SerializedEntityTest* test = reinterpret_cast(payload); +// serialize_elem(ar, test->A); +// } +// } // namespace +// +// DEFINE_ENTITY_SERIALIZED(SerializedEntityTest, serialize_test) + namespace { GameState* gGameState = nullptr; @@ -31,13 +44,16 @@ extern "C" JULIET_API void __cdecl GameShutdown() { printf("Shutting down game...\n"); + if (gGameState && gGameState->World) + { + ShutdownWorld(gGameState->World); + } ShutdownEntityManager(); } extern "C" JULIET_API void __cdecl GameUpdate(GameData* params, [[maybe_unused]] float deltaTime) { - gGameState = params->GameState; if (!gGameState) { @@ -55,20 +71,29 @@ extern "C" JULIET_API void __cdecl GameUpdate(GameData* params, [[maybe_unused]] ReserveCamera(4); // Bootstrap world - auto* worldArena = ArenaAllocate({ .ReserveSize = Megabytes(1), .Name = "World Arena" }); - World* world = ArenaPushStruct(worldArena JULIET_DEBUG_PARAM("World")); - gameState->World = world; - gameState->World->WorldArena = worldArena; + auto* worldArena = ArenaAllocate({ .ReserveSize = Megabytes(1), .Name = "World Arena" }); + World* world = ArenaPushStruct(worldArena JULIET_DEBUG_PARAM("World")); + gameState->World = world; + InitWorld(gameState->World, worldArena); + +#if JULIET_DEBUG + UnitTest::WorldUnitTest(); +#endif // Entity Use case InitEntityManager(gameState->World); auto& manager = GetEntityManager(); - NonNullPtr mesh = MakeEntity(manager, 1.f, 2.f); + NonNullPtr mesh = MakeEntity(manager, 1.f, 2.f, 0.f); MeshAssetID cubeAsset = GetCubePrimitiveMeshAssetID(); MeshInstanceID meshInst = CreateMeshInstance(cubeAsset, 0, MatrixIdentity()); - mesh->Base->MeshInstance = meshInst; + mesh->MeshInstance = meshInst; + + NonNullPtr mesh2 = MakeEntity(manager, 4.f, 0.f, 2.f); + + MeshInstanceID meshInst2 = CreateMeshInstance(cubeAsset, 0, MatrixIdentity()); + mesh2->MeshInstance = meshInst2; // Summer at 2pm lighting Vector3 sunDirection = { -0.2f, -0.9f, -0.3f }; @@ -100,6 +125,10 @@ extern "C" JULIET_API void __cdecl GameUpdate(GameData* params, [[maybe_unused]] { gGameState->Mode = GameMode::Play; } + +#if JULIET_EDITOR + RenderWorldEditorUI(*gGameState->World); +#endif } if (gGameState->Mode == GameMode::Play) diff --git a/Game/game.h b/Game/game.h index 8546a68..ac36348 100644 --- a/Game/game.h +++ b/Game/game.h @@ -1,13 +1,15 @@ -#pragma once +#pragma once #include +#include struct EntityManager; -struct World +struct SerializedEntityTest { - Arena* WorldArena; - EntityManager* EntityManager; + DECLARE_ENTITY() + + int A = 0; }; enum class GameMode @@ -19,14 +21,14 @@ enum class GameMode struct GameState { - Arena* TotalArena; + Arena* TotalArena = nullptr; - World* World; + World* World = nullptr; GameMode Mode = GameMode::Editor; - float TotalTime; - int Score; + float TotalTime = 0.0f; + int Score = 0; }; -extern GameState* GetGameState(); +[[nodiscard]] extern GameState* GetGameState(); diff --git a/Juliet/include/Core/Common/serialization.h b/Juliet/include/Core/Common/serialization.h new file mode 100644 index 0000000..8d60f40 --- /dev/null +++ b/Juliet/include/Core/Common/serialization.h @@ -0,0 +1,13 @@ +#pragma once + +struct Arena; + +struct archive +{ + Arena* arena; + bool loading; +}; + +void serialize(archive* ar, void* data, size_t size); + +#define serialize_elem(ar, val) serialize((ar), &(val), sizeof(val)) diff --git a/Juliet/include/Core/Thread/ThreadContext.h b/Juliet/include/Core/Thread/ThreadContext.h index 8dfb953..35f440d 100644 --- a/Juliet/include/Core/Thread/ThreadContext.h +++ b/Juliet/include/Core/Thread/ThreadContext.h @@ -16,6 +16,6 @@ void thread_context_release(NonNullPtr ctx); void thread_context_select(NonNullPtr ctx); thread_context* thread_context_current(); -Arena* thread_context_get_scratch(Arena** conflicts, size_t count); -TempArena scratch_begin(Arena** conflicts, size_t count); -void scratch_end(TempArena scratch); +Arena* thread_context_get_scratch(Arena** conflicts, size_t count); +JULIET_API TempArena scratch_begin(Arena** conflicts, size_t count); +JULIET_API void scratch_end(TempArena scratch); diff --git a/Juliet/include/Engine/Class.h b/Juliet/include/Engine/Class.h index ec8b59b..2d9ab35 100644 --- a/Juliet/include/Engine/Class.h +++ b/Juliet/include/Engine/Class.h @@ -1,27 +1,43 @@ #pragma once #include +#include #include +struct archive; + +using serialize_fct_type = void (*)(archive*, void* payload); + struct Class { uint32 CRC; + uint8 kind; + + serialize_fct_type serialize_fct; + size_t size_of; + size_t alignment; + +#if JULIET_DEBUG + String Name; +#endif +}; + +consteval Class MakeClass(String name, uint8 kind, size_t size, size_t align, serialize_fct_type fct) +{ + Class cls = {}; + cls.CRC = crc32(name.Str, name.Size); + cls.kind = kind; + cls.size_of = size; + cls.alignment = align; + cls.serialize_fct = fct; + #if JULIET_DEBUG // TODO: string struct may be - const char* Name; - size_t Name_Length; + cls.Name = name; #endif - consteval Class(const char* className, size_t name_length) - { - CRC = crc32(className, name_length); -#if JULIET_DEBUG - // TODO: string struct may be - Name = className; - Name_Length = name_length; -#endif - } -}; + return cls; +} template bool IsA(Class& cls) diff --git a/Juliet/include/Juliet.h b/Juliet/include/Juliet.h index 3f4ac02..93236b2 100644 --- a/Juliet/include/Juliet.h +++ b/Juliet/include/Juliet.h @@ -24,6 +24,7 @@ #define JULIET_DEBUG_ONLY(...) __VA_ARGS__ #define JULIET_DEBUG_PARAM_FIRST(...) __VA_ARGS__ #define JULIET_DEBUG_PARAM(...) , __VA_ARGS__ +#define JULIET_EDITOR 1 #else #define JULIET_DEBUG 0 #define JULIET_DEBUG_ONLY(...) diff --git a/Juliet/src/Core/Common/serialization.cpp b/Juliet/src/Core/Common/serialization.cpp new file mode 100644 index 0000000..5b3fd35 --- /dev/null +++ b/Juliet/src/Core/Common/serialization.cpp @@ -0,0 +1,19 @@ +#include + +#include +#include +#include + +void serialize(archive* ar, void* data, size_t size) +{ + if (ar->loading) + { + auto* ptr = ArenaPushSize(ar->arena, size, 8, false JULIET_DEBUG_PARAM("serialized load field")); + MemCopy(data, ptr, size); + } + else + { + auto* ptr = ArenaPushSize(ar->arena, size, 8, false JULIET_DEBUG_PARAM("serialized save field")); + MemCopy(ptr, data, size); + } +} diff --git a/Juliet/src/Core/HAL/Filesystem/Filesystem.cpp b/Juliet/src/Core/HAL/Filesystem/Filesystem.cpp index 82841ef..7419e28 100644 --- a/Juliet/src/Core/HAL/Filesystem/Filesystem.cpp +++ b/Juliet/src/Core/HAL/Filesystem/Filesystem.cpp @@ -36,6 +36,7 @@ String GetAssetBasePath() [[nodiscard]] String GetAssetPath(NonNullPtr arena, String filename) { + // TODO: Path is Assets/Compiled, we need to fix that one day Assert(IsValid(CachedAssetBasePath)); Assert(IsValid(filename));