From a462575af4e6ae9d2084e91a3e6a7fe3953447a4 Mon Sep 17 00:00:00 2001 From: Patedam Date: Mon, 7 Sep 2026 16:58:57 -0400 Subject: [PATCH] ongoing refactor of serialization and various cleanup --- .agent/rules/coding-guidelines.md | 16 +- Game/Controller/ControllerUtils.h | 2 - Game/Data/World.cpp | 66 ++-- Game/Data/World.h | 8 +- Game/Entity/Entity.cpp | 29 +- Game/Entity/Entity.h | 52 ++- Game/Entity/EntityManager.cpp | 26 +- Game/Entity/EntityManager.h | 2 - .../01_Serialization_And_Text_Archive.md | 372 ++++++++---------- .../02_Entity_Allocation_And_Lifecycle.md | 72 ++-- .../Plans/03_Entity_ID_And_World_Directory.md | 43 +- Game/game.cpp | 2 +- .../Core/Application/ApplicationManager.h | 1 - Juliet/include/Core/Common/CoreTypes.h | 18 + Juliet/include/Core/Common/CoreUtils.h | 54 ++- Juliet/include/Core/Common/String.h | 8 +- Juliet/include/Core/Common/serialization.h | 122 +++++- Juliet/include/Core/HAL/Display/Display.h | 4 +- Juliet/include/Core/HAL/Keyboard/KeyCode.h | 2 - Juliet/include/Core/HAL/OS/OS.h | 3 - Juliet/include/Core/ImGui/ImGuiService.h | 1 - Juliet/include/Core/JulietInit.h | 2 - Juliet/include/Core/Logging/LogManager.h | 2 - Juliet/include/Core/Math/MathUtils.h | 3 - Juliet/include/Core/Math/Vector.h | 14 +- Juliet/include/Core/Memory/MemoryArena.h | 5 +- Juliet/include/Core/Memory/MemoryArenaDebug.h | 3 - Juliet/include/Core/Memory/Utils.h | 16 +- Juliet/include/Core/Networking/IPAddress.h | 2 - .../include/Core/Networking/NetworkPacket.h | 1 - Juliet/include/Core/PCH.h | 2 + Juliet/include/Core/Thread/ThreadContext.h | 1 - Juliet/include/Engine/Class.h | 32 +- Juliet/include/Graphics/GraphicsConfig.h | 4 - Juliet/include/Graphics/Mesh.h | 8 +- Juliet/src/Core/Common/String.cpp | 22 +- Juliet/src/Core/Common/serialization.cpp | 363 ++++++++++++++++- Juliet/src/Core/HAL/Display/Display.cpp | 3 +- Juliet/src/Core/HAL/Filesystem/Filesystem.cpp | 3 +- Juliet/src/Core/HAL/OS/Win32/Win32OS.cpp | 3 +- Juliet/src/Core/Math/MathRound.cpp | 3 +- Juliet/src/Core/Memory/MemoryArenaTests.cpp | 3 +- Juliet/src/Engine/class.cpp | 37 ++ 43 files changed, 952 insertions(+), 483 deletions(-) diff --git a/.agent/rules/coding-guidelines.md b/.agent/rules/coding-guidelines.md index e3934f1..5b00cff 100644 --- a/.agent/rules/coding-guidelines.md +++ b/.agent/rules/coding-guidelines.md @@ -7,10 +7,18 @@ use static_cast or reinterpret_cast but not parenthesis for casting. No exceptions Use [[nodiscard]] when risk of memory leak (anything returning pointer) auto is allowed but when its a pointer add the * and when reference adds the & -Member variable are CamelCase -Types are CamelCase -Functions are CamelCase. Add Assert to make sure all assumptions are good. Parameters of functions for example should be verified with Assert. Code should be self commented using proper variable names, types and functions. No need to add comments most of the time, unless the algorithm is very complex and hard to read. When creating a new system framework, make a unit test. To make the unit test we should not modify the framework code for special unit test case. -Always put braces for if,else,for,while etc. \ No newline at end of file +Always put braces for if,else,for,while etc. +Coding Style: +Note: The code base do not fully use those, if in doubt, use this style and not legacy style. +Types (Structs/Enums/Unions) : PascalCase. Exemple : Arena, String8, DbgTarget, EvalValue +Primitive Types : lower_snake_case. Exemple: uint8, uint16, uint32, uint64, size_t, index_t, int32... +Function Names: lower_snake_case. Exemple: str16_from_8. +Namespace: do not use c++ namespace, add PascalCase prefix to function names. Exemple: W32_do_something +Local Variables and parameters: lower_snake_case. Exemple: arena, str, data. +Struct members: lower_snake_case. Exemple: data, node, next. +global/static variables: lower_snake_case prefixed. Exemple: g_my_global. +const variables: lower_snake_case prefixed. Exemple: k_my_const_var. +Defines and Macros: ALL_CAPS_SNAKE. Exemple: MY_MACRO diff --git a/Game/Controller/ControllerUtils.h b/Game/Controller/ControllerUtils.h index d0318d0..328efae 100644 --- a/Game/Controller/ControllerUtils.h +++ b/Game/Controller/ControllerUtils.h @@ -1,6 +1,4 @@ #pragma once -#include - constexpr index_t kPlayCamera = 0; constexpr index_t kDebugCamera = 1; diff --git a/Game/Data/World.cpp b/Game/Data/World.cpp index e6e6443..b44ab72 100644 --- a/Game/Data/World.cpp +++ b/Game/Data/World.cpp @@ -24,14 +24,14 @@ void ShutdownWorld(NonNullPtr world) world->WorldArena = nullptr; } -void AddToWorld(NonNullPtr world, NonNullPtr entity) +void AddToWorld(NonNullPtr /*world*/, NonNullPtr /*entity*/) { // RegisterEntity(*world->EntityManager, entity.Get()); } -void RemoveWorldEntity(World& world, size_t index) {} +void RemoveWorldEntity(World& /*world*/, size_t /*index*/) {} -void Serialize(archive& ar, World& world, String filename) +void Serialize(Archive& ar, World& /*world*/, String filename) { Assert(IsValid(filename)); @@ -41,28 +41,33 @@ void Serialize(archive& ar, World& world, String filename) { // Load ByteBuffer fileBuffer = LoadFile(ar.arena, filename); - if (fileBuffer.Size >= sizeof(WorldFileHeader)) - { - ar.base_ptr = fileBuffer.Data; - WorldFileHeader header; - serialize_elem(ar, header); - Assert(header.Magic == kWorldMagic); - Assert(header.Version == kWorldVersion); + // TEST SERIALIZATION + ParsedArchive archive = tokenize_archive(ar.arena, fileBuffer); + // ArchivePropertyNode* property = find_property(&archive, "Position"_crc32); + audit_unconsumed_properties(&archive, ConstString("World")); - for (typed_entity_array& type : entityManager.by_type) - { - serialize_elem(ar, type.count); - - if (type.count > 0) - { - - // Unserialize the base entity to get informations - Entity entity; - serialize(ar, &entity); - } - } - } + // if (fileBuffer.Size >= sizeof(WorldFileHeader)) + // { + // ar.base_ptr = fileBuffer.Data; + // + // WorldFileHeader header; + // serialize_elem(ar, header); + // Assert(header.Magic == kWorldMagic); + // + // for (typed_entity_array& type : entityManager.by_type) + // { + // serialize_elem(ar, type.count); + // + // if (type.count > 0) + // { + // + // // Unserialize the base entity to get informations + // Entity entity; + // // serialize(ar, &entity); + // } + // } + // } } else { @@ -72,9 +77,8 @@ void Serialize(archive& ar, World& world, String filename) index_t beginPos = ArenaPos(ar.arena); // Headers - auto* header = ArenaPushStruct(ar.arena); - header->Magic = kWorldMagic; - header->Version = kWorldVersion; + auto* header = ArenaPushStruct(ar.arena); + header->Magic = kWorldMagic; ar.base_ptr = header; @@ -84,12 +88,12 @@ void Serialize(archive& ar, World& world, String filename) serialize_elem(ar, type.count); auto* element = type.array; uint8* rawElement = reinterpret_cast(element); - size_t stride = element->Base->Kind->size_of; + size_t stride = element->base->derived_kind->size_of; for (index_t idx = 0; idx < type.count; ++idx) { // Todo : utils // Get base entity from type - Entity* entity = reinterpret_cast(rawElement + (idx * stride))->Base; + Entity* entity = reinterpret_cast(rawElement + (idx * stride))->base; serialize(ar, entity); } } @@ -164,7 +168,7 @@ void Serialize(archive& ar, World& world, String filename) // return true; } -[[nodiscard]] bool LoadWorld(World& world, String filename) +[[nodiscard]] bool LoadWorld(World& /*world*/, String filename) { Assert(IsValid(filename)); // Assert(world.WorldArena != nullptr); @@ -236,13 +240,13 @@ void RenderWorldEditorUI(World& world) if (ImGui::Button("Save World")) { - archive data{ .arena = temp.Arena, .loading = false }; + Archive data = { .arena = temp.Arena, .loading = false }; Serialize(data, world, path); } ImGui::SameLine(); if (ImGui::Button("Load World")) { - archive data{ .arena = temp.Arena, .loading = true }; + Archive data{ .arena = temp.Arena, .loading = true }; Serialize(data, world, path); } diff --git a/Game/Data/World.h b/Game/Data/World.h index 9ba1ff4..8f8ce1f 100644 --- a/Game/Data/World.h +++ b/Game/Data/World.h @@ -1,20 +1,18 @@ #pragma once -#include #include #include #include #include #include -struct archive; +struct Archive; struct EntityManager; #pragma pack(push, 1) struct WorldFileHeader { - uint32 Magic = 0x444C574A; // 'JWLD' in little-endian - uint32 Version = 1; + uint32 Magic = 0x444C574A; // 'JWLD' in little-endian }; #pragma pack(pop) @@ -33,7 +31,7 @@ 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); +void Serialize(Archive& data, World& world, String filename); #if JULIET_EDITOR void RenderWorldEditorUI(World& world); diff --git a/Game/Entity/Entity.cpp b/Game/Entity/Entity.cpp index 2b8a011..320b546 100644 --- a/Game/Entity/Entity.cpp +++ b/Game/Entity/Entity.cpp @@ -2,24 +2,21 @@ #include -DEFINE_ENTITY(Inert); +DEFINE_CLASS_VERSIONED(Entity, 1, nullptr, nullptr) -void serialize(archive& ar, NonNullPtr entity) +DEFINE_ENTITY_VERSIONED(Inert, 1, nullptr) + +void serialize(Archive& ar, NonNullPtr entity) { - serialize_elem(ar, entity->ID); + // Entity fields + serialize(ar, Entity::kind, entity.Get()); - if (ar.loading) - { - serialize_elem(ar, entity->Kind->kind); - } - else - { - uint8 kind; - serialize_elem(ar, kind); - entity->Kind = kEntity_type_class_ptr[kind]; - } + SERIALIZE(ar, id, entity->ID); + SERIALIZE(ar, position, entity->position); - serialize_elem(ar, entity->X); - serialize_elem(ar, entity->Y); - serialize_elem(ar, entity->Z); + // Derived fields + if (entity->derived_kind != nullptr && entity->derived != nullptr) + { + serialize(ar, entity->derived_kind, entity->derived); + } } diff --git a/Game/Entity/Entity.h b/Game/Entity/Entity.h index a2f990c..8639980 100644 --- a/Game/Entity/Entity.h +++ b/Game/Entity/Entity.h @@ -2,24 +2,19 @@ #include #include +#include #include #include #include #define DECLARE_ENTITY() \ - Entity* Base; \ - static Class* Kind; + Entity* base; \ + static Class* kind; -// Will register the class globally at launch -#define DEFINE_ENTITY(entity) \ - Class entityKind##entity = \ - MakeClass(ConstString(#entity), (uint8)Entity_Type::entity, sizeof(entity), alignof(entity), nullptr); \ - Class* entity::Kind = &entityKind##entity; - -#define DEFINE_ENTITY_SERIALIZED(entity, serialize_fct) \ - Class entityKind##entity = \ - MakeClass(ConstString(#entity), (uint8)Entity_Type::entity, sizeof(entity), alignof(entity), serialize_fct); \ - Class* entity::Kind = &entityKind##entity; +#define DEFINE_ENTITY_VERSIONED(entity, version, serialize_fct) \ + constexpr Class entityKind##entity = MakeClass(ConstString(#entity), (uint8)Entity_Type::entity, (version), \ + &classKindEntity, sizeof(entity), alignof(entity), (serialize_fct)); \ + Class* entity::kind = const_cast(&entityKind##entity); struct EntityManager; using DerivedType = void*; @@ -27,12 +22,12 @@ using EntityID = uint64_t; struct Entity final { - EntityID ID = 0; - Class* Kind = nullptr; - DerivedType Derived = nullptr; - float X = 0.0f; - float Y = 0.0f; - float Z = 0.0f; + static Class* kind; + + EntityID ID = 0; + Class* derived_kind = nullptr; + DerivedType derived = nullptr; + Vector4 position = {}; }; struct Inert @@ -60,7 +55,7 @@ inline const char* kEntity_type_names[] = { }; #undef AS_STR -#define AS_CLASS(name) name::Kind +#define AS_CLASS(name) name::kind inline Class* kEntity_type_class_ptr[] { ENTITY_TYPE_LIST(AS_CLASS) @@ -80,8 +75,8 @@ struct entity_template // template concept EntityConcept = requires(EntityType entity) { - { EntityType::Kind } -> std::convertible_to; - requires std::same_as; + { EntityType::kind } -> std::convertible_to; + requires std::same_as; }; template @@ -89,7 +84,7 @@ template [[nodiscard]] bool IsA(const Entity* entity) { Assert(entity != nullptr); - return entity->Kind == EntityType::Kind; + return entity->derived_kind == EntityType::kind; } template @@ -98,10 +93,11 @@ template { EntityType result; Entity base; - base.X = x; - base.Y = y; - base.Z = z; - base.Kind = EntityType::Kind; + base.position.x = x; + base.position.y = y; + base.position.z = z; + base.position.w = 1.0f; + base.derived_kind = EntityType::kind; return (EntityType*)RegisterEntity(manager, &base, &result); } @@ -112,7 +108,7 @@ template { Assert(entity != nullptr); Assert(IsA(entity)); - return static_cast(entity->Derived); + return static_cast(entity->derived); } -void serialize(archive& ar, NonNullPtr entity); +void serialize(Archive& ar, NonNullPtr entity); diff --git a/Game/Entity/EntityManager.cpp b/Game/Entity/EntityManager.cpp index 3c0680e..83326fc 100644 --- a/Game/Entity/EntityManager.cpp +++ b/Game/Entity/EntityManager.cpp @@ -46,30 +46,26 @@ EntityManager& GetEntityManager() entity_template* RegisterEntity(EntityManager& manager, Entity* base, DerivedType entity) { base->ID = EntityManager::ID++; - base->Derived = entity; + 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; + auto* ptr = (entity_template*)ArenaPushSize(manager.by_type[base->derived_kind->kind].arena, + base->derived_kind->size_of, base->derived_kind->alignment, + false JULIET_DEBUG_PARAM(kEntity_type_names[base->derived_kind->kind])); + MemCopy(ptr, entity, base->derived_kind->size_of); + manager.by_type[base->derived_kind->kind].count += 1; - ptr->Base = manager.Entities.Back(); + ptr->base = manager.Entities.Back(); - if (manager.by_type[base->Kind->kind].array == nullptr) + if (manager.by_type[base->derived_kind->kind].array == nullptr) { - manager.by_type[base->Kind->kind].array = ptr; + manager.by_type[base->derived_kind->kind].array = ptr; } return ptr; } -void RegisterBaseEntity(EntityManager& manager, Entity&& base) -{ - GetEntityManager().Entities.PushBack(std::move(base)); -} - void UpdateEntityManager(EntityManager& manager) { // Todo : inert by definition dont move, but this is for test @@ -79,7 +75,9 @@ void UpdateEntityManager(EntityManager& manager) Inert* inert = reinterpret_cast(by_type.array) + i; if (inert->MeshInstance != indexMax) { - SetMeshInstanceTransform(inert->MeshInstance, MatrixTranslation(inert->Base->X, inert->Base->Y, inert->Base->Z)); + SetMeshInstanceTransform(inert->MeshInstance, + MatrixTranslation(inert->base->position.x, inert->base->position.y, + inert->base->position.z)); } } } diff --git a/Game/Entity/EntityManager.h b/Game/Entity/EntityManager.h index 33d56b1..2097535 100644 --- a/Game/Entity/EntityManager.h +++ b/Game/Entity/EntityManager.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include @@ -28,5 +27,4 @@ void InitEntityManager(NonNullPtr world); void ShutdownEntityManager(); EntityManager& GetEntityManager(); entity_template* RegisterEntity(EntityManager& manager, Entity* base, DerivedType entity); -void RegisterBaseEntity(EntityManager& manager, Entity&& base); void UpdateEntityManager(EntityManager& manager); diff --git a/Game/Plans/01_Serialization_And_Text_Archive.md b/Game/Plans/01_Serialization_And_Text_Archive.md index a57a087..3e0c0cd 100644 --- a/Game/Plans/01_Serialization_And_Text_Archive.md +++ b/Game/Plans/01_Serialization_And_Text_Archive.md @@ -24,7 +24,7 @@ The `.jasset` text archive framework is engineered to replace legacy binary blob - **Symmetric Single-Function Serialization**: A single `Serialize` implementation per entity or data structure handles both Save and Load paths, guaranteeing that write and read schemas never diverge. - **Graceful Forward/Backward Compatibility**: Missing keys during load automatically preserve struct default values. Unknown keys present in newer asset files are safely ignored without parse failure. - **Two-Tier Decoupled Versioning**: Core engine entity properties (`kEntityBaseVersion`) and derived gameplay class properties (`Class::Version`) are versioned independently. Engine-level updates never bump derived entity class versions. -- **Post-Serialization In-Place Migration**: Deprecated fields can be read through specialized primitives (`ReadDeprecated*`) to migrate legacy data structures in-place upon loading, producing cleaned, modern schemas on subsequent saves. +- **In-Place Schema Migration**: Deprecated properties no longer present in modern structs can be read during loading into local stack variables using standard serialization (`serialize` / `SERIALIZE`) guarded by version checks (`if (ar.loading && ar.class_version < N)`), seamlessly transforming legacy values without struct pollution or persisting obsolete keys on subsequent saves. - **Zero Exceptions & Total Warning Cleanliness**: Fully conforming to Juliet coding guidelines: strict assertions (`Assert`), `[[nodiscard]]`, `auto*`/`auto&`, mandatory braces, and `static_cast`/`reinterpret_cast`. --- @@ -133,7 +133,7 @@ Identifier ::= [a-zA-Z_][a-zA-Z0-9_]* ; ; AssetType Entity -; BaseVersion +; Version 1 ; EntityID @@ -712,10 +712,15 @@ In entity-component systems or object-oriented engine hierarchies, entities cons #### The Fragility of Monolithic Versioning In naive serialization architectures, a single `uint32 Version` governs the entire file. When an engine programmer updates the base `Entity` struct (e.g. adding a `uint32 LayerMask`), bumping the global version invalidates or forces schema changes across every single derived entity type in the project. -#### The Two-Tier Solution -Juliet decouples versioning into two independent tiers: -- **Tier 1: Base Engine Version (`kEntityBaseVersion`)**: Declared centrally in `Entity.h`. Governs `Entity` base fields. -- **Tier 2: Derived Class Version (`Class::Version`)**: Declared per-entity class in `Class.h` and initialized in `DEFINE_ENTITY_VERSIONED`. +#### The Two-Tier Solution: Universal `; Version` + Optional `; ClassVersion` +To keep `.jasset` files clean and unified across all asset types: +- **All `.jasset` files declare a universal `; Version` tag**: + - In simple standalone assets (e.g., `WorldSettings.jasset`, `Material.jasset`), `; Version` is the single asset schema version. + - In entity assets, `; Version` maps to the Base Engine Version (`kEntityBaseVersion` in `Entity.h`), governing core engine fields (`Position`, `Rotation`, `Scale`, etc.). +- **Entity classes with derived versioning add an optional `; ClassVersion`**: + - Governed by `Class::Version` in `Class.h`. + - Only written/read for entity classes that define custom versions. If omitted in the file, it defaults to `1`. + - Non-entity assets never see or use `; ClassVersion`. ``` ======================================================================== @@ -723,7 +728,7 @@ Juliet decouples versioning into two independent tiers: ======================================================================== ; AssetType Entity - ; BaseVersion ------> Governed by kEntityBaseVersion in Entity.h + ; Version ------> Universal asset version (kEntityBaseVersion for Entity) 1 ; EntityID 0x0000000000000001 @@ -732,32 +737,30 @@ Juliet decouples versioning into two independent tiers: ; Position 0.0 0.0 0.0 ------------------------------------------------------------------------ - ; ClassVersion ------> Governed by Class::Version in Class.h + ; ClassVersion ------> Optional derived version (Class::Version in Class.h) 2 ; MeshInstance 42 ======================================================================== ``` -When an engine programmer bumps `kEntityBaseVersion` from `1` to `2` to add `LayerMask`, no derived game classes (`Inert`, `Monster`, `Vehicle`) need version increments or code modifications. +When an engine programmer bumps `kEntityBaseVersion` from `1` to `2` to add `LayerMask`, no derived game classes (`Inert`, `Monster`, `Vehicle`) need version increments or code modifications. Conversely, non-entity assets (like `WorldSettings.jasset`) simply use `; Version \n 1` without carrying entity-specific terminology. -### 6.2 Implementation Details +### 6.2 Implementation Details: The Generalized `Class` Model -#### Engine Base Version (`Game/Entity/Entity.h`) +In this architecture, **every serializable struct in Juliet has a `Class` descriptor**: + +#### Generalized `Class` Struct (`Juliet/include/Engine/Class.h`) ```cpp -constexpr uint32 kEntityBaseVersion = 1; +using serialize_fct_type = void (*)(archive& ar, void* payload, uint16 version); -void SerializeEntityBase(archive& ar, NonNullPtr entity); -``` - -#### Derived Class Version (`Juliet/include/Engine/Class.h`) -```cpp struct Class { uint32 CRC; uint8 kind; - uint16 Version; // Added derived class version - serialize_fct_type serialize_fct; + uint16 Version; // Struct schema version + Class* BaseClass; // Pointer to parent class (or nullptr if root) + serialize_fct_type serialize_fct; // Type-specific serialization callback size_t size_of; size_t alignment; @@ -766,12 +769,14 @@ struct Class #endif }; -consteval Class MakeClass(String name, uint8 kind, uint16 version, size_t size, size_t align, serialize_fct_type fct) +consteval Class MakeClass(String name, uint8 kind, uint16 version, Class* baseClass, + size_t size, size_t align, serialize_fct_type fct) { Class cls = {}; cls.CRC = crc32(name.Str, name.Size); cls.kind = kind; cls.Version = version; + cls.BaseClass = baseClass; cls.size_of = size; cls.alignment = align; cls.serialize_fct = fct; @@ -784,80 +789,95 @@ consteval Class MakeClass(String name, uint8 kind, uint16 version, size_t size, } ``` -#### Class Registration Macros (`Game/Entity/Entity.h`) +#### Registration Macros ```cpp +// General class registration (e.g. for Entity, WorldSettings, Materials) +#define DEFINE_CLASS_VERSIONED(cls, version, base_class, serialize_fct) \ + Class classKind##cls = \ + MakeClass(ConstString(#cls), 0, (version), (base_class), sizeof(cls), alignof(cls), (serialize_fct)); \ + Class* cls::Kind = &classKind##cls; + +// Entity derived class registration (automatically sets BaseClass = Entity::Kind) #define DEFINE_ENTITY_VERSIONED(entity, version, serialize_fct) \ Class entityKind##entity = \ - MakeClass(ConstString(#entity), (uint8)Entity_Type::entity, (version), sizeof(entity), alignof(entity), serialize_fct); \ + MakeClass(ConstString(#entity), (uint8)Entity_Type::entity, (version), Entity::Kind, sizeof(entity), \ + alignof(entity), (serialize_fct)); \ Class* entity::Kind = &entityKind##entity; ``` -### 6.3 Execution Flow in `SerializeEntity` +#### Universal Class Instance Serializer +Because every type is a `Class`, serializing **any** struct in the engine is unified into a single function: + +```cpp +void SerializeClassInstance(archive& ar, Class* cls, void* instance) +{ + Assert(cls != nullptr); + Assert(instance != nullptr); + + uint16 version = cls->Version; + if (ar.IsSaving()) + { + // If this is a derived entity class, write as class_version; otherwise write universal version + if (cls->BaseClass != nullptr) + { + SerializeProp(ar, "class_version", "class_version"_crc32, version); + } + else + { + SerializeProp(ar, "version", "version"_crc32, version); + } + } + else + { + if (cls->BaseClass != nullptr) + { + (void)SerializeProp(ar, "class_version", "class_version"_crc32, version); + } + else + { + (void)SerializeProp(ar, "version", "version"_crc32, version); + } + } + + if (cls->serialize_fct != nullptr) + { + cls->serialize_fct(ar, instance, version); + } +} +``` + +#### Hierarchical Type Queries via `BaseClass` +```cpp +[[nodiscard]] inline bool IsA(const Class* queryClass, const Class* targetClass) +{ + const Class* current = queryClass; + while (current != nullptr) + { + if (current == targetClass) + { + return true; + } + current = current->BaseClass; + } + return false; +} +``` + +### 6.3 Entity Serialization with Generalized Classes +An entity instance is cleanly composed of its base `Entity` class and its derived `DerivedKind` class: + ```cpp void Serialize(archive& ar, NonNullPtr entity) { Assert(entity.Get() != nullptr); - // --- Tier 1: Base Entity Serialization --- - if (ar.IsSaving()) + // 1. Serialize Base Entity using Entity's own Class descriptor (Entity::Kind) + SerializeClassInstance(ar, Entity::Kind, entity.Get()); + + // 2. Serialize Derived Entity using its Class descriptor (entity->DerivedKind) + if (entity->DerivedKind != nullptr && entity->Derived != nullptr) { - uint32 baseVer = kEntityBaseVersion; - SERIALIZE_PROP(ar, baseVer); - SERIALIZE_PROP(ar, entity->ID); - - String kindStr = WrapString(kEntity_type_names[entity->Kind->kind]); - SerializeProp(ar, "Kind", "Kind"_crc32, kindStr); - - SerializeProp(ar, "Position", "Position"_crc32, entity->X, entity->Y, entity->Z); - } - else - { - uint32 baseVer = 0; - if (!SerializeProp(ar, "BaseVersion", "BaseVersion"_crc32, baseVer)) - { - baseVer = 1; // Default to initial schema if absent - } - ar.BaseVersion = baseVer; - - SERIALIZE_PROP(ar, entity->ID); - - String kindStr = {}; - if (SerializeProp(ar, "Kind", "Kind"_crc32, kindStr)) - { - // Resolve class pointer from name - for (uint8 i = 0; i < ToUnderlying(Entity_Type::Count); ++i) - { - if (StringCompare(kindStr, WrapString(kEntity_type_names[i])) == 0) - { - entity->Kind = kEntity_type_class_ptr[i]; - break; - } - } - } - Assert(entity->Kind != nullptr); - - SerializeProp(ar, "Position", "Position"_crc32, entity->X, entity->Y, entity->Z); - } - - // --- Tier 2: Derived Entity Serialization --- - if (entity->Kind->serialize_fct != nullptr && entity->Derived != nullptr) - { - if (ar.IsSaving()) - { - uint32 classVer = entity->Kind->Version; - SerializeProp(ar, "ClassVersion", "ClassVersion"_crc32, classVer); - } - else - { - uint32 classVer = 0; - if (!SerializeProp(ar, "ClassVersion", "ClassVersion"_crc32, classVer)) - { - classVer = entity->Kind->Version; - } - ar.ClassVersion = static_cast(classVer); - } - - entity->Kind->serialize_fct(&ar, entity->Derived); + SerializeClassInstance(ar, entity->DerivedKind, entity->Derived); } } ``` @@ -874,96 +894,19 @@ Over the lifecycle of a game, gameplay mechanics evolve: Retaining deprecated members in active C++ structs creates code clutter, wastes memory, and invites bugs. -### 7.2 Deprecation Primitives (`ReadDeprecated*`) -Deprecation primitives allow serializers to ingest obsolete properties exclusively during loading without polluting modern structs or writing deprecated keys back to disk during saving. +### 7.2 Stack-Allocated Migration via Standard Serialization +Rather than maintaining dedicated deprecation primitives or polluting C++ structs with obsolete members, deprecated properties are migrated using the standard `serialize` / `SERIALIZE` function with temporary local variables allocated on the stack. -```cpp -bool ReadDeprecated(archive& ar, const char* keyName, uint32 keyCRC, float& outVal) -{ - Assert(keyName != nullptr); - if (ar.IsSaving()) - { - return false; // Deprecated fields are never saved - } - - auto* node = FindProperty(ar.Properties, ar.PropertyCount, keyCRC); - if (node == nullptr) - { - return false; - } - - char buffer[64]; - size_t copySize = Min(node->Value.Size, sizeof(buffer) - 1); - MemCopy(buffer, node->Value.Str, copySize); - buffer[copySize] = '\0'; - - char* endPtr = nullptr; - float parsed = strtof(buffer, &endPtr); - if (endPtr != buffer) - { - outVal = parsed; - return true; - } - return false; -} - -bool ReadDeprecatedVec2(archive& ar, const char* keyName, uint32 keyCRC, float& outX, float& outY) -{ - Assert(keyName != nullptr); - if (ar.IsSaving()) - { - return false; - } - - auto* node = FindProperty(ar.Properties, ar.PropertyCount, keyCRC); - if (node == nullptr) - { - return false; - } - - char buffer[128]; - size_t copySize = Min(node->Value.Size, sizeof(buffer) - 1); - MemCopy(buffer, node->Value.Str, copySize); - buffer[copySize] = '\0'; - - float x = 0.0f; - float y = 0.0f; - if (sscanf_s(buffer, "%f %f", &x, &y) == 2) - { - outX = x; - outY = y; - return true; - } - return false; -} - -bool ReadDeprecatedString(archive& ar, const char* keyName, uint32 keyCRC, NonNullPtr arena, String& outVal) -{ - Assert(keyName != nullptr); - if (ar.IsSaving()) - { - return false; - } - - auto* node = FindProperty(ar.Properties, ar.PropertyCount, keyCRC); - if (node == nullptr) - { - return false; - } - - String parsed = node->Value; - if (parsed.Size >= 2 && parsed.Str[0] == '"' && parsed.Str[parsed.Size - 1] == '"') - { - parsed.Str++; - parsed.Size -= 2; - } - outVal = StringCopy(arena, parsed); - return true; -} -``` +When an asset property is deprecated, restructured, or renamed: +1. The obsolete field is completely removed from the modern C++ struct definition. +2. In the entity/component serializer, an `if (ar.loading && ar.class_version < N)` block is added. +3. A local variable of the legacy type is declared on the stack. +4. The standard `SERIALIZE(ar, OldPropName, deprecated_val)` (or `serialize(ar, ConstString("OldPropName"), "OldPropName"_crc32, deprecated_val)`) is called. If the property exists in the loaded asset, it parses into `deprecated_val` and returns `true`. +5. The serializer performs whatever mapping or transformation is required into the modern struct fields. +6. Because the migration block is strictly guarded by `ar.loading`, it never executes during save operations (`ar.loading == false`). The serializer writes only modern struct fields, automatically purging obsolete keys on subsequent saves without requiring dedicated cleanup routines. ### 7.3 In-Place Migration Pattern -When an asset file with an older `ClassVersion` is loaded, the derived serializer detects `ar.ClassVersion < N`, calls `ReadDeprecated*` to read obsolete fields, maps the legacy data into the modern struct, and continues. On the subsequent save, the asset file is emitted using the modern schema without deprecated keys. +When an asset file with an older `class_version` is loaded, the serializer detects `ar.class_version < N`, reads obsolete fields into stack variables using standard serialization, maps the legacy data into the modern struct, and completes loading. On the subsequent save, the asset file is emitted using the modern schema without deprecated keys. ```cpp struct Projectile @@ -976,39 +919,40 @@ struct Projectile float Damage = 50.0f; }; -void SerializeProjectile(archive* arPtr, void* payload) +void SerializeProjectile(Archive* arPtr, void* payload) { Assert(arPtr != nullptr); Assert(payload != nullptr); auto& ar = *arPtr; auto* projectile = static_cast(payload); - if (ar.IsSaving()) + if (ar.loading) { - SERIALIZE_PROP(ar, projectile->VelocityX); - SERIALIZE_PROP(ar, projectile->VelocityY); - SERIALIZE_PROP(ar, projectile->Damage); - } - else - { - SERIALIZE_PROP(ar, projectile->Damage); + SERIALIZE(ar, Damage, projectile->Damage); - if (ar.ClassVersion < 2) + if (ar.class_version < 2) { - // Migration from v1: scalar 'Speed' converted to 'VelocityX' - float legacySpeed = 0.0f; - if (ReadDeprecated(ar, "Speed", "Speed"_crc32, legacySpeed)) + // Migration from v1: scalar 'Speed' converted to 'VelocityX' on the stack + float deprecated_speed = 0.0f; + if (SERIALIZE(ar, Speed, deprecated_speed)) { - projectile->VelocityX = legacySpeed; + projectile->VelocityX = deprecated_speed; projectile->VelocityY = 0.0f; } } else { - SERIALIZE_PROP(ar, projectile->VelocityX); - SERIALIZE_PROP(ar, projectile->VelocityY); + SERIALIZE(ar, VelocityX, projectile->VelocityX); + SERIALIZE(ar, VelocityY, projectile->VelocityY); } } + else + { + // Save modern schema + SERIALIZE(ar, VelocityX, projectile->VelocityX); + SERIALIZE(ar, VelocityY, projectile->VelocityY); + SERIALIZE(ar, Damage, projectile->Damage); + } } ``` @@ -1034,10 +978,10 @@ void SerializeProjectile(archive* arPtr, void* payload) | v +-------------------------------------------------------------------------+ -| Phase 3: Archive Struct & SerializeProp Helpers | -| - Upgrade archive in serialization.h with ArchiveMode | -| - Implement primitive, vector, and string SerializeProp helpers | -| - Implement ReadDeprecated* primitives | +| Phase 3: Archive Struct & Serialize Helpers | +| - Upgrade Archive in serialization.h with loading / version state | +| - Implement primitive, vector, and string serialize helpers | +| - Support stack-allocated schema migration | +-------------------------------------------------------------------------+ | v @@ -1068,12 +1012,11 @@ void SerializeProjectile(archive* arPtr, void* payload) - Implement `TokenizeTextArchive(NonNullPtr arena, ByteBuffer buffer)`. - Implement `FindProperty` and `AuditUnconsumedProperties`. -#### Phase 3: Archive Struct & `SerializeProp` Helpers +#### Phase 3: Archive Struct & `serialize` Helpers 1. **Target**: `Juliet/include/Core/Common/serialization.h` - - Introduce `enum class ArchiveMode : uint8`. - - Upgrade `struct archive` with stream pointer, property array, mode, and versions. - - Implement overloaded `SerializeProp` for `float`, `int32`, `uint64`, `bool`, vectors, and `String`. - - Implement `ReadDeprecated`, `ReadDeprecatedVec2`, `ReadDeprecatedString`. + - Upgrade `struct Archive` with stream pointer, property array, `loading` flag, and versions (`base_version`, `class_version`). + - Implement overloaded `serialize`, `read_prop`, and `write` for `float`, `int32`, `uint64`, `bool`, vectors, and `String`. + - Support deprecation migration using standard `serialize` / `SERIALIZE` with local stack variables under `if (ar.loading && ar.class_version < N)`. #### Phase 4: Entity & World Integration 1. **Target**: `Game/Entity/Entity.h` and `Game/Entity/Entity.cpp` @@ -1240,35 +1183,35 @@ namespace UnitTest float VelocityY = 0.0f; }; - void SerializeLegacyWeapon(archive* arPtr, void* payload) + void SerializeLegacyWeapon(Archive* arPtr, void* payload) { Assert(arPtr != nullptr); Assert(payload != nullptr); auto& ar = *arPtr; auto* weapon = static_cast(payload); - if (ar.IsSaving()) + if (ar.loading) { - SERIALIZE_PROP(ar, weapon->VelocityX); - SERIALIZE_PROP(ar, weapon->VelocityY); - } - else - { - if (ar.ClassVersion < 2) + if (ar.class_version < 2) { - float oldSpeed = 0.0f; - if (ReadDeprecated(ar, "Speed", "Speed"_crc32, oldSpeed)) + float deprecated_speed = 0.0f; + if (SERIALIZE(ar, Speed, deprecated_speed)) { - weapon->VelocityX = oldSpeed; + weapon->VelocityX = deprecated_speed; weapon->VelocityY = 0.0f; } } else { - SERIALIZE_PROP(ar, weapon->VelocityX); - SERIALIZE_PROP(ar, weapon->VelocityY); + SERIALIZE(ar, VelocityX, weapon->VelocityX); + SERIALIZE(ar, VelocityY, weapon->VelocityY); } } + else + { + SERIALIZE(ar, VelocityX, weapon->VelocityX); + SERIALIZE(ar, VelocityY, weapon->VelocityY); + } } DEFINE_ENTITY_VERSIONED(LegacyWeapon, 2, SerializeLegacyWeapon); @@ -1279,7 +1222,7 @@ namespace UnitTest // Simulated v1 file containing obsolete 'Speed' const char* v1Content = - "; ClassVersion\n" + "; class_version\n" "1\n" "; Speed\n" "75.500000\n"; @@ -1289,14 +1232,13 @@ namespace UnitTest .Size = strlen(v1Content) }; - ParsedTextArchive parsed = TokenizeTextArchive(temp.Arena, buffer); + ParsedArchive parsed = tokenize_archive(temp.Arena, buffer); - archive ar = {}; - ar.ArenaInstance = temp.Arena; - ar.Mode = ArchiveMode::LoadingText; - ar.Properties = parsed.Nodes; - ar.PropertyCount = parsed.PropertyCount; - ar.ClassVersion = 1; + Archive ar = {}; + ar.arena = temp.Arena; + ar.loading = true; + ar.base = parsed; + ar.class_version = 1; LegacyWeapon weapon; SerializeLegacyWeapon(&ar, &weapon); diff --git a/Game/Plans/02_Entity_Allocation_And_Lifecycle.md b/Game/Plans/02_Entity_Allocation_And_Lifecycle.md index 2bc1764..28e002c 100644 --- a/Game/Plans/02_Entity_Allocation_And_Lifecycle.md +++ b/Game/Plans/02_Entity_Allocation_And_Lifecycle.md @@ -205,36 +205,36 @@ The canonical allocation function is defined in `EntityManager.h`: The implementation replaces the flawed `RegisterEntity` routine in [`Game/Entity/EntityManager.cpp`](file:///w:/Classified/Juliet/Game/Entity/EntityManager.cpp): ```cpp -[[nodiscard]] Entity* AllocateEntity(EntityManager& manager, Class* classPtr) +[[nodiscard]] Entity* AllocateEntity(EntityManager& manager, Class* derivedClassPtr) { - Assert(classPtr != nullptr); - Assert(classPtr->kind < ENTITY(Count)); - Assert(classPtr->size_of >= sizeof(entity_template)); - Assert(classPtr->alignment > 0); + Assert(derivedClassPtr != nullptr); + Assert(derivedClassPtr->kind < ENTITY(Count)); + Assert(derivedClassPtr->size_of >= sizeof(entity_template)); + Assert(derivedClassPtr->alignment > 0); // 1. Allocate uninitialized Base Entity in the contiguous VectorArena Entity baseTemplate{}; - baseTemplate.ID = EntityManager::ID++; - baseTemplate.Kind = classPtr; - baseTemplate.Derived = nullptr; - baseTemplate.X = 0.0f; - baseTemplate.Y = 0.0f; - baseTemplate.Z = 0.0f; - baseTemplate.IsDirty = true; + baseTemplate.ID = EntityManager::ID++; + baseTemplate.DerivedKind = derivedClassPtr; + baseTemplate.Derived = nullptr; + baseTemplate.X = 0.0f; + baseTemplate.Y = 0.0f; + baseTemplate.Z = 0.0f; + baseTemplate.IsDirty = true; manager.Entities.PushBack(baseTemplate); Entity* basePtr = manager.Entities.Back(); Assert(basePtr != nullptr); // 2. Allocate zeroed derived component memory in the typed arena - typed_entity_array& typedArray = manager.by_type[classPtr->kind]; + typed_entity_array& typedArray = manager.by_type[derivedClassPtr->kind]; Assert(typedArray.arena != nullptr); void* rawMemory = ArenaPushSize( typedArray.arena, - classPtr->size_of, - classPtr->alignment, - true JULIET_DEBUG_PARAM(kEntity_type_names[classPtr->kind])); + derivedClassPtr->size_of, + derivedClassPtr->alignment, + true JULIET_DEBUG_PARAM(kEntity_type_names[derivedClassPtr->kind])); Assert(rawMemory != nullptr); auto* derivedTemplate = reinterpret_cast(rawMemory); @@ -330,9 +330,9 @@ entity_instance 0x0100000000000042 ; class Inert -; base_version +; version 1 -; derived_version +; class_version 1 ; position 0.43 0.32 1.56 @@ -342,7 +342,8 @@ Inert #### Important: No Header Structs for Derived Types - **Derived types NEVER require their own file header**: You do **not** write an `InertHeader`, `DoorHeader`, or `PlayerHeader`. Derived types only serialize their own member variables. -- **No binary `EntityFileHeader` struct is needed**: Under the `; variable_name\nvalues` text format, there is no packed binary C-struct header at all. The common properties (`; id`, `; class`, `; base_version`, `; derived_version`, `; position`) are standard text Key-Value nodes read by the exact same `archive` parser. +- **Universal `; version` + optional `; class_version`**: Every `.jasset` file has a universal `; version` tag. For entity assets, `; version` governs base entity properties (`kEntityBaseVersion`), while an optional `; class_version` governs derived class properties (`Class::Version`). Non-entity assets like `WorldSettings.jasset` only have `; version`. +- **No binary `EntityFileHeader` struct is needed**: Under the `; variable_name\nvalues` text format, there is no packed binary C-struct header at all. The common properties (`; id`, `; class`, `; version`, `; class_version`, `; position`) are standard text Key-Value nodes read by the exact same `archive` parser. ### 4.2 Eliminating Intermediate Stack Allocations Under the new pipeline: @@ -351,7 +352,7 @@ Under the new pipeline: 3. The engine reads `; class` (e.g. `"Inert"`) and resolves `Class* classPtr = FindClassByName(className)`. 4. `AllocateEntity(manager, classPtr)` is called immediately. Memory for both base and derived components is allocated in-place in their permanent engine arenas. 5. Base properties (`id`, `position`, etc.) are read directly into `*basePtr` via `SerializeEntityBase`. -6. `classPtr->serialize_fct(ar, basePtr->Derived, derivedVersion)` is called. Derived fields stream **directly into the typed arena** without temporary staging buffers or stack copies. +6. `classPtr->serialize_fct(ar, basePtr->Derived, classVersion)` is called. Derived fields stream **directly into the typed arena** without temporary staging buffers or stack copies. ### 4.3 Runtime Class Resolution To ensure fast and safe type lookup during file deserialization: @@ -440,18 +441,13 @@ To ensure fast and safe type lookup during file deserialization: Entity* basePtr = AllocateEntity(manager, classPtr); Assert(basePtr != nullptr); - // 3. Read base versions and properties in-place - uint16 baseVersion = 1; - uint16 derivedVersion = 1; - SerializeProp(ar, "base_version", baseVersion); - SerializeProp(ar, "derived_version", derivedVersion); + // 3. Serialize Base Entity in-place using Entity::Kind + SerializeClassInstance(ar, Entity::Kind, basePtr); - SerializeEntityBase(ar, *basePtr, baseVersion); - - // 4. Stream derived properties in-place directly into the typed arena - if (classPtr->serialize_fct != nullptr) + // 4. Stream derived properties in-place using basePtr->DerivedKind + if (basePtr->DerivedKind != nullptr && basePtr->Derived != nullptr) { - classPtr->serialize_fct(ar, basePtr->Derived, derivedVersion); + SerializeClassInstance(ar, basePtr->DerivedKind, basePtr->Derived); } // Freshly loaded entity matches disk state exactly @@ -663,13 +659,15 @@ To solve this, `Entity` in [`Game/Entity/Entity.h`](file:///w:/Classified/Juliet ```cpp struct Entity final { - EntityID ID = 0; - Class* Kind = nullptr; - DerivedType Derived = nullptr; - float X = 0.0f; - float Y = 0.0f; - float Z = 0.0f; - bool IsDirty = false; + DECLARE_ENTITY() // static Class* Kind; (Entity's own Class descriptor) + + EntityID ID = 0; + Class* DerivedKind = nullptr; // Pointer to derived class descriptor (e.g. Inert::Kind) + DerivedType Derived = nullptr; // Pointer to derived component memory + float X = 0.0f; + float Y = 0.0f; + float Z = 0.0f; + bool IsDirty = false; }; ``` diff --git a/Game/Plans/03_Entity_ID_And_World_Directory.md b/Game/Plans/03_Entity_ID_And_World_Directory.md index 21f61bc..a476181 100644 --- a/Game/Plans/03_Entity_ID_And_World_Directory.md +++ b/Game/Plans/03_Entity_ID_And_World_Directory.md @@ -353,23 +353,36 @@ Assets/Worlds// └── ... ``` -### 4.2 `WorldSettings.jasset` Binary Layout -Global world settings are stored in `WorldSettings.jasset`. +### 4.2 `WorldSettings.jasset` Format Specification +Global world settings are stored in `WorldSettings.jasset` in the standard `.jasset` text format with universal `; version`: +```ini +; asset_type +world_settings +; version +1 +; sun_direction +0.577 -0.577 -0.577 +; sun_color +1.0 0.95 0.8 +; sun_intensity +1.0 +; ambient_color +0.2 0.25 0.35 +; ambient_intensity +0.15 +; gravity +0.0 0.0 -9.81 +; kill_plane_z +-50.0 +``` + +In memory, these map to standard C-style POD data: ```cpp -#pragma pack(push, 1) -struct WorldSettingsFileHeader -{ - uint32 Magic = 0x5453574A; // 'JWST' (Juliet World SeTtings) in little-endian - uint32 Version = 1; -}; -#pragma pack(pop) - struct WorldEnvironmentSettings { // Directional Sun & Ambient Lighting Vector3 SunDirection = { 0.577f, -0.577f, -0.577f }; - float _Pad0 = 0.0f; Vector3 SunColor = { 1.0f, 0.95f, 0.8f }; float SunIntensity = 1.0f; Vector3 AmbientColor = { 0.2f, 0.25f, 0.35f }; @@ -391,9 +404,9 @@ entity_instance 0x0100000000000042 ; class Inert -; base_version +; version 1 -; derived_version +; class_version 1 ; position 0.43 0.32 1.56 @@ -408,8 +421,8 @@ Inert - `; class`: The runtime `Class` name (e.g. `Inert`, `Door`). - `; template`: Optional relative path to archetype template (e.g. `Assets/Templates/Door_Wood.jasset`). 2. **Version Directives**: - - `; base_version`: Engine-wide base entity version (`kEntityBaseVersion`). - - `; derived_version`: Class-specific gameplay version (`Class::Version`). + - `; version`: Universal asset version (`kEntityBaseVersion` for entities, or asset schema version for non-entity files like `WorldSettings.jasset`). + - `; class_version`: Optional class-specific gameplay version (`Class::Version`), defaults to 1 if omitted. Non-entity assets do not use this. 3. **Base Entity Properties**: - Position (`position\n0.43 0.32 1.56`), Rotation, Scale. 4. **Derived Entity Properties**: diff --git a/Game/game.cpp b/Game/game.cpp index c74581b..a982855 100644 --- a/Game/game.cpp +++ b/Game/game.cpp @@ -21,7 +21,7 @@ // namespace // { -// void serialize_test(archive* ar, void* payload) +// void serialize_test(Archive* ar, void* payload) // { // SerializedEntityTest* test = reinterpret_cast(payload); // serialize_elem(ar, test->A); diff --git a/Juliet/include/Core/Application/ApplicationManager.h b/Juliet/include/Core/Application/ApplicationManager.h index d1848a0..e8c90f0 100644 --- a/Juliet/include/Core/Application/ApplicationManager.h +++ b/Juliet/include/Core/Application/ApplicationManager.h @@ -1,7 +1,6 @@ #pragma once #include -#include #include enum class JulietInit_Flags : uint8; diff --git a/Juliet/include/Core/Common/CoreTypes.h b/Juliet/include/Core/Common/CoreTypes.h index c1c4e95..9a356eb 100644 --- a/Juliet/include/Core/Common/CoreTypes.h +++ b/Juliet/include/Core/Common/CoreTypes.h @@ -43,5 +43,23 @@ constexpr int64 int64Max = MaxValueOf(); constexpr index_t indexMax = MaxValueOf(); +template +consteval Type MinValueOf() +{ + return std::numeric_limits::lowest(); +} + +constexpr uint8 uint8Min = MinValueOf(); +constexpr uint16 uint16Min = MinValueOf(); +constexpr uint32 uint32Min = MinValueOf(); +constexpr uint64 uint64Min = MinValueOf(); + +constexpr int8 int8Min = MinValueOf(); +constexpr int16 int16Min = MinValueOf(); +constexpr int32 int32Min = MinValueOf(); +constexpr int64 int64Min = MinValueOf(); + +constexpr index_t indexMin = MinValueOf(); + #define Kilobytes(value) value * 1024 #define Megabytes(value) Kilobytes(value) * 1024 diff --git a/Juliet/include/Core/Common/CoreUtils.h b/Juliet/include/Core/Common/CoreUtils.h index fd04ee6..ff391c1 100644 --- a/Juliet/include/Core/Common/CoreUtils.h +++ b/Juliet/include/Core/Common/CoreUtils.h @@ -1,9 +1,7 @@ #pragma once -#include #include - #define global static // 1. Stringify helpers @@ -50,40 +48,40 @@ #if JULIET_DEBUG #define JULIET_ASSERT_INTERNAL(expression, message) \ -JULIET_WARNING_PUSH \ -JULIET_SUPPRESS_CLANG("-Wextra-semi-stmt") \ -JULIET_SUPPRESS_MSVC(4127) \ -JULIET_SUPPRESS_MSVC(4548) \ -{ \ - if (!(expression)) [[unlikely]] \ - { \ - JulietAssert(#expression, message); \ - } \ -} \ -JULIET_WARNING_POP \ -static_assert(true, "") + JULIET_WARNING_PUSH \ + JULIET_SUPPRESS_CLANG("-Wextra-semi-stmt") \ + JULIET_SUPPRESS_MSVC(4127) \ + JULIET_SUPPRESS_MSVC(4548) \ + { \ + if (!(expression)) [[unlikely]] \ + { \ + JulietAssert(#expression, message); \ + } \ + } \ + JULIET_WARNING_POP \ + static_assert(true, "") #define AssertHR(hr_expression, message) \ -do \ -{ \ - long hr_val = (hr_expression); \ - if (hr_val < 0) \ - { \ - JulietAssert(#hr_expression, message, std::source_location::current(), hr_val); \ - } \ -} \ -while (0) + do \ + { \ + long hr_val = (hr_expression); \ + if (hr_val < 0) \ + { \ + JulietAssert(#hr_expression, message, std::source_location::current(), hr_val); \ + } \ + } \ + while (0) #define GET_ASSERT_MACRO(_1, _2, NAME, ...) NAME #define Assert(...) GET_ASSERT_MACRO(__VA_ARGS__, JULIET_ASSERT_INTERNAL, JULIET_ASSERT_NO_MSG)(__VA_ARGS__) #define JULIET_ASSERT_NO_MSG(expression) JULIET_ASSERT_INTERNAL(expression, "No additional information provided.") #define Unimplemented() \ -do \ -{ \ - JulietAssert("UNIMPLEMENTED", "This code path is not yet functional."); \ -} \ -while (0) + do \ + { \ + JulietAssert("UNIMPLEMENTED", "This code path is not yet functional."); \ + } \ + while (0) #else #define Assert(...) ((void)0) diff --git a/Juliet/include/Core/Common/String.h b/Juliet/include/Core/Common/String.h index d6293c1..4d92dba 100644 --- a/Juliet/include/Core/Common/String.h +++ b/Juliet/include/Core/Common/String.h @@ -19,9 +19,9 @@ struct Arena; #define ConstString(str) { const_cast((str)), sizeof(str) - 1 } #define CStr(str) ((str).Str) #define InplaceString(name, size) \ -char name##_[size]; \ -MemSet(name##_, 0, sizeof(uint32)); \ -String name = { name##_, 0 } + char name##_[size]; \ + MemSet(name##_, 0, sizeof(uint32)); \ + String name = { name##_, 0 } // Everything is Little Endian enum class StringEncoding : uint8 @@ -164,6 +164,8 @@ extern JULIET_API bool ConvertString(String from, String to, String src, StringB JULIET_API String StringCopy(NonNullPtr arena, String str); JULIET_API String16 str16_from_8(NonNullPtr arena, String8 str); +String trim_whitespace(String str); + template String Format(NonNullPtr arena, const char* formatStr, Args&&... args) { diff --git a/Juliet/include/Core/Common/serialization.h b/Juliet/include/Core/Common/serialization.h index 81d785e..e7f37d1 100644 --- a/Juliet/include/Core/Common/serialization.h +++ b/Juliet/include/Core/Common/serialization.h @@ -2,16 +2,126 @@ #include +#include +#include + +// .jasset +// AssetFile ::= { CommentLine | EmptyLine | PropertyDeclaration } ; +// CommentLine ::= ( "#" | "//" ) { Character } LineEnding ; +// EmptyLine ::= { Whitespace } LineEnding ; +// PropertyDeclaration ::= KeyHeader LineEnding ValueBlock ; +// KeyHeader ::= ";" { Whitespace } Identifier ; +// ValueBlock ::= { ValueLine LineEnding } ; +// ValueLine ::= { Whitespace } ValueString { Whitespace } ; +// LineEnding ::= "\r\n" | "\n" ; +// Identifier ::= [a-zA-Z_][a-zA-Z0-9_]* ; + +struct Vector4; +struct IOStream; struct Arena; -struct archive +struct ArchivePropertyNode { - Arena* arena; - void* base_ptr; - index_t offset; - bool loading; + String key; + String value; + uint32 key_crc; + bool consumed; }; -JULIET_API void serialize(archive& ar, void* data, size_t size); +struct ParsedArchive +{ + ArchivePropertyNode* nodes = nullptr; + uint32 property_count = 0; +}; + +struct Archive +{ + Arena* arena; + bool loading; + ParsedArchive base = {}; + IOStream* stream = nullptr; + uint32 base_version = 0; + uint16 class_version = 0; + + // to remove + void* base_ptr = nullptr; + index_t offset = 0; +}; + +JULIET_API void serialize(Archive& ar, void* data, size_t size); #define serialize_elem(ar, val) serialize((ar), &(val), sizeof(val)) + +JULIET_API ParsedArchive tokenize_archive(NonNullPtr arena, ByteBuffer file_buffer); +JULIET_API ArchivePropertyNode* find_property(NonNullPtr archive, uint32 property_crc); +JULIET_API void write_property_header(Archive& archive, String property_name); + +JULIET_API bool read_prop(Archive& ar, String value_raw, String& value); +JULIET_API void write(NonNullPtr stream, String value); +JULIET_API bool read(const char* buffer, float& value, const char** end = nullptr); +JULIET_API void write(NonNullPtr stream, float value); +JULIET_API bool read(const char* buffer, int8& value); +JULIET_API void write(NonNullPtr stream, int8 value); +JULIET_API bool read(const char* buffer, int16& value); +JULIET_API void write(NonNullPtr stream, int16 value); +JULIET_API bool read(const char* buffer, int32& value); +JULIET_API void write(NonNullPtr stream, int32 value); +JULIET_API bool read(const char* buffer, int64& value); +JULIET_API void write(NonNullPtr stream, int64 value); +JULIET_API bool read(const char* buffer, uint8& value); +JULIET_API void write(NonNullPtr stream, uint8 value); +JULIET_API bool read(const char* buffer, uint16& value); +JULIET_API void write(NonNullPtr stream, uint16 value); +JULIET_API bool read(const char* buffer, uint32& value); +JULIET_API void write(NonNullPtr stream, uint32 value); +JULIET_API bool read(const char* buffer, uint64& value); +JULIET_API void write(NonNullPtr stream, uint64 value); +JULIET_API bool read(const char* buffer, bool& value); +JULIET_API void write(NonNullPtr stream, bool value); +JULIET_API bool read(const char* buffer, Vector4& value); +JULIET_API void write(NonNullPtr stream, Vector4 value); + +// For primitives not needing archive nor allocation +template +bool read_prop(Archive& /*ar*/, String value_raw, Type& value) +{ + char buffer[64]; + size_t cpy_size = Min(value_raw.Size, sizeof(buffer) - 1); + MemCopy(buffer, value_raw.Str, cpy_size); + buffer[cpy_size] = '\0'; + + return read(buffer, value); +} + +#define SERIALIZE(ar, name, var) serialize((ar), ConstString(#name), #name##_crc32, (var)) + +template +bool serialize(Archive& ar, String property_name, uint32 property_crc, Type& value) +{ + Assert(IsValid(property_name)); + + bool result = false; + if (ar.loading) + { + if (auto* prop = find_property(&ar.base, property_crc)) + { + if (read_prop(ar, prop->value, value)) + { + result = true; + } + } + } + else + { + // Save + write_property_header(ar, property_name); + write(ar.stream, value); + result = true; + } + + return result; +} + +#if JULIET_DEBUG +JULIET_API void audit_unconsumed_properties(NonNullPtr archive, String context_name); +#endif diff --git a/Juliet/include/Core/HAL/Display/Display.h b/Juliet/include/Core/HAL/Display/Display.h index b1ca2f5..e7ebd21 100644 --- a/Juliet/include/Core/HAL/Display/Display.h +++ b/Juliet/include/Core/HAL/Display/Display.h @@ -1,9 +1,7 @@ #pragma once -#include #include #include -#include struct Window; @@ -15,4 +13,4 @@ extern JULIET_API void ShowWindow(NonNullPtr window); extern JULIET_API void HideWindow(NonNullPtr window); extern JULIET_API WindowID GetWindowID(NonNullPtr window); -extern JULIET_API void SetWindowTitle(NonNullPtr window, String title); +extern JULIET_API void SetWindowTitle(NonNullPtr window, String title); diff --git a/Juliet/include/Core/HAL/Keyboard/KeyCode.h b/Juliet/include/Core/HAL/Keyboard/KeyCode.h index 3791edb..6d2924d 100644 --- a/Juliet/include/Core/HAL/Keyboard/KeyCode.h +++ b/Juliet/include/Core/HAL/Keyboard/KeyCode.h @@ -1,7 +1,5 @@ #pragma once -#include - // Represents a Virtual Key corresponding to the Physical key, but localized using the keyboard layout // ScanCode reprensent US ASCII Keyboard // WASD Scan codes are ZQSD in KeyCode for French keyboard diff --git a/Juliet/include/Core/HAL/OS/OS.h b/Juliet/include/Core/HAL/OS/OS.h index 5d95468..6bc53a2 100644 --- a/Juliet/include/Core/HAL/OS/OS.h +++ b/Juliet/include/Core/HAL/OS/OS.h @@ -1,8 +1,5 @@ #pragma once -#include -#include - namespace Memory { Byte* OS_Reserve(size_t size); diff --git a/Juliet/include/Core/ImGui/ImGuiService.h b/Juliet/include/Core/ImGui/ImGuiService.h index 4182d6d..d5ac535 100644 --- a/Juliet/include/Core/ImGui/ImGuiService.h +++ b/Juliet/include/Core/ImGui/ImGuiService.h @@ -1,6 +1,5 @@ #pragma once -#include #include #ifdef JULIET_ENABLE_IMGUI diff --git a/Juliet/include/Core/JulietInit.h b/Juliet/include/Core/JulietInit.h index c052856..16748d5 100644 --- a/Juliet/include/Core/JulietInit.h +++ b/Juliet/include/Core/JulietInit.h @@ -1,7 +1,5 @@ #pragma once -#include - enum class JulietInit_Flags : uint8 { None = 0, diff --git a/Juliet/include/Core/Logging/LogManager.h b/Juliet/include/Core/Logging/LogManager.h index 32acff0..e5e07bc 100644 --- a/Juliet/include/Core/Logging/LogManager.h +++ b/Juliet/include/Core/Logging/LogManager.h @@ -1,9 +1,7 @@ #pragma once -#include #include #include -#include // TODO : Juliet strings // TODO Juliet Containers + Allocators... diff --git a/Juliet/include/Core/Math/MathUtils.h b/Juliet/include/Core/Math/MathUtils.h index 97e910c..bde7374 100644 --- a/Juliet/include/Core/Math/MathUtils.h +++ b/Juliet/include/Core/Math/MathUtils.h @@ -1,8 +1,5 @@ #pragma once -#include -#include - extern JULIET_API float RoundF(float value); inline int32 LRoundF(float value) diff --git a/Juliet/include/Core/Math/Vector.h b/Juliet/include/Core/Math/Vector.h index 3d089ad..434520a 100644 --- a/Juliet/include/Core/Math/Vector.h +++ b/Juliet/include/Core/Math/Vector.h @@ -1,9 +1,5 @@ #pragma once -#include -#include -#include - struct Vector3 { float x, y, z; @@ -15,7 +11,10 @@ struct Vector3 struct Vector4 { - float x, y, z, w; + float x = 0.f; + float y = 0.f; + float z = 0.f; + float w = 0.f; }; inline Vector3 Normalize(const Vector3& v) @@ -33,4 +32,7 @@ inline Vector3 Cross(const Vector3& a, const Vector3& b) return { a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x }; } -inline float Dot(const Vector3& a, const Vector3& b) { return a.x * b.x + a.y * b.y + a.z * b.z; } +inline float Dot(const Vector3& a, const Vector3& b) +{ + return a.x * b.x + a.y * b.y + a.z * b.z; +} diff --git a/Juliet/include/Core/Memory/MemoryArena.h b/Juliet/include/Core/Memory/MemoryArena.h index 113d6ce..855cff4 100644 --- a/Juliet/include/Core/Memory/MemoryArena.h +++ b/Juliet/include/Core/Memory/MemoryArena.h @@ -1,10 +1,8 @@ #pragma once -#include #include #include #include -#include #if JULIET_DEBUG #include @@ -79,8 +77,7 @@ JULIET_API void ArenaClear(NonNullPtr arena); template #endif [[nodiscard]] inline void* ArenaPushSize(NonNullPtr arena, size_t size, size_t align, - bool shouldBeZeroed JULIET_DEBUG_PARAM(FirstDebugArg&& firstDebugArg, - DebugArgs&&... debugArgs)) + bool shouldBeZeroed JULIET_DEBUG_PARAM(FirstDebugArg&& firstDebugArg, DebugArgs&&... debugArgs)) { return ArenaPush(arena, size, align, shouldBeZeroed JULIET_DEBUG_PARAM( diff --git a/Juliet/include/Core/Memory/MemoryArenaDebug.h b/Juliet/include/Core/Memory/MemoryArenaDebug.h index 2793768..738d41d 100644 --- a/Juliet/include/Core/Memory/MemoryArenaDebug.h +++ b/Juliet/include/Core/Memory/MemoryArenaDebug.h @@ -1,9 +1,7 @@ #pragma once -#include #include #include -#include #if JULIET_DEBUG @@ -45,5 +43,4 @@ void DebugArenaRemoveLastAllocation(MemoryBlock* blk); JULIET_API Arena* GetDebugInfoArena(); - #endif diff --git a/Juliet/include/Core/Memory/Utils.h b/Juliet/include/Core/Memory/Utils.h index f02697f..f1dbfe8 100644 --- a/Juliet/include/Core/Memory/Utils.h +++ b/Juliet/include/Core/Memory/Utils.h @@ -1,7 +1,5 @@ #pragma once -#include - #define ArraySize(array) (sizeof(array) / sizeof(array[0])) inline int32 MemCompare(const void* leftValue, const void* rightValue, size_t size) @@ -60,13 +58,13 @@ struct QueueNode }; #define DECLARE_QUEUE(type) \ -struct type##Queue \ -{ \ - type* First; \ - type* Last; \ - size_t Nodecount; \ - size_t Size; \ -}; + struct type##Queue \ + { \ + type* First; \ + type* Last; \ + size_t Nodecount; \ + size_t Size; \ + }; // TODO: homemade versions #define MemSet memset diff --git a/Juliet/include/Core/Networking/IPAddress.h b/Juliet/include/Core/Networking/IPAddress.h index 441dbc4..8677b0d 100644 --- a/Juliet/include/Core/Networking/IPAddress.h +++ b/Juliet/include/Core/Networking/IPAddress.h @@ -1,7 +1,5 @@ #pragma once -#include - // TODO : Do something better. constexpr uint32 kLocalhost = (127 << 3) | (0 << 2) | (0 << 1) | 1; constexpr uint32 kAnyIp = 0; diff --git a/Juliet/include/Core/Networking/NetworkPacket.h b/Juliet/include/Core/Networking/NetworkPacket.h index 1d99295..e5d4208 100644 --- a/Juliet/include/Core/Networking/NetworkPacket.h +++ b/Juliet/include/Core/Networking/NetworkPacket.h @@ -1,6 +1,5 @@ #pragma once -#include #include class NetworkPacket diff --git a/Juliet/include/Core/PCH.h b/Juliet/include/Core/PCH.h index 7ac627b..1c12dbd 100644 --- a/Juliet/include/Core/PCH.h +++ b/Juliet/include/Core/PCH.h @@ -29,4 +29,6 @@ #include #include +#include + #include diff --git a/Juliet/include/Core/Thread/ThreadContext.h b/Juliet/include/Core/Thread/ThreadContext.h index 35f440d..4c161db 100644 --- a/Juliet/include/Core/Thread/ThreadContext.h +++ b/Juliet/include/Core/Thread/ThreadContext.h @@ -1,6 +1,5 @@ #pragma once -#include #include struct thread_context diff --git a/Juliet/include/Engine/Class.h b/Juliet/include/Engine/Class.h index 241e6f8..7863748 100644 --- a/Juliet/include/Engine/Class.h +++ b/Juliet/include/Engine/Class.h @@ -1,19 +1,20 @@ -#pragma once +#pragma once #include #include #include -struct archive; +struct Archive; -using serialize_fct_type = void (*)(archive*, void* payload); +using serialize_fct_type = void (*)(Archive&, uint16 version, void* payload); struct Class { - uint32 CRC; - uint8 kind; - + uint32 CRC; + uint8 kind; + uint16 version; + const Class* base_class; serialize_fct_type serialize_fct; size_t size_of; size_t alignment; @@ -23,25 +24,36 @@ struct Class #endif }; -consteval Class MakeClass(String name, uint8 kind, size_t size, size_t align, serialize_fct_type fct) +#define DEFINE_CLASS_VERSIONED(cls, version, base_class, serialize_fct) \ + constexpr Class classKind##cls = \ + MakeClass(ConstString(#cls), 0, (version), (base_class), sizeof(cls), alignof(cls), (serialize_fct)); \ + Class* cls::kind = const_cast(&classKind##cls); + +consteval Class MakeClass(String name, uint8 kind, uint16 version, const Class* base_class, size_t size, size_t align, + serialize_fct_type fct) { Class cls = {}; cls.CRC = crc32(name.Str, name.Size); cls.kind = kind; + cls.version = version; + cls.base_class = base_class; cls.size_of = size; cls.alignment = align; cls.serialize_fct = fct; #if JULIET_DEBUG - // TODO: string struct may be cls.Name = name; #endif return cls; } +bool IsA(const Class& query, const Class* target); + template -bool IsA(Class& cls) +bool IsA(const Class& cls) { - return cls.CRC == type::StaticClass->CRC; + return IsA(cls, type::StaticClass); } + +JULIET_API void serialize(Archive& ar, NonNullPtr cls, void* instance); diff --git a/Juliet/include/Graphics/GraphicsConfig.h b/Juliet/include/Graphics/GraphicsConfig.h index 3364491..436ef00 100644 --- a/Juliet/include/Graphics/GraphicsConfig.h +++ b/Juliet/include/Graphics/GraphicsConfig.h @@ -1,9 +1,5 @@ #pragma once -#include - -#include - #if JULIET_DEBUG #define ALLOW_SHADER_HOT_RELOAD 1 #else diff --git a/Juliet/include/Graphics/Mesh.h b/Juliet/include/Graphics/Mesh.h index 267ea2b..088dc7b 100644 --- a/Juliet/include/Graphics/Mesh.h +++ b/Juliet/include/Graphics/Mesh.h @@ -1,18 +1,16 @@ #pragma once -#include #include #include #include #include -#include struct Arena; struct Vertex; -using MeshAssetID = index_t; +using MeshAssetID = index_t; using MaterialAssetID = index_t; -using MeshInstanceID = index_t; +using MeshInstanceID = index_t; struct MeshAsset { @@ -26,7 +24,7 @@ struct MeshAsset struct MaterialAsset { - Vector4 AlbedoColor = {1.0f, 1.0f, 1.0f, 1.0f}; + Vector4 AlbedoColor = { 1.0f, 1.0f, 1.0f, 1.0f }; }; struct MeshInstance diff --git a/Juliet/src/Core/Common/String.cpp b/Juliet/src/Core/Common/String.cpp index c04d315..88ba0b2 100644 --- a/Juliet/src/Core/Common/String.cpp +++ b/Juliet/src/Core/Common/String.cpp @@ -351,8 +351,7 @@ bool ConvertString(StringEncoding from, StringEncoding to, String src, StringBuf { character = kUnknown_UNICODE; } - if ((character >= 0xD800 && character <= 0xDFFF) || (character == 0xFFFE || character == 0xFFFF) || - character > 0x10FFFF) + if ((character >= 0xD800 && character <= 0xDFFF) || (character == 0xFFFE || character == 0xFFFF) || character > 0x10FFFF) { character = kUnknown_UNICODE; } @@ -593,7 +592,7 @@ String StringCopy(NonNullPtr arena, String str) { String result; result.Size = str.Size; - result.Str = static_cast(ArenaPush(arena, str.Size + 1, alignof(char), true JULIET_DEBUG_PARAM("String"))); + result.Str = static_cast(ArenaPush(arena, str.Size + 1, alignof(char), true JULIET_DEBUG_PARAM("String"))); MemCopy(result.Str, str.Str, str.Size); result.Str[result.Size] = 0; return result; @@ -623,3 +622,20 @@ String16 str16_from_8(NonNullPtr arena, String8 in) return result; } +String trim_whitespace(String str) +{ + // Handles windos and unix kind of whitespaces + // Remove any whitespace at the begin of the string + while (str.Size > 0 && (*str.Str == ' ' || *str.Str == '\t' || *str.Str == '\r' || *str.Str == '\n')) + { + str.Str++; + str.Size--; + } + // Remove any whitespace at the end of the string. + while (str.Size > 0 && (str.Str[str.Size - 1] == ' ' || str.Str[str.Size - 1] == '\t' || + str.Str[str.Size - 1] == '\r' || str.Str[str.Size - 1] == '\n')) + { + str.Size--; + } + return str; +} diff --git a/Juliet/src/Core/Common/serialization.cpp b/Juliet/src/Core/Common/serialization.cpp index 2126afb..55cd36b 100644 --- a/Juliet/src/Core/Common/serialization.cpp +++ b/Juliet/src/Core/Common/serialization.cpp @@ -1,10 +1,15 @@ #include #include +#include +#include +#include +#include +#include #include #include -void serialize(archive& ar, void* data, size_t size) +void serialize(Archive& ar, void* data, size_t size) { if (ar.loading) { @@ -19,3 +24,359 @@ void serialize(archive& ar, void* data, size_t size) ar.offset += size; } } + +ParsedArchive tokenize_archive(NonNullPtr arena, ByteBuffer file_buffer) +{ + Assert(file_buffer.Data); + + ParsedArchive archive = {}; + + uint8* cursor = (uint8*)file_buffer.Data; + uint8* end = cursor + file_buffer.Size; + + // Pass 1: count properties + uint32 property_count = 0; + uint8* scan = cursor; + while (scan < end) + { + if (*scan == ';') + { + if (scan == cursor || *(scan - 1) == '\n') + { + property_count++; + } + } + scan++; + } + + if (property_count > 0) + { + // Pass 2: extract properties + NonNullPtr nodes = + ArenaPushArray(arena, property_count JULIET_DEBUG_PARAM("tokenizer nodes")); + + uint32 node_index = 0; + scan = cursor; + + while (scan < end && node_index < property_count) + { + // Skip comments + if (*scan == '#' || (*scan == '/' && scan + 1 < end && *(scan + 1) == '/')) + { + while (scan < end && *scan != '\n') + { + ++scan; + } + ++scan; // skipping trailing \n + continue; + } + + if (*scan == ';') + { + ++scan; // skipping the ; + + // extract property name + uint8* property_name_cursor = scan; + while (scan < end && *scan != '\r' && *scan != '\n') + { + ++scan; + } + + String raw_property_name = { .Str = (char*)property_name_cursor, .Size = (size_t)(scan - property_name_cursor) }; + String property_name = trim_whitespace(raw_property_name); + + Assert(property_name.Str && property_name.Size > 0); + + // skip \r\n + while (scan < end && (*scan == '\r' || *scan == '\n')) + { + ++scan; + } + + // Value block is everything until comment or new line + + uint8* value_start = scan; + uint8* value_end = scan; + + while (scan < end) + { + if (*scan == '\r' || *scan == '\n') + { + break; + } + + if (*scan == '#' || (*scan == '/' && scan + 1 < end && *(scan + 1) == '/')) + { + break; + } + + ++scan; + value_end = scan; + } + + String raw_value = { .Str = (char*)value_start, .Size = static_cast(value_end - value_start) }; + String value = trim_whitespace(raw_value); + + nodes[node_index].key_crc = crc32(property_name); + nodes[node_index].key = StringCopy(arena, property_name); + nodes[node_index].value = value; + nodes[node_index].consumed = false; + node_index++; + } + else + { + ++scan; + } + } + + archive.nodes = nodes.Get(); + archive.property_count = node_index; + } + + return archive; +} + +ArchivePropertyNode* find_property(NonNullPtr archive, uint32 property_crc) +{ + Assert(archive->nodes && archive->property_count > 0); + + ArchivePropertyNode* result = nullptr; + ArchivePropertyNode* nodes = archive->nodes; + for (index_t idx = 0; idx < archive->property_count; ++idx) + { + if (nodes[idx].key_crc == property_crc) + { + nodes[idx].consumed = true; + result = &nodes[idx]; + break; + } + } + + return result; +} + +void write_property_header(Archive& ar, String property_name) +{ + Assert(!ar.loading); + Assert(ar.stream); + Assert(IsValid(property_name)); + IOPrintf(ar.stream, "; %s\n", CStr(property_name)); +} + +bool read_prop(Archive& ar, String value_raw, String& value) +{ + String parsed = value_raw; + + // Ignoring eventual " + if (parsed.Size >= 2 && parsed.Str[0] == '"' && parsed.Str[parsed.Size - 1] == '"') + { + parsed.Str++; + parsed.Size -= 2; + } + + Assert(ar.arena); + value = StringCopy(ar.arena, parsed); + return true; +} + +void write(NonNullPtr stream, String value) +{ + if (ContainsChar(value, ' ')) // Add " " around string with spaces + { + IOPrintf(stream, "\"%.*s\"\n", (int32)value.Size, value.Str); + } + else + { + IOPrintf(stream, "%.*s\n", (int32)value.Size, value.Str); + } +} + +bool read(const char* buffer, float& value, const char** next /* = nullptr */) +{ + bool result = false; + char* end = nullptr; + float parsed = strtof(buffer, &end); + if (end != buffer) + { + value = parsed; + if (next) + { + *next = end; + } + result = true; + } + return result; +} + +void write(NonNullPtr stream, float value) +{ + IOPrintf(stream, "%.9g\n", value); +} + +bool read(const char* buffer, int8& value) +{ + bool result = false; + char* end = nullptr; + int32 parsed = strtol(buffer, &end, 10); + if (end != buffer && parsed >= int8Min && parsed <= int8Max) + { + value = (int8)parsed; + result = true; + } + return result; +} + +void write(NonNullPtr stream, int8 value) +{ + IOPrintf(stream, "%d\n", (int32)value); +} + +bool read(const char* buffer, int16& value) +{ + bool result = false; + char* end = nullptr; + int32 parsed = strtol(buffer, &end, 10); + if (end != buffer && parsed >= int16Min && parsed <= int16Max) + { + value = (int16)parsed; + result = true; + } + return result; +} + +void write(NonNullPtr stream, int16 value) +{ + IOPrintf(stream, "%d\n", (int32)value); +} + +bool read(const char* buffer, int32& value) +{ + char* end = nullptr; + value = strtol(buffer, &end, 10); + return end != buffer; +} + +void write(NonNullPtr stream, int32 value) +{ + IOPrintf(stream, "%d\n", value); +} + +bool read(const char* buffer, int64& value) +{ + char* end = nullptr; + value = strtoll(buffer, &end, 0); + return end != buffer; +} + +void write(NonNullPtr stream, int64 value) +{ + IOPrintf(stream, "%lld\n", value); +} + +bool read(const char* buffer, uint8& value) +{ + bool result = false; + char* end = nullptr; + uint32 parsed = strtoul(buffer, &end, 10); + if (end != buffer && parsed >= uint8Min && parsed <= uint8Max) + { + value = (uint8)parsed; + result = true; + } + return result; +} + +void write(NonNullPtr stream, uint8 value) +{ + IOPrintf(stream, "%u\n", (uint32)value); +} + +bool read(const char* buffer, uint16& value) +{ + bool result = false; + char* end = nullptr; + uint32 parsed = strtoul(buffer, &end, 10); + if (end != buffer && parsed >= uint16Min && parsed <= uint16Max) + { + value = (uint16)parsed; + result = true; + } + return result; +} + +void write(NonNullPtr stream, uint16 value) +{ + IOPrintf(stream, "%u\n", (uint32)value); +} + +bool read(const char* buffer, uint32& value) +{ + char* end = nullptr; + value = strtoul(buffer, &end, 10); + return end != buffer; +} + +void write(NonNullPtr stream, uint32 value) +{ + IOPrintf(stream, "%u\n", value); +} + +bool read(const char* buffer, uint64& value) +{ + char* end = nullptr; + value = strtoull(buffer, &end, 10); + return end != buffer; +} + +void write(NonNullPtr stream, uint64 value) +{ + IOPrintf(stream, "%llu\n", value); +} + +bool read(const char* buffer, bool& value) +{ + bool result = false; + value = buffer[0] == '1'; + if (value || buffer[0] == '0') + { + result = true; + } + return result; +} + +void write(NonNullPtr stream, bool value) +{ + IOPrintf(stream, "%u\n", value); +} + +bool read(const char* buffer, Vector4& value) +{ + bool result = read(buffer, value.x, &buffer); + result &= read(buffer, value.y, &buffer); + result &= read(buffer, value.z, &buffer); + result &= read(buffer, value.w, &buffer); + + return result; +} + +void write(NonNullPtr stream, Vector4 value) +{ + IOPrintf(stream, "%.9g %.9g %.9g %.9g\n", value.x, value.y, value.z, value.w); +} + +#if JULIET_DEBUG +void audit_unconsumed_properties(NonNullPtr archive, String context_name) +{ + Assert(archive->nodes && archive->property_count > 0); + auto* nodes = archive->nodes; + for (index_t idx = 0; idx < archive->property_count; ++idx) + { + if (!nodes[idx].consumed) + { + LogWarning(LogCategory::Core, "[%s] Unconsumed or obsolete property detected: [%s] - CRC 0x%08X (Value: '%.*s')", + CStr(context_name), CStr(nodes[idx].key), nodes[idx].key_crc, + static_cast(nodes[idx].value.Size), nodes[idx].value.Str); + } + } +} +#endif diff --git a/Juliet/src/Core/HAL/Display/Display.cpp b/Juliet/src/Core/HAL/Display/Display.cpp index fde24cf..f781f44 100644 --- a/Juliet/src/Core/HAL/Display/Display.cpp +++ b/Juliet/src/Core/HAL/Display/Display.cpp @@ -1,5 +1,4 @@ -#include -#include +#include #include #include #include diff --git a/Juliet/src/Core/HAL/Filesystem/Filesystem.cpp b/Juliet/src/Core/HAL/Filesystem/Filesystem.cpp index 7419e28..f1f3a6e 100644 --- a/Juliet/src/Core/HAL/Filesystem/Filesystem.cpp +++ b/Juliet/src/Core/HAL/Filesystem/Filesystem.cpp @@ -1,5 +1,4 @@ -#include -#include +#include #include #include #include diff --git a/Juliet/src/Core/HAL/OS/Win32/Win32OS.cpp b/Juliet/src/Core/HAL/OS/Win32/Win32OS.cpp index 2d94758..ac2f916 100644 --- a/Juliet/src/Core/HAL/OS/Win32/Win32OS.cpp +++ b/Juliet/src/Core/HAL/OS/Win32/Win32OS.cpp @@ -1,5 +1,4 @@ -#include -#include +#include #include #include #include diff --git a/Juliet/src/Core/Math/MathRound.cpp b/Juliet/src/Core/Math/MathRound.cpp index 8f24190..8b49adb 100644 --- a/Juliet/src/Core/Math/MathRound.cpp +++ b/Juliet/src/Core/Math/MathRound.cpp @@ -1,5 +1,4 @@ -#include -#include +#include // From MUSL lib https://github.com/rofl0r/musl namespace diff --git a/Juliet/src/Core/Memory/MemoryArenaTests.cpp b/Juliet/src/Core/Memory/MemoryArenaTests.cpp index bb275d9..de970b0 100644 --- a/Juliet/src/Core/Memory/MemoryArenaTests.cpp +++ b/Juliet/src/Core/Memory/MemoryArenaTests.cpp @@ -1,5 +1,4 @@ -#include -#include +#include #include #include diff --git a/Juliet/src/Engine/class.cpp b/Juliet/src/Engine/class.cpp index a7f6d89..043e9ab 100644 --- a/Juliet/src/Engine/class.cpp +++ b/Juliet/src/Engine/class.cpp @@ -1 +1,38 @@ +#include #include + +bool IsA(const Class& query, const Class* target) +{ + bool result = false; + const Class* current = &query; + while (current != nullptr) + { + if (current == target) + { + result = true; + break; + } + current = current->base_class; + } + return result; +} + +void serialize(Archive& ar, NonNullPtr cls, void* instance) +{ + Assert(instance != nullptr); + + uint16 version = cls->version; + if (cls->base_class) + { + serialize(ar, ConstString("class_version"), "class_version"_crc32, version); + } + else + { + serialize(ar, ConstString("version"), "version"_crc32, version); + } + + if (cls->serialize_fct) + { + cls->serialize_fct(ar, version, instance); + } +}