ongoing refactor of serialization and various cleanup
This commit is contained in:
@@ -7,10 +7,18 @@ use static_cast or reinterpret_cast but not parenthesis for casting.
|
|||||||
No exceptions
|
No exceptions
|
||||||
Use [[nodiscard]] when risk of memory leak (anything returning pointer)
|
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 &
|
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.
|
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.
|
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.
|
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.
|
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
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Core/Common/CoreTypes.h>
|
|
||||||
|
|
||||||
constexpr index_t kPlayCamera = 0;
|
constexpr index_t kPlayCamera = 0;
|
||||||
constexpr index_t kDebugCamera = 1;
|
constexpr index_t kDebugCamera = 1;
|
||||||
|
|||||||
+33
-29
@@ -24,14 +24,14 @@ void ShutdownWorld(NonNullPtr<World> world)
|
|||||||
world->WorldArena = nullptr;
|
world->WorldArena = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddToWorld(NonNullPtr<World> world, NonNullPtr<Entity> entity)
|
void AddToWorld(NonNullPtr<World> /*world*/, NonNullPtr<Entity> /*entity*/)
|
||||||
{
|
{
|
||||||
// RegisterEntity(*world->EntityManager, entity.Get());
|
// 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));
|
Assert(IsValid(filename));
|
||||||
|
|
||||||
@@ -41,28 +41,33 @@ void Serialize(archive& ar, World& world, String filename)
|
|||||||
{
|
{
|
||||||
// Load
|
// Load
|
||||||
ByteBuffer fileBuffer = LoadFile(ar.arena, filename);
|
ByteBuffer fileBuffer = LoadFile(ar.arena, filename);
|
||||||
if (fileBuffer.Size >= sizeof(WorldFileHeader))
|
|
||||||
{
|
|
||||||
ar.base_ptr = fileBuffer.Data;
|
|
||||||
|
|
||||||
WorldFileHeader header;
|
// TEST SERIALIZATION
|
||||||
serialize_elem(ar, header);
|
ParsedArchive archive = tokenize_archive(ar.arena, fileBuffer);
|
||||||
Assert(header.Magic == kWorldMagic);
|
// ArchivePropertyNode* property = find_property(&archive, "Position"_crc32);
|
||||||
Assert(header.Version == kWorldVersion);
|
audit_unconsumed_properties(&archive, ConstString("World"));
|
||||||
|
|
||||||
for (typed_entity_array& type : entityManager.by_type)
|
// if (fileBuffer.Size >= sizeof(WorldFileHeader))
|
||||||
{
|
// {
|
||||||
serialize_elem(ar, type.count);
|
// ar.base_ptr = fileBuffer.Data;
|
||||||
|
//
|
||||||
if (type.count > 0)
|
// WorldFileHeader header;
|
||||||
{
|
// serialize_elem(ar, header);
|
||||||
|
// Assert(header.Magic == kWorldMagic);
|
||||||
// Unserialize the base entity to get informations
|
//
|
||||||
Entity entity;
|
// for (typed_entity_array& type : entityManager.by_type)
|
||||||
serialize(ar, &entity);
|
// {
|
||||||
}
|
// serialize_elem(ar, type.count);
|
||||||
}
|
//
|
||||||
}
|
// if (type.count > 0)
|
||||||
|
// {
|
||||||
|
//
|
||||||
|
// // Unserialize the base entity to get informations
|
||||||
|
// Entity entity;
|
||||||
|
// // serialize(ar, &entity);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -74,7 +79,6 @@ void Serialize(archive& ar, World& world, String filename)
|
|||||||
// Headers
|
// Headers
|
||||||
auto* header = ArenaPushStruct<WorldFileHeader>(ar.arena);
|
auto* header = ArenaPushStruct<WorldFileHeader>(ar.arena);
|
||||||
header->Magic = kWorldMagic;
|
header->Magic = kWorldMagic;
|
||||||
header->Version = kWorldVersion;
|
|
||||||
|
|
||||||
ar.base_ptr = header;
|
ar.base_ptr = header;
|
||||||
|
|
||||||
@@ -84,12 +88,12 @@ void Serialize(archive& ar, World& world, String filename)
|
|||||||
serialize_elem(ar, type.count);
|
serialize_elem(ar, type.count);
|
||||||
auto* element = type.array;
|
auto* element = type.array;
|
||||||
uint8* rawElement = reinterpret_cast<uint8*>(element);
|
uint8* rawElement = reinterpret_cast<uint8*>(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)
|
for (index_t idx = 0; idx < type.count; ++idx)
|
||||||
{
|
{
|
||||||
// Todo : utils
|
// Todo : utils
|
||||||
// Get base entity from type
|
// Get base entity from type
|
||||||
Entity* entity = reinterpret_cast<entity_template*>(rawElement + (idx * stride))->Base;
|
Entity* entity = reinterpret_cast<entity_template*>(rawElement + (idx * stride))->base;
|
||||||
serialize(ar, entity);
|
serialize(ar, entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -164,7 +168,7 @@ void Serialize(archive& ar, World& world, String filename)
|
|||||||
// return true;
|
// return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] bool LoadWorld(World& world, String filename)
|
[[nodiscard]] bool LoadWorld(World& /*world*/, String filename)
|
||||||
{
|
{
|
||||||
Assert(IsValid(filename));
|
Assert(IsValid(filename));
|
||||||
// Assert(world.WorldArena != nullptr);
|
// Assert(world.WorldArena != nullptr);
|
||||||
@@ -236,13 +240,13 @@ void RenderWorldEditorUI(World& world)
|
|||||||
|
|
||||||
if (ImGui::Button("Save World"))
|
if (ImGui::Button("Save World"))
|
||||||
{
|
{
|
||||||
archive data{ .arena = temp.Arena, .loading = false };
|
Archive data = { .arena = temp.Arena, .loading = false };
|
||||||
Serialize(data, world, path);
|
Serialize(data, world, path);
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::Button("Load World"))
|
if (ImGui::Button("Load World"))
|
||||||
{
|
{
|
||||||
archive data{ .arena = temp.Arena, .loading = true };
|
Archive data{ .arena = temp.Arena, .loading = true };
|
||||||
Serialize(data, world, path);
|
Serialize(data, world, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-4
@@ -1,20 +1,18 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Core/Common/CoreTypes.h>
|
|
||||||
#include <Core/Common/NonNullPtr.h>
|
#include <Core/Common/NonNullPtr.h>
|
||||||
#include <Core/Common/String.h>
|
#include <Core/Common/String.h>
|
||||||
#include <Core/Container/Vector.h>
|
#include <Core/Container/Vector.h>
|
||||||
#include <Core/Memory/MemoryArena.h>
|
#include <Core/Memory/MemoryArena.h>
|
||||||
#include <Entity/Entity.h>
|
#include <Entity/Entity.h>
|
||||||
|
|
||||||
struct archive;
|
struct Archive;
|
||||||
struct EntityManager;
|
struct EntityManager;
|
||||||
|
|
||||||
#pragma pack(push, 1)
|
#pragma pack(push, 1)
|
||||||
struct WorldFileHeader
|
struct WorldFileHeader
|
||||||
{
|
{
|
||||||
uint32 Magic = 0x444C574A; // 'JWLD' in little-endian
|
uint32 Magic = 0x444C574A; // 'JWLD' in little-endian
|
||||||
uint32 Version = 1;
|
|
||||||
};
|
};
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|
||||||
@@ -33,7 +31,7 @@ void ShutdownWorld(NonNullPtr<World> world);
|
|||||||
void AddToWorld(NonNullPtr<World> world, NonNullPtr<Entity> entity);
|
void AddToWorld(NonNullPtr<World> world, NonNullPtr<Entity> entity);
|
||||||
void RemoveWorldEntity(World& world, size_t index);
|
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
|
#if JULIET_EDITOR
|
||||||
void RenderWorldEditorUI(World& world);
|
void RenderWorldEditorUI(World& world);
|
||||||
|
|||||||
+13
-16
@@ -2,24 +2,21 @@
|
|||||||
|
|
||||||
#include <Core/Common/serialization.h>
|
#include <Core/Common/serialization.h>
|
||||||
|
|
||||||
DEFINE_ENTITY(Inert);
|
DEFINE_CLASS_VERSIONED(Entity, 1, nullptr, nullptr)
|
||||||
|
|
||||||
void serialize(archive& ar, NonNullPtr<Entity> entity)
|
DEFINE_ENTITY_VERSIONED(Inert, 1, nullptr)
|
||||||
{
|
|
||||||
serialize_elem(ar, entity->ID);
|
|
||||||
|
|
||||||
if (ar.loading)
|
void serialize(Archive& ar, NonNullPtr<Entity> entity)
|
||||||
{
|
{
|
||||||
serialize_elem(ar, entity->Kind->kind);
|
// Entity fields
|
||||||
}
|
serialize(ar, Entity::kind, entity.Get());
|
||||||
else
|
|
||||||
{
|
|
||||||
uint8 kind;
|
|
||||||
serialize_elem(ar, kind);
|
|
||||||
entity->Kind = kEntity_type_class_ptr[kind];
|
|
||||||
}
|
|
||||||
|
|
||||||
serialize_elem(ar, entity->X);
|
SERIALIZE(ar, id, entity->ID);
|
||||||
serialize_elem(ar, entity->Y);
|
SERIALIZE(ar, position, entity->position);
|
||||||
serialize_elem(ar, entity->Z);
|
|
||||||
|
// Derived fields
|
||||||
|
if (entity->derived_kind != nullptr && entity->derived != nullptr)
|
||||||
|
{
|
||||||
|
serialize(ar, entity->derived_kind, entity->derived);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-27
@@ -2,24 +2,19 @@
|
|||||||
|
|
||||||
#include <Core/Common/CoreUtils.h>
|
#include <Core/Common/CoreUtils.h>
|
||||||
#include <Core/Common/EnumUtils.h>
|
#include <Core/Common/EnumUtils.h>
|
||||||
|
#include <Core/Math/Vector.h>
|
||||||
#include <Core/Memory/Allocator.h>
|
#include <Core/Memory/Allocator.h>
|
||||||
#include <Core/Memory/MemoryArena.h>
|
#include <Core/Memory/MemoryArena.h>
|
||||||
#include <Engine/Class.h>
|
#include <Engine/Class.h>
|
||||||
|
|
||||||
#define DECLARE_ENTITY() \
|
#define DECLARE_ENTITY() \
|
||||||
Entity* Base; \
|
Entity* base; \
|
||||||
static Class* Kind;
|
static Class* kind;
|
||||||
|
|
||||||
// Will register the class globally at launch
|
#define DEFINE_ENTITY_VERSIONED(entity, version, serialize_fct) \
|
||||||
#define DEFINE_ENTITY(entity) \
|
constexpr Class entityKind##entity = MakeClass(ConstString(#entity), (uint8)Entity_Type::entity, (version), \
|
||||||
Class entityKind##entity = \
|
&classKindEntity, sizeof(entity), alignof(entity), (serialize_fct)); \
|
||||||
MakeClass(ConstString(#entity), (uint8)Entity_Type::entity, sizeof(entity), alignof(entity), nullptr); \
|
Class* entity::kind = const_cast<Class*>(&entityKind##entity);
|
||||||
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;
|
|
||||||
|
|
||||||
struct EntityManager;
|
struct EntityManager;
|
||||||
using DerivedType = void*;
|
using DerivedType = void*;
|
||||||
@@ -27,12 +22,12 @@ using EntityID = uint64_t;
|
|||||||
|
|
||||||
struct Entity final
|
struct Entity final
|
||||||
{
|
{
|
||||||
|
static Class* kind;
|
||||||
|
|
||||||
EntityID ID = 0;
|
EntityID ID = 0;
|
||||||
Class* Kind = nullptr;
|
Class* derived_kind = nullptr;
|
||||||
DerivedType Derived = nullptr;
|
DerivedType derived = nullptr;
|
||||||
float X = 0.0f;
|
Vector4 position = {};
|
||||||
float Y = 0.0f;
|
|
||||||
float Z = 0.0f;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Inert
|
struct Inert
|
||||||
@@ -60,7 +55,7 @@ inline const char* kEntity_type_names[] = {
|
|||||||
};
|
};
|
||||||
#undef AS_STR
|
#undef AS_STR
|
||||||
|
|
||||||
#define AS_CLASS(name) name::Kind
|
#define AS_CLASS(name) name::kind
|
||||||
inline Class* kEntity_type_class_ptr[]
|
inline Class* kEntity_type_class_ptr[]
|
||||||
{
|
{
|
||||||
ENTITY_TYPE_LIST(AS_CLASS)
|
ENTITY_TYPE_LIST(AS_CLASS)
|
||||||
@@ -80,8 +75,8 @@ struct entity_template
|
|||||||
//
|
//
|
||||||
template <typename EntityType>
|
template <typename EntityType>
|
||||||
concept EntityConcept = requires(EntityType entity) {
|
concept EntityConcept = requires(EntityType entity) {
|
||||||
{ EntityType::Kind } -> std::convertible_to<const Class*>;
|
{ EntityType::kind } -> std::convertible_to<const Class*>;
|
||||||
requires std::same_as<decltype(entity.Base), Entity*>;
|
requires std::same_as<decltype(entity.base), Entity*>;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename EntityType>
|
template <typename EntityType>
|
||||||
@@ -89,7 +84,7 @@ template <typename EntityType>
|
|||||||
[[nodiscard]] bool IsA(const Entity* entity)
|
[[nodiscard]] bool IsA(const Entity* entity)
|
||||||
{
|
{
|
||||||
Assert(entity != nullptr);
|
Assert(entity != nullptr);
|
||||||
return entity->Kind == EntityType::Kind;
|
return entity->derived_kind == EntityType::kind;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename EntityType>
|
template <typename EntityType>
|
||||||
@@ -98,10 +93,11 @@ template <typename EntityType>
|
|||||||
{
|
{
|
||||||
EntityType result;
|
EntityType result;
|
||||||
Entity base;
|
Entity base;
|
||||||
base.X = x;
|
base.position.x = x;
|
||||||
base.Y = y;
|
base.position.y = y;
|
||||||
base.Z = z;
|
base.position.z = z;
|
||||||
base.Kind = EntityType::Kind;
|
base.position.w = 1.0f;
|
||||||
|
base.derived_kind = EntityType::kind;
|
||||||
|
|
||||||
return (EntityType*)RegisterEntity(manager, &base, &result);
|
return (EntityType*)RegisterEntity(manager, &base, &result);
|
||||||
}
|
}
|
||||||
@@ -112,7 +108,7 @@ template <typename EntityType>
|
|||||||
{
|
{
|
||||||
Assert(entity != nullptr);
|
Assert(entity != nullptr);
|
||||||
Assert(IsA<EntityType>(entity));
|
Assert(IsA<EntityType>(entity));
|
||||||
return static_cast<EntityType*>(entity->Derived);
|
return static_cast<EntityType*>(entity->derived);
|
||||||
}
|
}
|
||||||
|
|
||||||
void serialize(archive& ar, NonNullPtr<Entity> entity);
|
void serialize(Archive& ar, NonNullPtr<Entity> entity);
|
||||||
|
|||||||
@@ -46,30 +46,26 @@ EntityManager& GetEntityManager()
|
|||||||
entity_template* RegisterEntity(EntityManager& manager, Entity* base, DerivedType entity)
|
entity_template* RegisterEntity(EntityManager& manager, Entity* base, DerivedType entity)
|
||||||
{
|
{
|
||||||
base->ID = EntityManager::ID++;
|
base->ID = EntityManager::ID++;
|
||||||
base->Derived = entity;
|
base->derived = entity;
|
||||||
|
|
||||||
manager.Entities.PushBack(*base);
|
manager.Entities.PushBack(*base);
|
||||||
|
|
||||||
auto* ptr = (entity_template*)ArenaPushSize(manager.by_type[base->Kind->kind].arena, base->Kind->size_of, base->Kind->alignment,
|
auto* ptr = (entity_template*)ArenaPushSize(manager.by_type[base->derived_kind->kind].arena,
|
||||||
false JULIET_DEBUG_PARAM(kEntity_type_names[base->Kind->kind]));
|
base->derived_kind->size_of, base->derived_kind->alignment,
|
||||||
MemCopy(ptr, entity, base->Kind->size_of);
|
false JULIET_DEBUG_PARAM(kEntity_type_names[base->derived_kind->kind]));
|
||||||
manager.by_type[base->Kind->kind].count += 1;
|
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;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RegisterBaseEntity(EntityManager& manager, Entity&& base)
|
|
||||||
{
|
|
||||||
GetEntityManager().Entities.PushBack(std::move(base));
|
|
||||||
}
|
|
||||||
|
|
||||||
void UpdateEntityManager(EntityManager& manager)
|
void UpdateEntityManager(EntityManager& manager)
|
||||||
{
|
{
|
||||||
// Todo : inert by definition dont move, but this is for test
|
// Todo : inert by definition dont move, but this is for test
|
||||||
@@ -79,7 +75,9 @@ void UpdateEntityManager(EntityManager& manager)
|
|||||||
Inert* inert = reinterpret_cast<Inert*>(by_type.array) + i;
|
Inert* inert = reinterpret_cast<Inert*>(by_type.array) + i;
|
||||||
if (inert->MeshInstance != indexMax)
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Core/Common/CoreTypes.h>
|
|
||||||
#include <Core/Container/Vector.h>
|
#include <Core/Container/Vector.h>
|
||||||
#include <Entity/Entity.h>
|
#include <Entity/Entity.h>
|
||||||
|
|
||||||
@@ -28,5 +27,4 @@ void InitEntityManager(NonNullPtr<World> world);
|
|||||||
void ShutdownEntityManager();
|
void ShutdownEntityManager();
|
||||||
EntityManager& GetEntityManager();
|
EntityManager& GetEntityManager();
|
||||||
entity_template* RegisterEntity(EntityManager& manager, Entity* base, DerivedType entity);
|
entity_template* RegisterEntity(EntityManager& manager, Entity* base, DerivedType entity);
|
||||||
void RegisterBaseEntity(EntityManager& manager, Entity&& base);
|
|
||||||
void UpdateEntityManager(EntityManager& manager);
|
void UpdateEntityManager(EntityManager& manager);
|
||||||
|
|||||||
@@ -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.
|
- **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.
|
- **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.
|
- **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`.
|
- **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
|
; AssetType
|
||||||
Entity
|
Entity
|
||||||
|
|
||||||
; BaseVersion
|
; Version
|
||||||
1
|
1
|
||||||
|
|
||||||
; EntityID
|
; EntityID
|
||||||
@@ -712,10 +712,15 @@ In entity-component systems or object-oriented engine hierarchies, entities cons
|
|||||||
#### The Fragility of Monolithic Versioning
|
#### 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.
|
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
|
#### The Two-Tier Solution: Universal `; Version` + Optional `; ClassVersion`
|
||||||
Juliet decouples versioning into two independent tiers:
|
To keep `.jasset` files clean and unified across all asset types:
|
||||||
- **Tier 1: Base Engine Version (`kEntityBaseVersion`)**: Declared centrally in `Entity.h`. Governs `Entity` base fields.
|
- **All `.jasset` files declare a universal `; Version` tag**:
|
||||||
- **Tier 2: Derived Class Version (`Class::Version`)**: Declared per-entity class in `Class.h` and initialized in `DEFINE_ENTITY_VERSIONED`.
|
- 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
|
; AssetType
|
||||||
Entity
|
Entity
|
||||||
; BaseVersion ------> Governed by kEntityBaseVersion in Entity.h
|
; Version ------> Universal asset version (kEntityBaseVersion for Entity)
|
||||||
1
|
1
|
||||||
; EntityID
|
; EntityID
|
||||||
0x0000000000000001
|
0x0000000000000001
|
||||||
@@ -732,32 +737,30 @@ Juliet decouples versioning into two independent tiers:
|
|||||||
; Position
|
; Position
|
||||||
0.0 0.0 0.0
|
0.0 0.0 0.0
|
||||||
------------------------------------------------------------------------
|
------------------------------------------------------------------------
|
||||||
; ClassVersion ------> Governed by Class::Version in Class.h
|
; ClassVersion ------> Optional derived version (Class::Version in Class.h)
|
||||||
2
|
2
|
||||||
; MeshInstance
|
; MeshInstance
|
||||||
42
|
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
|
```cpp
|
||||||
constexpr uint32 kEntityBaseVersion = 1;
|
using serialize_fct_type = void (*)(archive& ar, void* payload, uint16 version);
|
||||||
|
|
||||||
void SerializeEntityBase(archive& ar, NonNullPtr<Entity> entity);
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Derived Class Version (`Juliet/include/Engine/Class.h`)
|
|
||||||
```cpp
|
|
||||||
struct Class
|
struct Class
|
||||||
{
|
{
|
||||||
uint32 CRC;
|
uint32 CRC;
|
||||||
uint8 kind;
|
uint8 kind;
|
||||||
uint16 Version; // Added derived class version
|
uint16 Version; // Struct schema version
|
||||||
serialize_fct_type serialize_fct;
|
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 size_of;
|
||||||
size_t alignment;
|
size_t alignment;
|
||||||
|
|
||||||
@@ -766,12 +769,14 @@ struct Class
|
|||||||
#endif
|
#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 = {};
|
Class cls = {};
|
||||||
cls.CRC = crc32(name.Str, name.Size);
|
cls.CRC = crc32(name.Str, name.Size);
|
||||||
cls.kind = kind;
|
cls.kind = kind;
|
||||||
cls.Version = version;
|
cls.Version = version;
|
||||||
|
cls.BaseClass = baseClass;
|
||||||
cls.size_of = size;
|
cls.size_of = size;
|
||||||
cls.alignment = align;
|
cls.alignment = align;
|
||||||
cls.serialize_fct = fct;
|
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
|
```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) \
|
#define DEFINE_ENTITY_VERSIONED(entity, version, serialize_fct) \
|
||||||
Class entityKind##entity = \
|
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;
|
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
|
```cpp
|
||||||
void Serialize(archive& ar, NonNullPtr<Entity> entity)
|
void Serialize(archive& ar, NonNullPtr<Entity> entity)
|
||||||
{
|
{
|
||||||
Assert(entity.Get() != nullptr);
|
Assert(entity.Get() != nullptr);
|
||||||
|
|
||||||
// --- Tier 1: Base Entity Serialization ---
|
// 1. Serialize Base Entity using Entity's own Class descriptor (Entity::Kind)
|
||||||
if (ar.IsSaving())
|
SerializeClassInstance(ar, Entity::Kind, entity.Get());
|
||||||
{
|
|
||||||
uint32 baseVer = kEntityBaseVersion;
|
|
||||||
SERIALIZE_PROP(ar, baseVer);
|
|
||||||
SERIALIZE_PROP(ar, entity->ID);
|
|
||||||
|
|
||||||
String kindStr = WrapString(kEntity_type_names[entity->Kind->kind]);
|
// 2. Serialize Derived Entity using its Class descriptor (entity->DerivedKind)
|
||||||
SerializeProp(ar, "Kind", "Kind"_crc32, kindStr);
|
if (entity->DerivedKind != nullptr && entity->Derived != nullptr)
|
||||||
|
|
||||||
SerializeProp(ar, "Position", "Position"_crc32, entity->X, entity->Y, entity->Z);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
uint32 baseVer = 0;
|
SerializeClassInstance(ar, entity->DerivedKind, entity->Derived);
|
||||||
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<uint16>(classVer);
|
|
||||||
}
|
|
||||||
|
|
||||||
entity->Kind->serialize_fct(&ar, 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.
|
Retaining deprecated members in active C++ structs creates code clutter, wastes memory, and invites bugs.
|
||||||
|
|
||||||
### 7.2 Deprecation Primitives (`ReadDeprecated*`)
|
### 7.2 Stack-Allocated Migration via Standard Serialization
|
||||||
Deprecation primitives allow serializers to ingest obsolete properties exclusively during loading without polluting modern structs or writing deprecated keys back to disk during saving.
|
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
|
When an asset property is deprecated, restructured, or renamed:
|
||||||
bool ReadDeprecated(archive& ar, const char* keyName, uint32 keyCRC, float& outVal)
|
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.
|
||||||
Assert(keyName != nullptr);
|
3. A local variable of the legacy type is declared on the stack.
|
||||||
if (ar.IsSaving())
|
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.
|
||||||
return false; // Deprecated fields are never saved
|
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.
|
||||||
}
|
|
||||||
|
|
||||||
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> 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;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 7.3 In-Place Migration Pattern
|
### 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
|
```cpp
|
||||||
struct Projectile
|
struct Projectile
|
||||||
@@ -976,39 +919,40 @@ struct Projectile
|
|||||||
float Damage = 50.0f;
|
float Damage = 50.0f;
|
||||||
};
|
};
|
||||||
|
|
||||||
void SerializeProjectile(archive* arPtr, void* payload)
|
void SerializeProjectile(Archive* arPtr, void* payload)
|
||||||
{
|
{
|
||||||
Assert(arPtr != nullptr);
|
Assert(arPtr != nullptr);
|
||||||
Assert(payload != nullptr);
|
Assert(payload != nullptr);
|
||||||
auto& ar = *arPtr;
|
auto& ar = *arPtr;
|
||||||
auto* projectile = static_cast<Projectile*>(payload);
|
auto* projectile = static_cast<Projectile*>(payload);
|
||||||
|
|
||||||
if (ar.IsSaving())
|
if (ar.loading)
|
||||||
{
|
{
|
||||||
SERIALIZE_PROP(ar, projectile->VelocityX);
|
SERIALIZE(ar, Damage, projectile->Damage);
|
||||||
SERIALIZE_PROP(ar, projectile->VelocityY);
|
|
||||||
SERIALIZE_PROP(ar, projectile->Damage);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SERIALIZE_PROP(ar, projectile->Damage);
|
|
||||||
|
|
||||||
if (ar.ClassVersion < 2)
|
if (ar.class_version < 2)
|
||||||
{
|
{
|
||||||
// Migration from v1: scalar 'Speed' converted to 'VelocityX'
|
// Migration from v1: scalar 'Speed' converted to 'VelocityX' on the stack
|
||||||
float legacySpeed = 0.0f;
|
float deprecated_speed = 0.0f;
|
||||||
if (ReadDeprecated(ar, "Speed", "Speed"_crc32, legacySpeed))
|
if (SERIALIZE(ar, Speed, deprecated_speed))
|
||||||
{
|
{
|
||||||
projectile->VelocityX = legacySpeed;
|
projectile->VelocityX = deprecated_speed;
|
||||||
projectile->VelocityY = 0.0f;
|
projectile->VelocityY = 0.0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
SERIALIZE_PROP(ar, projectile->VelocityX);
|
SERIALIZE(ar, VelocityX, projectile->VelocityX);
|
||||||
SERIALIZE_PROP(ar, projectile->VelocityY);
|
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
|
v
|
||||||
+-------------------------------------------------------------------------+
|
+-------------------------------------------------------------------------+
|
||||||
| Phase 3: Archive Struct & SerializeProp Helpers |
|
| Phase 3: Archive Struct & Serialize Helpers |
|
||||||
| - Upgrade archive in serialization.h with ArchiveMode |
|
| - Upgrade Archive in serialization.h with loading / version state |
|
||||||
| - Implement primitive, vector, and string SerializeProp helpers |
|
| - Implement primitive, vector, and string serialize helpers |
|
||||||
| - Implement ReadDeprecated* primitives |
|
| - Support stack-allocated schema migration |
|
||||||
+-------------------------------------------------------------------------+
|
+-------------------------------------------------------------------------+
|
||||||
|
|
|
|
||||||
v
|
v
|
||||||
@@ -1068,12 +1012,11 @@ void SerializeProjectile(archive* arPtr, void* payload)
|
|||||||
- Implement `TokenizeTextArchive(NonNullPtr<Arena> arena, ByteBuffer buffer)`.
|
- Implement `TokenizeTextArchive(NonNullPtr<Arena> arena, ByteBuffer buffer)`.
|
||||||
- Implement `FindProperty` and `AuditUnconsumedProperties`.
|
- Implement `FindProperty` and `AuditUnconsumedProperties`.
|
||||||
|
|
||||||
#### Phase 3: Archive Struct & `SerializeProp` Helpers
|
#### Phase 3: Archive Struct & `serialize` Helpers
|
||||||
1. **Target**: `Juliet/include/Core/Common/serialization.h`
|
1. **Target**: `Juliet/include/Core/Common/serialization.h`
|
||||||
- Introduce `enum class ArchiveMode : uint8`.
|
- Upgrade `struct Archive` with stream pointer, property array, `loading` flag, and versions (`base_version`, `class_version`).
|
||||||
- Upgrade `struct archive` with stream pointer, property array, mode, and versions.
|
- Implement overloaded `serialize`, `read_prop`, and `write` for `float`, `int32`, `uint64`, `bool`, vectors, and `String`.
|
||||||
- Implement overloaded `SerializeProp` 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)`.
|
||||||
- Implement `ReadDeprecated`, `ReadDeprecatedVec2`, `ReadDeprecatedString`.
|
|
||||||
|
|
||||||
#### Phase 4: Entity & World Integration
|
#### Phase 4: Entity & World Integration
|
||||||
1. **Target**: `Game/Entity/Entity.h` and `Game/Entity/Entity.cpp`
|
1. **Target**: `Game/Entity/Entity.h` and `Game/Entity/Entity.cpp`
|
||||||
@@ -1240,35 +1183,35 @@ namespace UnitTest
|
|||||||
float VelocityY = 0.0f;
|
float VelocityY = 0.0f;
|
||||||
};
|
};
|
||||||
|
|
||||||
void SerializeLegacyWeapon(archive* arPtr, void* payload)
|
void SerializeLegacyWeapon(Archive* arPtr, void* payload)
|
||||||
{
|
{
|
||||||
Assert(arPtr != nullptr);
|
Assert(arPtr != nullptr);
|
||||||
Assert(payload != nullptr);
|
Assert(payload != nullptr);
|
||||||
auto& ar = *arPtr;
|
auto& ar = *arPtr;
|
||||||
auto* weapon = static_cast<LegacyWeapon*>(payload);
|
auto* weapon = static_cast<LegacyWeapon*>(payload);
|
||||||
|
|
||||||
if (ar.IsSaving())
|
if (ar.loading)
|
||||||
{
|
{
|
||||||
SERIALIZE_PROP(ar, weapon->VelocityX);
|
if (ar.class_version < 2)
|
||||||
SERIALIZE_PROP(ar, weapon->VelocityY);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
if (ar.ClassVersion < 2)
|
float deprecated_speed = 0.0f;
|
||||||
|
if (SERIALIZE(ar, Speed, deprecated_speed))
|
||||||
{
|
{
|
||||||
float oldSpeed = 0.0f;
|
weapon->VelocityX = deprecated_speed;
|
||||||
if (ReadDeprecated(ar, "Speed", "Speed"_crc32, oldSpeed))
|
|
||||||
{
|
|
||||||
weapon->VelocityX = oldSpeed;
|
|
||||||
weapon->VelocityY = 0.0f;
|
weapon->VelocityY = 0.0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
SERIALIZE_PROP(ar, weapon->VelocityX);
|
SERIALIZE(ar, VelocityX, weapon->VelocityX);
|
||||||
SERIALIZE_PROP(ar, weapon->VelocityY);
|
SERIALIZE(ar, VelocityY, weapon->VelocityY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SERIALIZE(ar, VelocityX, weapon->VelocityX);
|
||||||
|
SERIALIZE(ar, VelocityY, weapon->VelocityY);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_ENTITY_VERSIONED(LegacyWeapon, 2, SerializeLegacyWeapon);
|
DEFINE_ENTITY_VERSIONED(LegacyWeapon, 2, SerializeLegacyWeapon);
|
||||||
@@ -1279,7 +1222,7 @@ namespace UnitTest
|
|||||||
|
|
||||||
// Simulated v1 file containing obsolete 'Speed'
|
// Simulated v1 file containing obsolete 'Speed'
|
||||||
const char* v1Content =
|
const char* v1Content =
|
||||||
"; ClassVersion\n"
|
"; class_version\n"
|
||||||
"1\n"
|
"1\n"
|
||||||
"; Speed\n"
|
"; Speed\n"
|
||||||
"75.500000\n";
|
"75.500000\n";
|
||||||
@@ -1289,14 +1232,13 @@ namespace UnitTest
|
|||||||
.Size = strlen(v1Content)
|
.Size = strlen(v1Content)
|
||||||
};
|
};
|
||||||
|
|
||||||
ParsedTextArchive parsed = TokenizeTextArchive(temp.Arena, buffer);
|
ParsedArchive parsed = tokenize_archive(temp.Arena, buffer);
|
||||||
|
|
||||||
archive ar = {};
|
Archive ar = {};
|
||||||
ar.ArenaInstance = temp.Arena;
|
ar.arena = temp.Arena;
|
||||||
ar.Mode = ArchiveMode::LoadingText;
|
ar.loading = true;
|
||||||
ar.Properties = parsed.Nodes;
|
ar.base = parsed;
|
||||||
ar.PropertyCount = parsed.PropertyCount;
|
ar.class_version = 1;
|
||||||
ar.ClassVersion = 1;
|
|
||||||
|
|
||||||
LegacyWeapon weapon;
|
LegacyWeapon weapon;
|
||||||
SerializeLegacyWeapon(&ar, &weapon);
|
SerializeLegacyWeapon(&ar, &weapon);
|
||||||
|
|||||||
@@ -205,17 +205,17 @@ 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):
|
The implementation replaces the flawed `RegisterEntity` routine in [`Game/Entity/EntityManager.cpp`](file:///w:/Classified/Juliet/Game/Entity/EntityManager.cpp):
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
[[nodiscard]] Entity* AllocateEntity(EntityManager& manager, Class* classPtr)
|
[[nodiscard]] Entity* AllocateEntity(EntityManager& manager, Class* derivedClassPtr)
|
||||||
{
|
{
|
||||||
Assert(classPtr != nullptr);
|
Assert(derivedClassPtr != nullptr);
|
||||||
Assert(classPtr->kind < ENTITY(Count));
|
Assert(derivedClassPtr->kind < ENTITY(Count));
|
||||||
Assert(classPtr->size_of >= sizeof(entity_template));
|
Assert(derivedClassPtr->size_of >= sizeof(entity_template));
|
||||||
Assert(classPtr->alignment > 0);
|
Assert(derivedClassPtr->alignment > 0);
|
||||||
|
|
||||||
// 1. Allocate uninitialized Base Entity in the contiguous VectorArena
|
// 1. Allocate uninitialized Base Entity in the contiguous VectorArena
|
||||||
Entity baseTemplate{};
|
Entity baseTemplate{};
|
||||||
baseTemplate.ID = EntityManager::ID++;
|
baseTemplate.ID = EntityManager::ID++;
|
||||||
baseTemplate.Kind = classPtr;
|
baseTemplate.DerivedKind = derivedClassPtr;
|
||||||
baseTemplate.Derived = nullptr;
|
baseTemplate.Derived = nullptr;
|
||||||
baseTemplate.X = 0.0f;
|
baseTemplate.X = 0.0f;
|
||||||
baseTemplate.Y = 0.0f;
|
baseTemplate.Y = 0.0f;
|
||||||
@@ -227,14 +227,14 @@ The implementation replaces the flawed `RegisterEntity` routine in [`Game/Entity
|
|||||||
Assert(basePtr != nullptr);
|
Assert(basePtr != nullptr);
|
||||||
|
|
||||||
// 2. Allocate zeroed derived component memory in the typed arena
|
// 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);
|
Assert(typedArray.arena != nullptr);
|
||||||
|
|
||||||
void* rawMemory = ArenaPushSize(
|
void* rawMemory = ArenaPushSize(
|
||||||
typedArray.arena,
|
typedArray.arena,
|
||||||
classPtr->size_of,
|
derivedClassPtr->size_of,
|
||||||
classPtr->alignment,
|
derivedClassPtr->alignment,
|
||||||
true JULIET_DEBUG_PARAM(kEntity_type_names[classPtr->kind]));
|
true JULIET_DEBUG_PARAM(kEntity_type_names[derivedClassPtr->kind]));
|
||||||
Assert(rawMemory != nullptr);
|
Assert(rawMemory != nullptr);
|
||||||
|
|
||||||
auto* derivedTemplate = reinterpret_cast<entity_template*>(rawMemory);
|
auto* derivedTemplate = reinterpret_cast<entity_template*>(rawMemory);
|
||||||
@@ -330,9 +330,9 @@ entity_instance
|
|||||||
0x0100000000000042
|
0x0100000000000042
|
||||||
; class
|
; class
|
||||||
Inert
|
Inert
|
||||||
; base_version
|
; version
|
||||||
1
|
1
|
||||||
; derived_version
|
; class_version
|
||||||
1
|
1
|
||||||
; position
|
; position
|
||||||
0.43 0.32 1.56
|
0.43 0.32 1.56
|
||||||
@@ -342,7 +342,8 @@ Inert
|
|||||||
|
|
||||||
#### Important: No Header Structs for Derived Types
|
#### 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.
|
- **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
|
### 4.2 Eliminating Intermediate Stack Allocations
|
||||||
Under the new pipeline:
|
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)`.
|
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.
|
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`.
|
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
|
### 4.3 Runtime Class Resolution
|
||||||
To ensure fast and safe type lookup during file deserialization:
|
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);
|
Entity* basePtr = AllocateEntity(manager, classPtr);
|
||||||
Assert(basePtr != nullptr);
|
Assert(basePtr != nullptr);
|
||||||
|
|
||||||
// 3. Read base versions and properties in-place
|
// 3. Serialize Base Entity in-place using Entity::Kind
|
||||||
uint16 baseVersion = 1;
|
SerializeClassInstance(ar, Entity::Kind, basePtr);
|
||||||
uint16 derivedVersion = 1;
|
|
||||||
SerializeProp(ar, "base_version", baseVersion);
|
|
||||||
SerializeProp(ar, "derived_version", derivedVersion);
|
|
||||||
|
|
||||||
SerializeEntityBase(ar, *basePtr, baseVersion);
|
// 4. Stream derived properties in-place using basePtr->DerivedKind
|
||||||
|
if (basePtr->DerivedKind != nullptr && basePtr->Derived != nullptr)
|
||||||
// 4. Stream derived properties in-place directly into the typed arena
|
|
||||||
if (classPtr->serialize_fct != nullptr)
|
|
||||||
{
|
{
|
||||||
classPtr->serialize_fct(ar, basePtr->Derived, derivedVersion);
|
SerializeClassInstance(ar, basePtr->DerivedKind, basePtr->Derived);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Freshly loaded entity matches disk state exactly
|
// Freshly loaded entity matches disk state exactly
|
||||||
@@ -663,9 +659,11 @@ To solve this, `Entity` in [`Game/Entity/Entity.h`](file:///w:/Classified/Juliet
|
|||||||
```cpp
|
```cpp
|
||||||
struct Entity final
|
struct Entity final
|
||||||
{
|
{
|
||||||
|
DECLARE_ENTITY() // static Class* Kind; (Entity's own Class descriptor)
|
||||||
|
|
||||||
EntityID ID = 0;
|
EntityID ID = 0;
|
||||||
Class* Kind = nullptr;
|
Class* DerivedKind = nullptr; // Pointer to derived class descriptor (e.g. Inert::Kind)
|
||||||
DerivedType Derived = nullptr;
|
DerivedType Derived = nullptr; // Pointer to derived component memory
|
||||||
float X = 0.0f;
|
float X = 0.0f;
|
||||||
float Y = 0.0f;
|
float Y = 0.0f;
|
||||||
float Z = 0.0f;
|
float Z = 0.0f;
|
||||||
|
|||||||
@@ -353,23 +353,36 @@ Assets/Worlds/<WorldName>/
|
|||||||
└── ...
|
└── ...
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4.2 `WorldSettings.jasset` Binary Layout
|
### 4.2 `WorldSettings.jasset` Format Specification
|
||||||
Global world settings are stored in `WorldSettings.jasset`.
|
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
|
```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
|
struct WorldEnvironmentSettings
|
||||||
{
|
{
|
||||||
// Directional Sun & Ambient Lighting
|
// Directional Sun & Ambient Lighting
|
||||||
Vector3 SunDirection = { 0.577f, -0.577f, -0.577f };
|
Vector3 SunDirection = { 0.577f, -0.577f, -0.577f };
|
||||||
float _Pad0 = 0.0f;
|
|
||||||
Vector3 SunColor = { 1.0f, 0.95f, 0.8f };
|
Vector3 SunColor = { 1.0f, 0.95f, 0.8f };
|
||||||
float SunIntensity = 1.0f;
|
float SunIntensity = 1.0f;
|
||||||
Vector3 AmbientColor = { 0.2f, 0.25f, 0.35f };
|
Vector3 AmbientColor = { 0.2f, 0.25f, 0.35f };
|
||||||
@@ -391,9 +404,9 @@ entity_instance
|
|||||||
0x0100000000000042
|
0x0100000000000042
|
||||||
; class
|
; class
|
||||||
Inert
|
Inert
|
||||||
; base_version
|
; version
|
||||||
1
|
1
|
||||||
; derived_version
|
; class_version
|
||||||
1
|
1
|
||||||
; position
|
; position
|
||||||
0.43 0.32 1.56
|
0.43 0.32 1.56
|
||||||
@@ -408,8 +421,8 @@ Inert
|
|||||||
- `; class`: The runtime `Class` name (e.g. `Inert`, `Door`).
|
- `; class`: The runtime `Class` name (e.g. `Inert`, `Door`).
|
||||||
- `; template`: Optional relative path to archetype template (e.g. `Assets/Templates/Door_Wood.jasset`).
|
- `; template`: Optional relative path to archetype template (e.g. `Assets/Templates/Door_Wood.jasset`).
|
||||||
2. **Version Directives**:
|
2. **Version Directives**:
|
||||||
- `; base_version`: Engine-wide base entity version (`kEntityBaseVersion`).
|
- `; version`: Universal asset version (`kEntityBaseVersion` for entities, or asset schema version for non-entity files like `WorldSettings.jasset`).
|
||||||
- `; derived_version`: Class-specific gameplay version (`Class::Version`).
|
- `; 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**:
|
3. **Base Entity Properties**:
|
||||||
- Position (`position\n0.43 0.32 1.56`), Rotation, Scale.
|
- Position (`position\n0.43 0.32 1.56`), Rotation, Scale.
|
||||||
4. **Derived Entity Properties**:
|
4. **Derived Entity Properties**:
|
||||||
|
|||||||
+1
-1
@@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
// namespace
|
// namespace
|
||||||
// {
|
// {
|
||||||
// void serialize_test(archive* ar, void* payload)
|
// void serialize_test(Archive* ar, void* payload)
|
||||||
// {
|
// {
|
||||||
// SerializedEntityTest* test = reinterpret_cast<SerializedEntityTest*>(payload);
|
// SerializedEntityTest* test = reinterpret_cast<SerializedEntityTest*>(payload);
|
||||||
// serialize_elem(ar, test->A);
|
// serialize_elem(ar, test->A);
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Core/Application/IApplication.h>
|
#include <Core/Application/IApplication.h>
|
||||||
#include <Core/Common/CoreTypes.h>
|
|
||||||
#include <Juliet.h>
|
#include <Juliet.h>
|
||||||
|
|
||||||
enum class JulietInit_Flags : uint8;
|
enum class JulietInit_Flags : uint8;
|
||||||
|
|||||||
@@ -43,5 +43,23 @@ constexpr int64 int64Max = MaxValueOf<int64>();
|
|||||||
|
|
||||||
constexpr index_t indexMax = MaxValueOf<index_t>();
|
constexpr index_t indexMax = MaxValueOf<index_t>();
|
||||||
|
|
||||||
|
template <typename Type>
|
||||||
|
consteval Type MinValueOf()
|
||||||
|
{
|
||||||
|
return std::numeric_limits<Type>::lowest();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr uint8 uint8Min = MinValueOf<uint8>();
|
||||||
|
constexpr uint16 uint16Min = MinValueOf<uint16>();
|
||||||
|
constexpr uint32 uint32Min = MinValueOf<uint32>();
|
||||||
|
constexpr uint64 uint64Min = MinValueOf<uint64>();
|
||||||
|
|
||||||
|
constexpr int8 int8Min = MinValueOf<int8>();
|
||||||
|
constexpr int16 int16Min = MinValueOf<int16>();
|
||||||
|
constexpr int32 int32Min = MinValueOf<int32>();
|
||||||
|
constexpr int64 int64Min = MinValueOf<int64>();
|
||||||
|
|
||||||
|
constexpr index_t indexMin = MinValueOf<index_t>();
|
||||||
|
|
||||||
#define Kilobytes(value) value * 1024
|
#define Kilobytes(value) value * 1024
|
||||||
#define Megabytes(value) Kilobytes(value) * 1024
|
#define Megabytes(value) Kilobytes(value) * 1024
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Core/Common/CoreTypes.h>
|
|
||||||
#include <Juliet.h>
|
#include <Juliet.h>
|
||||||
|
|
||||||
|
|
||||||
#define global static
|
#define global static
|
||||||
|
|
||||||
// 1. Stringify helpers
|
// 1. Stringify helpers
|
||||||
|
|||||||
@@ -164,6 +164,8 @@ extern JULIET_API bool ConvertString(String from, String to, String src, StringB
|
|||||||
JULIET_API String StringCopy(NonNullPtr<Arena> arena, String str);
|
JULIET_API String StringCopy(NonNullPtr<Arena> arena, String str);
|
||||||
JULIET_API String16 str16_from_8(NonNullPtr<Arena> arena, String8 str);
|
JULIET_API String16 str16_from_8(NonNullPtr<Arena> arena, String8 str);
|
||||||
|
|
||||||
|
String trim_whitespace(String str);
|
||||||
|
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
String Format(NonNullPtr<Arena> arena, const char* formatStr, Args&&... args)
|
String Format(NonNullPtr<Arena> arena, const char* formatStr, Args&&... args)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,16 +2,126 @@
|
|||||||
|
|
||||||
#include <Juliet.h>
|
#include <Juliet.h>
|
||||||
|
|
||||||
|
#include <Core/Common/String.h>
|
||||||
|
#include <Core/HAL/IO/IOStream.h>
|
||||||
|
|
||||||
|
// .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 Arena;
|
||||||
|
|
||||||
struct archive
|
struct ArchivePropertyNode
|
||||||
{
|
{
|
||||||
Arena* arena;
|
String key;
|
||||||
void* base_ptr;
|
String value;
|
||||||
index_t offset;
|
uint32 key_crc;
|
||||||
bool loading;
|
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))
|
#define serialize_elem(ar, val) serialize((ar), &(val), sizeof(val))
|
||||||
|
|
||||||
|
JULIET_API ParsedArchive tokenize_archive(NonNullPtr<Arena> arena, ByteBuffer file_buffer);
|
||||||
|
JULIET_API ArchivePropertyNode* find_property(NonNullPtr<ParsedArchive> 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<IOStream> stream, String value);
|
||||||
|
JULIET_API bool read(const char* buffer, float& value, const char** end = nullptr);
|
||||||
|
JULIET_API void write(NonNullPtr<IOStream> stream, float value);
|
||||||
|
JULIET_API bool read(const char* buffer, int8& value);
|
||||||
|
JULIET_API void write(NonNullPtr<IOStream> stream, int8 value);
|
||||||
|
JULIET_API bool read(const char* buffer, int16& value);
|
||||||
|
JULIET_API void write(NonNullPtr<IOStream> stream, int16 value);
|
||||||
|
JULIET_API bool read(const char* buffer, int32& value);
|
||||||
|
JULIET_API void write(NonNullPtr<IOStream> stream, int32 value);
|
||||||
|
JULIET_API bool read(const char* buffer, int64& value);
|
||||||
|
JULIET_API void write(NonNullPtr<IOStream> stream, int64 value);
|
||||||
|
JULIET_API bool read(const char* buffer, uint8& value);
|
||||||
|
JULIET_API void write(NonNullPtr<IOStream> stream, uint8 value);
|
||||||
|
JULIET_API bool read(const char* buffer, uint16& value);
|
||||||
|
JULIET_API void write(NonNullPtr<IOStream> stream, uint16 value);
|
||||||
|
JULIET_API bool read(const char* buffer, uint32& value);
|
||||||
|
JULIET_API void write(NonNullPtr<IOStream> stream, uint32 value);
|
||||||
|
JULIET_API bool read(const char* buffer, uint64& value);
|
||||||
|
JULIET_API void write(NonNullPtr<IOStream> stream, uint64 value);
|
||||||
|
JULIET_API bool read(const char* buffer, bool& value);
|
||||||
|
JULIET_API void write(NonNullPtr<IOStream> stream, bool value);
|
||||||
|
JULIET_API bool read(const char* buffer, Vector4& value);
|
||||||
|
JULIET_API void write(NonNullPtr<IOStream> stream, Vector4 value);
|
||||||
|
|
||||||
|
// For primitives not needing archive nor allocation
|
||||||
|
template <typename Type>
|
||||||
|
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 <typename Type>
|
||||||
|
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<ParsedArchive> archive, String context_name);
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Core/Common/CoreTypes.h>
|
|
||||||
#include <Core/Common/NonNullPtr.h>
|
#include <Core/Common/NonNullPtr.h>
|
||||||
#include <Core/Common/String.h>
|
#include <Core/Common/String.h>
|
||||||
#include <Juliet.h>
|
|
||||||
|
|
||||||
struct Window;
|
struct Window;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Core/Common/CoreTypes.h>
|
|
||||||
|
|
||||||
// Represents a Virtual Key corresponding to the Physical key, but localized using the keyboard layout
|
// Represents a Virtual Key corresponding to the Physical key, but localized using the keyboard layout
|
||||||
// ScanCode reprensent US ASCII Keyboard
|
// ScanCode reprensent US ASCII Keyboard
|
||||||
// WASD Scan codes are ZQSD in KeyCode for French keyboard
|
// WASD Scan codes are ZQSD in KeyCode for French keyboard
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Core/Common/CoreTypes.h>
|
|
||||||
#include <Juliet.h>
|
|
||||||
|
|
||||||
namespace Memory
|
namespace Memory
|
||||||
{
|
{
|
||||||
Byte* OS_Reserve(size_t size);
|
Byte* OS_Reserve(size_t size);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Core/Common/CoreTypes.h>
|
|
||||||
#include <Core/Common/NonNullPtr.h>
|
#include <Core/Common/NonNullPtr.h>
|
||||||
|
|
||||||
#ifdef JULIET_ENABLE_IMGUI
|
#ifdef JULIET_ENABLE_IMGUI
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Core/Common/CoreTypes.h>
|
|
||||||
|
|
||||||
enum class JulietInit_Flags : uint8
|
enum class JulietInit_Flags : uint8
|
||||||
{
|
{
|
||||||
None = 0,
|
None = 0,
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Core/Common/CoreTypes.h>
|
|
||||||
#include <Core/Common/NonNullPtr.h>
|
#include <Core/Common/NonNullPtr.h>
|
||||||
#include <Core/Memory/MemoryArena.h>
|
#include <Core/Memory/MemoryArena.h>
|
||||||
#include <Juliet.h>
|
|
||||||
|
|
||||||
// TODO : Juliet strings
|
// TODO : Juliet strings
|
||||||
// TODO Juliet Containers + Allocators...
|
// TODO Juliet Containers + Allocators...
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Core/Common/CoreTypes.h>
|
|
||||||
#include <Juliet.h>
|
|
||||||
|
|
||||||
extern JULIET_API float RoundF(float value);
|
extern JULIET_API float RoundF(float value);
|
||||||
|
|
||||||
inline int32 LRoundF(float value)
|
inline int32 LRoundF(float value)
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Core/Common/CoreTypes.h>
|
|
||||||
#include <Juliet.h>
|
|
||||||
#include <math.h>
|
|
||||||
|
|
||||||
struct Vector3
|
struct Vector3
|
||||||
{
|
{
|
||||||
float x, y, z;
|
float x, y, z;
|
||||||
@@ -15,7 +11,10 @@ struct Vector3
|
|||||||
|
|
||||||
struct Vector4
|
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)
|
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 };
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Core/Common/CoreTypes.h>
|
|
||||||
#include <Core/Common/CoreUtils.h>
|
#include <Core/Common/CoreUtils.h>
|
||||||
#include <Core/Common/NonNullPtr.h>
|
#include <Core/Common/NonNullPtr.h>
|
||||||
#include <Core/Common/String.h>
|
#include <Core/Common/String.h>
|
||||||
#include <Juliet.h>
|
|
||||||
|
|
||||||
#if JULIET_DEBUG
|
#if JULIET_DEBUG
|
||||||
#include <Core/Memory/MemoryArenaDebug.h>
|
#include <Core/Memory/MemoryArenaDebug.h>
|
||||||
@@ -79,8 +77,7 @@ JULIET_API void ArenaClear(NonNullPtr<Arena> arena);
|
|||||||
template <typename FirstDebugArg, typename... DebugArgs>
|
template <typename FirstDebugArg, typename... DebugArgs>
|
||||||
#endif
|
#endif
|
||||||
[[nodiscard]] inline void* ArenaPushSize(NonNullPtr<Arena> arena, size_t size, size_t align,
|
[[nodiscard]] inline void* ArenaPushSize(NonNullPtr<Arena> arena, size_t size, size_t align,
|
||||||
bool shouldBeZeroed JULIET_DEBUG_PARAM(FirstDebugArg&& firstDebugArg,
|
bool shouldBeZeroed JULIET_DEBUG_PARAM(FirstDebugArg&& firstDebugArg, DebugArgs&&... debugArgs))
|
||||||
DebugArgs&&... debugArgs))
|
|
||||||
{
|
{
|
||||||
return ArenaPush(arena, size, align,
|
return ArenaPush(arena, size, align,
|
||||||
shouldBeZeroed JULIET_DEBUG_PARAM(
|
shouldBeZeroed JULIET_DEBUG_PARAM(
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Core/Common/CoreTypes.h>
|
|
||||||
#include <Core/Common/NonNullPtr.h>
|
#include <Core/Common/NonNullPtr.h>
|
||||||
#include <Core/Common/String.h>
|
#include <Core/Common/String.h>
|
||||||
#include <Juliet.h>
|
|
||||||
|
|
||||||
#if JULIET_DEBUG
|
#if JULIET_DEBUG
|
||||||
|
|
||||||
@@ -45,5 +43,4 @@ void DebugArenaRemoveLastAllocation(MemoryBlock* blk);
|
|||||||
|
|
||||||
JULIET_API Arena* GetDebugInfoArena();
|
JULIET_API Arena* GetDebugInfoArena();
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Core/Common/CoreTypes.h>
|
|
||||||
|
|
||||||
#define ArraySize(array) (sizeof(array) / sizeof(array[0]))
|
#define ArraySize(array) (sizeof(array) / sizeof(array[0]))
|
||||||
|
|
||||||
inline int32 MemCompare(const void* leftValue, const void* rightValue, size_t size)
|
inline int32 MemCompare(const void* leftValue, const void* rightValue, size_t size)
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Core/Common/CoreTypes.h>
|
|
||||||
|
|
||||||
// TODO : Do something better.
|
// TODO : Do something better.
|
||||||
constexpr uint32 kLocalhost = (127 << 3) | (0 << 2) | (0 << 1) | 1;
|
constexpr uint32 kLocalhost = (127 << 3) | (0 << 2) | (0 << 1) | 1;
|
||||||
constexpr uint32 kAnyIp = 0;
|
constexpr uint32 kAnyIp = 0;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Core/Common/CoreTypes.h>
|
|
||||||
#include <Core/Container/Vector.h>
|
#include <Core/Container/Vector.h>
|
||||||
|
|
||||||
class NetworkPacket
|
class NetworkPacket
|
||||||
|
|||||||
@@ -29,4 +29,6 @@
|
|||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include <Juliet.h>
|
||||||
|
|
||||||
#include <Core/Common/CoreTypes.h>
|
#include <Core/Common/CoreTypes.h>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Core/Common/CoreTypes.h>
|
|
||||||
#include <Core/Memory/MemoryArena.h>
|
#include <Core/Memory/MemoryArena.h>
|
||||||
|
|
||||||
struct thread_context
|
struct thread_context
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Juliet.h>
|
#include <Juliet.h>
|
||||||
|
|
||||||
#include <Core/Common/CRC32.h>
|
#include <Core/Common/CRC32.h>
|
||||||
#include <Core/Common/String.h>
|
#include <Core/Common/String.h>
|
||||||
|
|
||||||
struct archive;
|
struct Archive;
|
||||||
|
|
||||||
using serialize_fct_type = void (*)(archive*, void* payload);
|
using serialize_fct_type = void (*)(Archive&, uint16 version, void* payload);
|
||||||
|
|
||||||
struct Class
|
struct Class
|
||||||
{
|
{
|
||||||
uint32 CRC;
|
uint32 CRC;
|
||||||
uint8 kind;
|
uint8 kind;
|
||||||
|
uint16 version;
|
||||||
|
const Class* base_class;
|
||||||
serialize_fct_type serialize_fct;
|
serialize_fct_type serialize_fct;
|
||||||
size_t size_of;
|
size_t size_of;
|
||||||
size_t alignment;
|
size_t alignment;
|
||||||
@@ -23,25 +24,36 @@ struct Class
|
|||||||
#endif
|
#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<Class*>(&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 = {};
|
Class cls = {};
|
||||||
cls.CRC = crc32(name.Str, name.Size);
|
cls.CRC = crc32(name.Str, name.Size);
|
||||||
cls.kind = kind;
|
cls.kind = kind;
|
||||||
|
cls.version = version;
|
||||||
|
cls.base_class = base_class;
|
||||||
cls.size_of = size;
|
cls.size_of = size;
|
||||||
cls.alignment = align;
|
cls.alignment = align;
|
||||||
cls.serialize_fct = fct;
|
cls.serialize_fct = fct;
|
||||||
|
|
||||||
#if JULIET_DEBUG
|
#if JULIET_DEBUG
|
||||||
// TODO: string struct may be
|
|
||||||
cls.Name = name;
|
cls.Name = name;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return cls;
|
return cls;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsA(const Class& query, const Class* target);
|
||||||
|
|
||||||
template <typename type>
|
template <typename type>
|
||||||
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<Class> cls, void* instance);
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Juliet.h>
|
|
||||||
|
|
||||||
#include <Core/Common/CoreTypes.h>
|
|
||||||
|
|
||||||
#if JULIET_DEBUG
|
#if JULIET_DEBUG
|
||||||
#define ALLOW_SHADER_HOT_RELOAD 1
|
#define ALLOW_SHADER_HOT_RELOAD 1
|
||||||
#else
|
#else
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Core/Common/CoreTypes.h>
|
|
||||||
#include <Core/Common/NonNullPtr.h>
|
#include <Core/Common/NonNullPtr.h>
|
||||||
#include <Core/Common/String.h>
|
#include <Core/Common/String.h>
|
||||||
#include <Core/Math/Matrix.h>
|
#include <Core/Math/Matrix.h>
|
||||||
#include <Core/Math/Vector.h>
|
#include <Core/Math/Vector.h>
|
||||||
#include <Juliet.h>
|
|
||||||
|
|
||||||
struct Arena;
|
struct Arena;
|
||||||
struct Vertex;
|
struct Vertex;
|
||||||
|
|||||||
@@ -351,8 +351,7 @@ bool ConvertString(StringEncoding from, StringEncoding to, String src, StringBuf
|
|||||||
{
|
{
|
||||||
character = kUnknown_UNICODE;
|
character = kUnknown_UNICODE;
|
||||||
}
|
}
|
||||||
if ((character >= 0xD800 && character <= 0xDFFF) || (character == 0xFFFE || character == 0xFFFF) ||
|
if ((character >= 0xD800 && character <= 0xDFFF) || (character == 0xFFFE || character == 0xFFFF) || character > 0x10FFFF)
|
||||||
character > 0x10FFFF)
|
|
||||||
{
|
{
|
||||||
character = kUnknown_UNICODE;
|
character = kUnknown_UNICODE;
|
||||||
}
|
}
|
||||||
@@ -623,3 +622,20 @@ String16 str16_from_8(NonNullPtr<Arena> arena, String8 in)
|
|||||||
return result;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
#include <Core/Common/serialization.h>
|
#include <Core/Common/serialization.h>
|
||||||
|
|
||||||
#include <Core/Common/CoreUtils.h>
|
#include <Core/Common/CoreUtils.h>
|
||||||
|
#include <Core/Common/CRC32.h>
|
||||||
|
#include <Core/HAL/IO/IOStream.h>
|
||||||
|
#include <Core/Logging/LogManager.h>
|
||||||
|
#include <Core/Logging/LogTypes.h>
|
||||||
|
#include <Core/Math/Vector.h>
|
||||||
#include <Core/Memory/MemoryArena.h>
|
#include <Core/Memory/MemoryArena.h>
|
||||||
#include <Core/Memory/Utils.h>
|
#include <Core/Memory/Utils.h>
|
||||||
|
|
||||||
void serialize(archive& ar, void* data, size_t size)
|
void serialize(Archive& ar, void* data, size_t size)
|
||||||
{
|
{
|
||||||
if (ar.loading)
|
if (ar.loading)
|
||||||
{
|
{
|
||||||
@@ -19,3 +24,359 @@ void serialize(archive& ar, void* data, size_t size)
|
|||||||
ar.offset += size;
|
ar.offset += size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ParsedArchive tokenize_archive(NonNullPtr<Arena> 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<ArchivePropertyNode>(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<size_t>(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<ParsedArchive> 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<IOStream> 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<IOStream> 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<IOStream> 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<IOStream> 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<IOStream> 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<IOStream> 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<IOStream> 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<IOStream> 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<IOStream> 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<IOStream> 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<IOStream> 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<IOStream> 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<ParsedArchive> 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<int>(nodes[idx].value.Size), nodes[idx].value.Str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
#include <Core/Common/CoreTypes.h>
|
#include <Core/HAL/Display/Display_cpp.h>
|
||||||
#include <Core/HAL/Display/Display_cpp.h>
|
|
||||||
#include <Core/HAL/Display/DisplayDevice.h>
|
#include <Core/HAL/Display/DisplayDevice.h>
|
||||||
#include <Core/Memory/Allocator.h>
|
#include <Core/Memory/Allocator.h>
|
||||||
#include <Core/Memory/MemoryArena.h>
|
#include <Core/Memory/MemoryArena.h>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
#include <Core/Common/CoreTypes.h>
|
#include <Core/Common/CoreUtils.h>
|
||||||
#include <Core/Common/CoreUtils.h>
|
|
||||||
#include <Core/Common/String.h>
|
#include <Core/Common/String.h>
|
||||||
#include <Core/HAL/Filesystem/Filesystem.h>
|
#include <Core/HAL/Filesystem/Filesystem.h>
|
||||||
#include <Core/HAL/Filesystem/Filesystem_Platform.h>
|
#include <Core/HAL/Filesystem/Filesystem_Platform.h>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
#include <Core/Common/CoreTypes.h>
|
#include <Core/Common/CoreUtils.h>
|
||||||
#include <Core/Common/CoreUtils.h>
|
|
||||||
#include <Core/HAL/DynLib/DynamicLibrary.h>
|
#include <Core/HAL/DynLib/DynamicLibrary.h>
|
||||||
#include <Core/HAL/OS/OS.h>
|
#include <Core/HAL/OS/OS.h>
|
||||||
#include <Core/HAL/OS/OS_Private.h>
|
#include <Core/HAL/OS/OS_Private.h>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
#include <Core/Common/CoreTypes.h>
|
#include <Core/Math/Math_Private.h>
|
||||||
#include <Core/Math/Math_Private.h>
|
|
||||||
|
|
||||||
// From MUSL lib https://github.com/rofl0r/musl
|
// From MUSL lib https://github.com/rofl0r/musl
|
||||||
namespace
|
namespace
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
#include <Core/Common/CoreTypes.h>
|
#include <Core/Common/CoreUtils.h>
|
||||||
#include <Core/Common/CoreUtils.h>
|
|
||||||
#include <Core/Memory/Allocator.h>
|
#include <Core/Memory/Allocator.h>
|
||||||
#include <Core/Memory/MemoryArena.h>
|
#include <Core/Memory/MemoryArena.h>
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1,38 @@
|
|||||||
|
#include <Core/Common/serialization.h>
|
||||||
#include <Engine/Class.h>
|
#include <Engine/Class.h>
|
||||||
|
|
||||||
|
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<Class> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user