Serialization : started to unify how to allocate entity whether its from MakeEntity or during deserialization.
This commit is contained in:
+1
-1
@@ -93,7 +93,7 @@ void Serialize(Archive& ar, World& /*world*/, String filename)
|
|||||||
{
|
{
|
||||||
// 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<EntityTemplate*>(rawElement + (idx * stride))->base;
|
||||||
serialize(ar, entity);
|
serialize(ar, entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-16
@@ -2,22 +2,6 @@
|
|||||||
|
|
||||||
#include <Core/Common/serialization.h>
|
#include <Core/Common/serialization.h>
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
void serialize_entity(Archive& ar, uint16 /*version*/, void* payload)
|
|
||||||
{
|
|
||||||
Assert(payload);
|
|
||||||
Entity* entity = static_cast<Entity*>(payload);
|
|
||||||
|
|
||||||
SERIALIZE(ar, id, entity->ID);
|
|
||||||
SERIALIZE(ar, position, entity->position);
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
DEFINE_CLASS_VERSIONED(Entity, 1, nullptr, serialize_entity)
|
|
||||||
|
|
||||||
DEFINE_ENTITY_VERSIONED(Inert, 1, nullptr)
|
|
||||||
|
|
||||||
void serialize(Archive& ar, NonNullPtr<Entity> entity)
|
void serialize(Archive& ar, NonNullPtr<Entity> entity)
|
||||||
{
|
{
|
||||||
// Entity fields
|
// Entity fields
|
||||||
@@ -29,3 +13,11 @@ void serialize(Archive& ar, NonNullPtr<Entity> entity)
|
|||||||
serialize(ar, entity->derived_kind, entity->derived);
|
serialize(ar, entity->derived_kind, entity->derived);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DEFINE_CLASS_VERSIONED(Entity, 1, nullptr)
|
||||||
|
|
||||||
|
internal void serialize(Archive& ar, uint16 /*version*/, Entity& entity)
|
||||||
|
{
|
||||||
|
SERIALIZE(ar, id, entity.ID);
|
||||||
|
SERIALIZE(ar, position, entity.position);
|
||||||
|
}
|
||||||
|
|||||||
+13
-61
@@ -1,24 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Core/Common/CoreUtils.h>
|
#include <Core/Common/CoreUtils.h>
|
||||||
#include <Core/Common/EnumUtils.h>
|
|
||||||
#include <Core/Math/Vector.h>
|
#include <Core/Math/Vector.h>
|
||||||
#include <Core/Memory/Allocator.h>
|
|
||||||
#include <Core/Memory/MemoryArena.h>
|
|
||||||
#include <Engine/Class.h>
|
#include <Engine/Class.h>
|
||||||
|
#include <Entity/entity_common.h>
|
||||||
#define DECLARE_ENTITY() \
|
#include <Entity/EntityManager.h>
|
||||||
Entity* base; \
|
|
||||||
DECLARE_CLASS()
|
|
||||||
|
|
||||||
#define DEFINE_ENTITY_VERSIONED(entity, version, serialize_fct) \
|
|
||||||
constexpr Class entityKind##entity = MakeClass(ConstString(#entity), (uint8)Entity_Type::entity, (version), \
|
|
||||||
&classKindEntity, sizeof(entity), alignof(entity), (serialize_fct)); \
|
|
||||||
Class* entity::kind = const_cast<Class*>(&entityKind##entity);
|
|
||||||
|
|
||||||
struct EntityManager;
|
|
||||||
using DerivedType = void*;
|
|
||||||
using EntityID = uint64_t;
|
|
||||||
|
|
||||||
struct Entity final
|
struct Entity final
|
||||||
{
|
{
|
||||||
@@ -30,44 +16,8 @@ struct Entity final
|
|||||||
Vector4 position = {};
|
Vector4 position = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Inert
|
|
||||||
{
|
|
||||||
DECLARE_ENTITY()
|
|
||||||
|
|
||||||
index_t MeshInstance = indexMax;
|
|
||||||
};
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
#define ENTITY_TYPE_LIST(X) X(Inert),
|
|
||||||
|
|
||||||
#define AS_ENUM(name) name
|
|
||||||
enum class Entity_Type : uint8
|
|
||||||
{
|
|
||||||
ENTITY_TYPE_LIST(AS_ENUM)
|
|
||||||
Count
|
|
||||||
};
|
|
||||||
#undef AS_ENUM
|
|
||||||
|
|
||||||
#define AS_STR(name) #name
|
|
||||||
inline const char* kEntity_type_names[] = {
|
|
||||||
ENTITY_TYPE_LIST(AS_STR)
|
|
||||||
"Count"
|
|
||||||
};
|
|
||||||
#undef AS_STR
|
|
||||||
|
|
||||||
#define AS_CLASS(name) name::kind
|
|
||||||
inline Class* kEntity_type_class_ptr[]
|
|
||||||
{
|
|
||||||
ENTITY_TYPE_LIST(AS_CLASS)
|
|
||||||
nullptr
|
|
||||||
};
|
|
||||||
#undef AS_CLASS
|
|
||||||
|
|
||||||
#define ENTITY(kind) ToUnderlying(Entity_Type::kind)
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
// Can reinterpret cast to this to have the offset of Base and Kind for any entity
|
// Can reinterpret cast to this to have the offset of Base and Kind for any entity
|
||||||
struct entity_template
|
struct EntityTemplate
|
||||||
{
|
{
|
||||||
DECLARE_ENTITY();
|
DECLARE_ENTITY();
|
||||||
};
|
};
|
||||||
@@ -91,15 +41,17 @@ template <typename EntityType>
|
|||||||
requires EntityConcept<EntityType>
|
requires EntityConcept<EntityType>
|
||||||
[[nodiscard]] EntityType* MakeEntity(EntityManager& manager, float x, float y, float z)
|
[[nodiscard]] EntityType* MakeEntity(EntityManager& manager, float x, float y, float z)
|
||||||
{
|
{
|
||||||
EntityType result;
|
Entity* base_ptr = allocate_entity(manager, EntityType::kind);
|
||||||
Entity base;
|
Assert(base_ptr);
|
||||||
base.position.x = x;
|
|
||||||
base.position.y = y;
|
|
||||||
base.position.z = z;
|
|
||||||
base.position.w = 1.0f;
|
|
||||||
base.derived_kind = EntityType::kind;
|
|
||||||
|
|
||||||
return (EntityType*)RegisterEntity(manager, &base, &result);
|
base_ptr->ID = EntityManager::ID++;
|
||||||
|
|
||||||
|
base_ptr->position.x = x;
|
||||||
|
base_ptr->position.y = y;
|
||||||
|
base_ptr->position.z = z;
|
||||||
|
base_ptr->position.w = 1.0f;
|
||||||
|
|
||||||
|
return (EntityType*)base_ptr->derived;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename EntityType>
|
template <typename EntityType>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <Core/Common/EnumUtils.h>
|
#include <Core/Common/EnumUtils.h>
|
||||||
#include <Data/World.h>
|
#include <Data/World.h>
|
||||||
#include <Entity/Entity.h>
|
#include <Entity/Entity.h>
|
||||||
|
#include <Entity/entity_types.h>
|
||||||
#include <game.h>
|
#include <game.h>
|
||||||
#include <Graphics/MeshRenderer.h>
|
#include <Graphics/MeshRenderer.h>
|
||||||
|
|
||||||
@@ -43,14 +44,14 @@ EntityManager& GetEntityManager()
|
|||||||
return *entityManager;
|
return *entityManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
entity_template* RegisterEntity(EntityManager& manager, Entity* base, DerivedType entity)
|
EntityTemplate* 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->derived_kind->kind].arena,
|
auto* ptr = (EntityTemplate*)ArenaPushSize(manager.by_type[base->derived_kind->kind].arena,
|
||||||
base->derived_kind->size_of, base->derived_kind->alignment,
|
base->derived_kind->size_of, base->derived_kind->alignment,
|
||||||
false JULIET_DEBUG_PARAM(kEntity_type_names[base->derived_kind->kind]));
|
false JULIET_DEBUG_PARAM(kEntity_type_names[base->derived_kind->kind]));
|
||||||
MemCopy(ptr, entity, base->derived_kind->size_of);
|
MemCopy(ptr, entity, base->derived_kind->size_of);
|
||||||
@@ -66,6 +67,42 @@ entity_template* RegisterEntity(EntityManager& manager, Entity* base, DerivedTyp
|
|||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Entity* allocate_entity(EntityManager& manager, NonNullPtr<Class> derived_type_class)
|
||||||
|
{
|
||||||
|
Assert(derived_type_class->kind < ENTITY(Count));
|
||||||
|
Assert(derived_type_class->size_of >= sizeof(EntityTemplate));
|
||||||
|
Assert(derived_type_class->alignment > 0);
|
||||||
|
Assert(derived_type_class->initialize_fct);
|
||||||
|
|
||||||
|
Entity base_template = {};
|
||||||
|
base_template.derived_kind = derived_type_class;
|
||||||
|
|
||||||
|
manager.Entities.PushBack(base_template);
|
||||||
|
Entity* base_ptr = manager.Entities.Back();
|
||||||
|
Assert(base_ptr);
|
||||||
|
|
||||||
|
typed_entity_array& typed_array = manager.by_type[derived_type_class->kind];
|
||||||
|
Assert(typed_array.arena);
|
||||||
|
|
||||||
|
void* derived = ArenaPushSize(typed_array.arena, derived_type_class->size_of, derived_type_class->alignment,
|
||||||
|
false JULIET_DEBUG_PARAM(kEntity_type_names[derived_type_class->kind]));
|
||||||
|
Assert(derived);
|
||||||
|
derived_type_class->initialize_fct(derived);
|
||||||
|
|
||||||
|
auto* derived_template = (EntityTemplate*)derived;
|
||||||
|
|
||||||
|
base_ptr->derived = derived;
|
||||||
|
derived_template->base = base_ptr;
|
||||||
|
|
||||||
|
if (typed_array.array == nullptr)
|
||||||
|
{
|
||||||
|
typed_array.array = derived_template;
|
||||||
|
}
|
||||||
|
typed_array.count += 1;
|
||||||
|
|
||||||
|
return base_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Core/Container/Vector.h>
|
#include <Core/Container/Vector.h>
|
||||||
#include <Entity/Entity.h>
|
#include <Entity/entity_common.h>
|
||||||
|
|
||||||
struct Entity;
|
|
||||||
struct World;
|
struct World;
|
||||||
|
struct EntityTemplate;
|
||||||
|
struct Entity;
|
||||||
|
struct Class;
|
||||||
|
|
||||||
struct typed_entity_array
|
struct typed_entity_array
|
||||||
{
|
{
|
||||||
Arena* arena;
|
Arena* arena;
|
||||||
entity_template* array;
|
EntityTemplate* array;
|
||||||
size_t count;
|
size_t count;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -26,5 +28,6 @@ struct EntityManager
|
|||||||
void InitEntityManager(NonNullPtr<World> world);
|
void InitEntityManager(NonNullPtr<World> world);
|
||||||
void ShutdownEntityManager();
|
void ShutdownEntityManager();
|
||||||
EntityManager& GetEntityManager();
|
EntityManager& GetEntityManager();
|
||||||
entity_template* RegisterEntity(EntityManager& manager, Entity* base, DerivedType entity);
|
EntityTemplate* RegisterEntity(EntityManager& manager, Entity* base, DerivedType entity);
|
||||||
|
[[nodiscard]] Entity* allocate_entity(EntityManager& manager, NonNullPtr<Class> derived_type_class);
|
||||||
void UpdateEntityManager(EntityManager& manager);
|
void UpdateEntityManager(EntityManager& manager);
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#include <Entity/entity_common.h>
|
||||||
|
|
||||||
|
#include <Entity/Entity.h>
|
||||||
|
#include <Entity/entity_types.h>
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
#define AS_STR(name) #name
|
||||||
|
const char* kEntity_type_names[] = {
|
||||||
|
ENTITY_TYPE_LIST(AS_STR)
|
||||||
|
"Count"
|
||||||
|
};
|
||||||
|
#undef AS_STR
|
||||||
|
|
||||||
|
#define AS_CLASS(name) name::kind
|
||||||
|
const Class* kEntity_type_class_ptr[]
|
||||||
|
{
|
||||||
|
ENTITY_TYPE_LIST(AS_CLASS)
|
||||||
|
nullptr
|
||||||
|
};
|
||||||
|
#undef AS_CLASS
|
||||||
|
// clang-format on
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Core/Common/EnumUtils.h>
|
||||||
|
#include <Engine/Class.h>
|
||||||
|
|
||||||
|
struct Class;
|
||||||
|
using DerivedType = void*;
|
||||||
|
using EntityID = uint64_t;
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
#define ENTITY_TYPE_LIST(X) X(Inert),
|
||||||
|
|
||||||
|
#define AS_ENUM(name) name
|
||||||
|
enum class Entity_Type : uint8
|
||||||
|
{
|
||||||
|
ENTITY_TYPE_LIST(AS_ENUM)
|
||||||
|
Count
|
||||||
|
};
|
||||||
|
#undef AS_ENUM
|
||||||
|
|
||||||
|
extern const char* kEntity_type_names[];
|
||||||
|
extern const Class* kEntity_type_class_ptr[];
|
||||||
|
|
||||||
|
#define ENTITY(kind) ToUnderlying(Entity_Type::kind)
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
struct Entity;
|
||||||
|
|
||||||
|
#define DECLARE_ENTITY() \
|
||||||
|
Entity* base; \
|
||||||
|
DECLARE_CLASS()
|
||||||
|
|
||||||
|
#define DEFINE_ENTITY_VERSIONED(entity, version) \
|
||||||
|
constexpr Class entityKind##entity = \
|
||||||
|
MakeClass(ConstString(#entity), (uint8)Entity_Type::entity, (version), &class_kind_Entity, sizeof(entity), \
|
||||||
|
alignof(entity), (&initialize_thunk<entity>), (&serialize_thunk<entity>)); \
|
||||||
|
Class* entity::kind = const_cast<Class*>(&entityKind##entity);
|
||||||
|
|
||||||
|
// Note: Needed by every classes see define above
|
||||||
|
extern const Class class_kind_Entity;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
#include <Entity/entity_types.h>
|
||||||
|
|
||||||
|
DEFINE_ENTITY_VERSIONED(Inert, 1)
|
||||||
|
internal void serialize(Archive& /*ar*/, uint16 /*version*/, Inert& /*inert*/) {}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Entity/entity_common.h>
|
||||||
|
|
||||||
|
struct Inert
|
||||||
|
{
|
||||||
|
DECLARE_ENTITY()
|
||||||
|
|
||||||
|
index_t MeshInstance = indexMax;
|
||||||
|
};
|
||||||
@@ -258,6 +258,7 @@ Every serializable entity or component is described by an immutable `Class` inst
|
|||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
using serialize_fct_type = void (*)(Archive& ar, uint16 version, void* payload);
|
using serialize_fct_type = void (*)(Archive& ar, uint16 version, void* payload);
|
||||||
|
using default_init_fct_type = void (*)(void* payload);
|
||||||
|
|
||||||
struct Class
|
struct Class
|
||||||
{
|
{
|
||||||
@@ -266,6 +267,7 @@ struct Class
|
|||||||
uint16 version;
|
uint16 version;
|
||||||
const Class* base_class;
|
const Class* base_class;
|
||||||
serialize_fct_type serialize_fct;
|
serialize_fct_type serialize_fct;
|
||||||
|
default_init_fct_type default_init_fct;
|
||||||
size_t size_of;
|
size_t size_of;
|
||||||
size_t alignment;
|
size_t alignment;
|
||||||
|
|
||||||
@@ -281,8 +283,10 @@ struct Class
|
|||||||
static Class* kind;
|
static Class* kind;
|
||||||
|
|
||||||
#define DEFINE_CLASS_VERSIONED(cls, version, base_class, serialize_fct) \
|
#define DEFINE_CLASS_VERSIONED(cls, version, base_class, serialize_fct) \
|
||||||
|
inline void default_init_##cls(void* payload) { *static_cast<cls*>(payload) = cls{}; } \
|
||||||
constexpr Class classKind##cls = \
|
constexpr Class classKind##cls = \
|
||||||
MakeClass(ConstString(#cls), 0, (version), (base_class), sizeof(cls), alignof(cls), (serialize_fct)); \
|
MakeClass(ConstString(#cls), 0, (version), (base_class), sizeof(cls), alignof(cls), \
|
||||||
|
(serialize_fct), default_init_##cls); \
|
||||||
Class* cls::kind = const_cast<Class*>(&classKind##cls);
|
Class* cls::kind = const_cast<Class*>(&classKind##cls);
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -293,8 +297,10 @@ For derived entity types, `DECLARE_ENTITY()` and `DEFINE_ENTITY_VERSIONED` compo
|
|||||||
DECLARE_CLASS()
|
DECLARE_CLASS()
|
||||||
|
|
||||||
#define DEFINE_ENTITY_VERSIONED(entity, version, serialize_fct) \
|
#define DEFINE_ENTITY_VERSIONED(entity, version, serialize_fct) \
|
||||||
|
inline void default_init_##entity(void* payload) { *static_cast<entity*>(payload) = entity{}; } \
|
||||||
constexpr Class entityKind##entity = MakeClass(ConstString(#entity), (uint8)Entity_Type::entity, (version), \
|
constexpr Class entityKind##entity = MakeClass(ConstString(#entity), (uint8)Entity_Type::entity, (version), \
|
||||||
&classKindEntity, sizeof(entity), alignof(entity), (serialize_fct)); \
|
&classKindEntity, sizeof(entity), alignof(entity), \
|
||||||
|
(serialize_fct), default_init_##entity); \
|
||||||
Class* entity::kind = const_cast<Class*>(&entityKind##entity);
|
Class* entity::kind = const_cast<Class*>(&entityKind##entity);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -192,11 +192,11 @@ The canonical allocation function is defined in `EntityManager.h`:
|
|||||||
#### Postconditions:
|
#### Postconditions:
|
||||||
- A new `Entity` record is appended to `manager.Entities`.
|
- A new `Entity` record is appended to `manager.Entities`.
|
||||||
- A new typed block of `classPtr->size_of` bytes is allocated in `manager.by_type[classPtr->kind].arena`.
|
- A new typed block of `classPtr->size_of` bytes is allocated in `manager.by_type[classPtr->kind].arena`.
|
||||||
- The derived memory is zeroed.
|
- The derived memory is initialized with C++ struct defaults via `classPtr->default_init_fct` (or zeroed via `MemZero` if null).
|
||||||
- `base->derived` points to the derived struct.
|
- `base->derived` points to the derived struct.
|
||||||
- `derived->base` points to the base `Entity`.
|
- `derived->base` points to the base `Entity`.
|
||||||
- `base->derived_kind` is assigned to `class_ptr`.
|
- `base->derived_kind` is assigned to `class_ptr`.
|
||||||
- `base->ID` is assigned the next unique `EntityManager::ID`.
|
- `base->ID` is initialized to `0` (unassigned; populated by `MakeEntity` or deserialization).
|
||||||
- `base->is_dirty` is initialized to `true`.
|
- `base->is_dirty` is initialized to `true`.
|
||||||
- `typed_entity_array::count` is incremented.
|
- `typed_entity_array::count` is incremented.
|
||||||
- `typed_entity_array::array` is initialized if this is the first entity of this type.
|
- `typed_entity_array::array` is initialized if this is the first entity of this type.
|
||||||
@@ -212,9 +212,9 @@ The implementation replaces the flawed `RegisterEntity` routine in [`Game/Entity
|
|||||||
Assert(derivedClassPtr->size_of >= sizeof(entity_template));
|
Assert(derivedClassPtr->size_of >= sizeof(entity_template));
|
||||||
Assert(derivedClassPtr->alignment > 0);
|
Assert(derivedClassPtr->alignment > 0);
|
||||||
|
|
||||||
// 1. Allocate uninitialized Base Entity in the contiguous VectorArena
|
// 1. Allocate Base Entity in the contiguous VectorArena (ID is 0 until assigned by MakeEntity or deserialization)
|
||||||
Entity baseTemplate{};
|
Entity baseTemplate{};
|
||||||
baseTemplate.ID = EntityManager::ID++;
|
baseTemplate.ID = 0;
|
||||||
baseTemplate.derived_kind = derivedClassPtr;
|
baseTemplate.derived_kind = derivedClassPtr;
|
||||||
baseTemplate.derived = nullptr;
|
baseTemplate.derived = nullptr;
|
||||||
baseTemplate.position = {};
|
baseTemplate.position = {};
|
||||||
@@ -224,7 +224,7 @@ The implementation replaces the flawed `RegisterEntity` routine in [`Game/Entity
|
|||||||
Entity* basePtr = manager.Entities.Back();
|
Entity* basePtr = manager.Entities.Back();
|
||||||
Assert(basePtr != nullptr);
|
Assert(basePtr != nullptr);
|
||||||
|
|
||||||
// 2. Allocate zeroed derived component memory in the typed arena
|
// 2. Allocate derived component memory in the typed arena
|
||||||
typed_entity_array& typedArray = manager.by_type[derivedClassPtr->kind];
|
typed_entity_array& typedArray = manager.by_type[derivedClassPtr->kind];
|
||||||
Assert(typedArray.arena != nullptr);
|
Assert(typedArray.arena != nullptr);
|
||||||
|
|
||||||
@@ -232,16 +232,26 @@ The implementation replaces the flawed `RegisterEntity` routine in [`Game/Entity
|
|||||||
typedArray.arena,
|
typedArray.arena,
|
||||||
derivedClassPtr->size_of,
|
derivedClassPtr->size_of,
|
||||||
derivedClassPtr->alignment,
|
derivedClassPtr->alignment,
|
||||||
true JULIET_DEBUG_PARAM(kEntity_type_names[derivedClassPtr->kind]));
|
false JULIET_DEBUG_PARAM(kEntity_type_names[derivedClassPtr->kind]));
|
||||||
Assert(rawMemory != nullptr);
|
Assert(rawMemory != nullptr);
|
||||||
|
|
||||||
|
// 3. Initialize derived component defaults via Class reflection stub
|
||||||
|
if (derivedClassPtr->default_init_fct != nullptr)
|
||||||
|
{
|
||||||
|
derivedClassPtr->default_init_fct(rawMemory);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MemZero(rawMemory, derivedClassPtr->size_of);
|
||||||
|
}
|
||||||
|
|
||||||
auto* derivedTemplate = reinterpret_cast<entity_template*>(rawMemory);
|
auto* derivedTemplate = reinterpret_cast<entity_template*>(rawMemory);
|
||||||
|
|
||||||
// 3. Establish mutual back-pointers
|
// 4. Establish mutual back-pointers
|
||||||
basePtr->Derived = rawMemory;
|
basePtr->derived = rawMemory;
|
||||||
derivedTemplate->Base = basePtr;
|
derivedTemplate->base = basePtr;
|
||||||
|
|
||||||
// 4. Update typed array tracking
|
// 5. Update typed array tracking
|
||||||
if (typedArray.array == nullptr)
|
if (typedArray.array == nullptr)
|
||||||
{
|
{
|
||||||
typedArray.array = derivedTemplate;
|
typedArray.array = derivedTemplate;
|
||||||
@@ -252,33 +262,49 @@ The implementation replaces the flawed `RegisterEntity` routine in [`Game/Entity
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3.3 Pure C-Style Zeroing & Initialization
|
### 3.3 Default Struct Initialization via `Class::default_init_fct`
|
||||||
`ArenaPushSize` is called with `shouldBeZeroed = true` (or followed by `MemZero`). This guarantees that:
|
|
||||||
- All bytes are zeroed (`0x00`).
|
|
||||||
- Pointers inside derived structs default to `nullptr`.
|
|
||||||
- Numerical fields default to `0`.
|
|
||||||
|
|
||||||
For structs that require specific non-zero sentinel values (such as `Inert::MeshInstance = indexMax`), Juliet follows a pure C approach without C++ constructor or placement-new machinery:
|
#### The Problem with Zero-Only Initialization
|
||||||
1. **Direct assignment or C-style init function**:
|
If allocation only zeroes memory (`MemZero` / `0x00`), any C++ member variables with non-zero defaults (such as `index_t MeshInstance = indexMax;` or `float Density = 1.0f;`) are populated with `0`. During deserialization, if a `.jasset` file lacks that property (e.g. an older file version or an optional field), `SERIALIZE` leaves the field untouched, meaning it incorrectly remains `0` rather than its intended default sentinel value!
|
||||||
|
|
||||||
|
#### The Solution: Compile-Time Default Stub in `DEFINE_ENTITY_VERSIONED`
|
||||||
|
Every entity descriptor `Class` includes a function pointer:
|
||||||
```cpp
|
```cpp
|
||||||
inline void InitInert(Inert* inert)
|
using default_init_fct_type = void (*)(void* payload);
|
||||||
|
|
||||||
|
struct Class
|
||||||
{
|
{
|
||||||
Assert(inert != nullptr);
|
...
|
||||||
inert->MeshInstance = indexMax;
|
default_init_fct_type default_init_fct = nullptr;
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
2. **Or simple struct literal assignment**:
|
|
||||||
|
When registering an entity type with `DEFINE_ENTITY_VERSIONED`, the macro automatically defines a tiny type-safe stub that aggregate value-initializes the struct:
|
||||||
```cpp
|
```cpp
|
||||||
*inert = Inert{ .MeshInstance = indexMax };
|
#define DEFINE_ENTITY_VERSIONED(entity, version, serialize_fct) \
|
||||||
|
inline void default_init_##entity(void* payload) \
|
||||||
|
{ \
|
||||||
|
*static_cast<entity*>(payload) = entity{}; \
|
||||||
|
} \
|
||||||
|
constexpr Class entityKind##entity = MakeClass(ConstString(#entity), (uint8)Entity_Type::entity, (version), \
|
||||||
|
&classKindEntity, sizeof(entity), alignof(entity), \
|
||||||
|
(serialize_fct), default_init_##entity); \
|
||||||
|
Class* entity::kind = const_cast<Class*>(&entityKind##entity);
|
||||||
```
|
```
|
||||||
No C++ placement `new`, `<new>` headers, or hidden constructor/destructor calls are ever used. Data structures remain pure C-style aggregates.
|
|
||||||
|
#### Architectural Advantages:
|
||||||
|
1. **Zero Boilerplate**: Developers write member initializers once in the struct definition (`index_t MeshInstance = indexMax;`).
|
||||||
|
2. **Type-Agnostic Core**: `AllocateEntity` does not need to know any C++ struct types; it unconditionally calls `derivedClassPtr->default_init_fct(rawMemory)`.
|
||||||
|
3. **Robust Deserialization**: In `deserialize_entity_in_place`, newly allocated entities already hold their canonical C++ defaults. Any properties absent in the `.jasset` file naturally retain their correct initial values.
|
||||||
|
4. **No Dynamic Heap / Placement-New**: `*static_cast<entity*>(payload) = entity{}` is pure aggregate value-assignment without `<new>` headers or exceptions.
|
||||||
|
|
||||||
### 3.4 Bidirectional Pointer Wiring
|
### 3.4 Bidirectional Pointer Wiring
|
||||||
Notice the sequence:
|
Notice the sequence:
|
||||||
1. `manager.Entities.PushBack(baseTemplate)` places the struct at its final, fixed arena address.
|
1. `manager.Entities.PushBack(baseTemplate)` places the struct at its final, fixed arena address.
|
||||||
2. `basePtr = manager.Entities.Back()` retrieves the persistent memory pointer.
|
2. `base_ptr = manager.Entities.Back()` retrieves the persistent memory pointer.
|
||||||
3. `derivedTemplate->Base = basePtr` wires the derived back-pointer directly to this permanent location.
|
3. `derived_class_ptr->default_init_fct(raw_memory)` initializes canonical struct defaults.
|
||||||
4. `basePtr->Derived = rawMemory` wires the base forward-pointer to the arena-allocated derived struct.
|
4. `derived_template->base = base_ptr` wires the derived back-pointer directly to this permanent location.
|
||||||
|
5. `base_ptr->derived = raw_memory` wires the base forward-pointer to the arena-allocated derived struct.
|
||||||
|
|
||||||
No stack copying occurs. Neither pointer is ever left dangling.
|
No stack copying occurs. Neither pointer is ever left dangling.
|
||||||
|
|
||||||
@@ -287,30 +313,26 @@ No stack copying occurs. Neither pointer is ever left dangling.
|
|||||||
- `typedArray.count`: The exact count of active entities of this type.
|
- `typedArray.count`: The exact count of active entities of this type.
|
||||||
- `typedArray.array`: Pointer to the first element in the arena.
|
- `typedArray.array`: Pointer to the first element in the arena.
|
||||||
|
|
||||||
When the first entity of a given type is allocated, `typedArray.array` is set to `derivedTemplate`. Because the arena allocates sequentially, `typedArray.array[i]` can be indexed directly with stride `classPtr->size_of` as long as memory remains contiguous.
|
When the first entity of a given type is allocated, `typedArray.array` is set to `derived_template`. Because the arena allocates sequentially, `typedArray.array[i]` can be indexed directly with stride `class_ptr->size_of` as long as memory remains contiguous.
|
||||||
|
|
||||||
### 3.6 Refactoring `MakeEntity<EntityType>` Template
|
### 3.6 Refactoring `MakeEntity<EntityType>` Template
|
||||||
With `AllocateEntity` operational, `MakeEntity` in [`Game/Entity/Entity.h`](file:///w:/Classified/Juliet/Game/Entity/Entity.h#L95-L107) becomes clean, safe, and stack-free:
|
With `AllocateEntity` handling memory allocation, default member initialization, and mutual pointer wiring, `MakeEntity` in [`Game/Entity/Entity.h`](file:///w:/Classified/Juliet/Game/Entity/Entity.h) becomes clean, safe, and stack-free:
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
template <typename EntityType>
|
template <typename EntityType>
|
||||||
requires EntityConcept<EntityType>
|
requires EntityConcept<EntityType>
|
||||||
[[nodiscard]] EntityType* MakeEntity(EntityManager& manager, float x, float y, float z)
|
[[nodiscard]] EntityType* MakeEntity(EntityManager& manager, float x, float y, float z)
|
||||||
{
|
{
|
||||||
Entity* basePtr = AllocateEntity(manager, EntityType::Kind);
|
Entity* base_ptr = AllocateEntity(manager, EntityType::kind);
|
||||||
Assert(basePtr != nullptr);
|
Assert(base_ptr != nullptr);
|
||||||
|
|
||||||
basePtr->X = x;
|
base_ptr->ID = EntityManager::ID++;
|
||||||
basePtr->Y = y;
|
base_ptr->position.x = x;
|
||||||
basePtr->Z = z;
|
base_ptr->position.y = y;
|
||||||
|
base_ptr->position.z = z;
|
||||||
|
base_ptr->position.w = 1.0f;
|
||||||
|
|
||||||
auto* derivedPtr = static_cast<EntityType*>(basePtr->Derived);
|
return static_cast<EntityType*>(base_ptr->derived);
|
||||||
ConstructDerivedDefaults<EntityType>(derivedPtr);
|
|
||||||
|
|
||||||
// Re-establish Base pointer after placement-new
|
|
||||||
derivedPtr->Base = basePtr;
|
|
||||||
|
|
||||||
return derivedPtr;
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -429,9 +451,15 @@ To ensure fast and safe type lookup during file deserialization:
|
|||||||
Entity* base_ptr = AllocateEntity(manager, class_ptr);
|
Entity* base_ptr = AllocateEntity(manager, class_ptr);
|
||||||
Assert(base_ptr != nullptr);
|
Assert(base_ptr != nullptr);
|
||||||
|
|
||||||
// 3. Serialize Base Entity and Derived in-place
|
// 3. Serialize Base Entity and Derived in-place (loads ID and properties from disk)
|
||||||
serialize(ar, NonNullPtr<Entity>(base_ptr));
|
serialize(ar, NonNullPtr<Entity>(base_ptr));
|
||||||
|
|
||||||
|
// 4. Advance generator counter to avoid collisions with loaded IDs
|
||||||
|
if (base_ptr->ID >= EntityManager::ID)
|
||||||
|
{
|
||||||
|
EntityManager::ID = base_ptr->ID + 1;
|
||||||
|
}
|
||||||
|
|
||||||
// Freshly loaded entity matches disk state exactly
|
// Freshly loaded entity matches disk state exactly
|
||||||
base_ptr->is_dirty = false;
|
base_ptr->is_dirty = false;
|
||||||
|
|
||||||
@@ -708,9 +736,11 @@ if (ImGui::DragFloat3("Position", pos, 0.1f))
|
|||||||
## 7. Step-by-Step Implementation Roadmap
|
## 7. Step-by-Step Implementation Roadmap
|
||||||
|
|
||||||
### Phase 1: Data Structures & Header Definitions
|
### Phase 1: Data Structures & Header Definitions
|
||||||
1. **Update `Entity.h`:**
|
1. **Update `Class.h` & `Entity.h`:**
|
||||||
|
- Add `using default_init_fct_type = void (*)(void* payload);` and `default_init_fct` to `struct Class` and `MakeClass`.
|
||||||
|
- Update `DEFINE_ENTITY_VERSIONED` and `DEFINE_CLASS_VERSIONED` to define `default_init_##entity` and pass it to `MakeClass`.
|
||||||
- Add `bool is_dirty = false;` to `struct Entity`.
|
- Add `bool is_dirty = false;` to `struct Entity`.
|
||||||
- Update `MakeEntity<EntityType>` to delegate to `AllocateEntity`.
|
- Update `MakeEntity<EntityType>` to assign `base_ptr->ID = EntityManager::ID++;` and delegate allocation and defaults cleanly to `AllocateEntity`.
|
||||||
2. **Update `EntityManager.h`:**
|
2. **Update `EntityManager.h`:**
|
||||||
- Declare `[[nodiscard]] Entity* AllocateEntity(EntityManager& manager, Class* class_ptr);`.
|
- Declare `[[nodiscard]] Entity* AllocateEntity(EntityManager& manager, Class* class_ptr);`.
|
||||||
- Declare `void DestroyEntity(EntityManager& manager, EntityID id);`.
|
- Declare `void DestroyEntity(EntityManager& manager, EntityID id);`.
|
||||||
@@ -722,8 +752,9 @@ if (ImGui::DragFloat3("Position", pos, 0.1f))
|
|||||||
### Phase 2: Core Memory Allocation & Wiring in `EntityManager.cpp`
|
### Phase 2: Core Memory Allocation & Wiring in `EntityManager.cpp`
|
||||||
1. Implement `AllocateEntity`:
|
1. Implement `AllocateEntity`:
|
||||||
- Enforce parameter assertions.
|
- Enforce parameter assertions.
|
||||||
- Push to `manager.Entities`.
|
- Push to `manager.Entities` with `baseTemplate.ID = 0` (unassigned).
|
||||||
- Allocate zeroed block in `manager.by_type[kind].arena`.
|
- Allocate block in `manager.by_type[kind].arena`.
|
||||||
|
- Call `derivedClassPtr->default_init_fct(rawMemory)` (or `MemZero` if null) to initialize struct defaults.
|
||||||
- Wire mutual pointers (`base->derived` and `derived->base`).
|
- Wire mutual pointers (`base->derived` and `derived->base`).
|
||||||
- Increment `typedArray.count` and initialize `typedArray.array`.
|
- Increment `typedArray.count` and initialize `typedArray.array`.
|
||||||
2. Implement `DestroyEntity` & `RemoveDerivedComponent`:
|
2. Implement `DestroyEntity` & `RemoveDerivedComponent`:
|
||||||
@@ -736,6 +767,7 @@ if (ImGui::DragFloat3("Position", pos, 0.1f))
|
|||||||
2. In `World.cpp`:
|
2. In `World.cpp`:
|
||||||
- Implement `serialize_entity_asset(Archive& ar, NonNullPtr<Entity> entity, String filepath)`.
|
- Implement `serialize_entity_asset(Archive& ar, NonNullPtr<Entity> entity, String filepath)`.
|
||||||
- Implement `deserialize_entity_asset(EntityManager& manager, Archive& ar, String filepath)`.
|
- Implement `deserialize_entity_asset(EntityManager& manager, Archive& ar, String filepath)`.
|
||||||
|
- In `deserialize_entity_in_place`, advance `EntityManager::ID` past `base_ptr->ID` to prevent ID collisions.
|
||||||
|
|
||||||
### Phase 4: World Save/Load Pipeline & Disk Deletion
|
### Phase 4: World Save/Load Pipeline & Disk Deletion
|
||||||
1. In `World.cpp`:
|
1. In `World.cpp`:
|
||||||
@@ -803,6 +835,8 @@ namespace UnitTest
|
|||||||
// 1. Allocate Inert Entity via AllocateEntity
|
// 1. Allocate Inert Entity via AllocateEntity
|
||||||
Entity* base_entity = AllocateEntity(manager, Inert::kind);
|
Entity* base_entity = AllocateEntity(manager, Inert::kind);
|
||||||
Assert(base_entity != nullptr);
|
Assert(base_entity != nullptr);
|
||||||
|
Assert(base_entity->ID == 0); // Pure memory allocator leaves ID unassigned (0) until MakeEntity or deserialization
|
||||||
|
base_entity->ID = EntityManager::ID++;
|
||||||
Assert(base_entity->ID > 0);
|
Assert(base_entity->ID > 0);
|
||||||
Assert(base_entity->derived_kind == Inert::kind);
|
Assert(base_entity->derived_kind == Inert::kind);
|
||||||
Assert(base_entity->derived != nullptr);
|
Assert(base_entity->derived != nullptr);
|
||||||
@@ -865,6 +899,7 @@ namespace UnitTest
|
|||||||
|
|
||||||
Assert(loaded_base != nullptr);
|
Assert(loaded_base != nullptr);
|
||||||
Assert(loaded_base->ID == original_id);
|
Assert(loaded_base->ID == original_id);
|
||||||
|
Assert(EntityManager::ID > original_id); // Counter was advanced past loaded ID to prevent collisions
|
||||||
Assert(loaded_base->position.x == 12.5f);
|
Assert(loaded_base->position.x == 12.5f);
|
||||||
Assert(loaded_base->position.y == -44.0f);
|
Assert(loaded_base->position.y == -44.0f);
|
||||||
Assert(loaded_base->position.z == 108.2f);
|
Assert(loaded_base->position.z == 108.2f);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <Core/Memory/MemoryArena.h>
|
#include <Core/Memory/MemoryArena.h>
|
||||||
#include <Data/World.h>
|
#include <Data/World.h>
|
||||||
#include <Entity/Entity.h>
|
#include <Entity/Entity.h>
|
||||||
|
#include <Entity/entity_types.h>
|
||||||
#include <Entity/EntityManager.h>
|
#include <Entity/EntityManager.h>
|
||||||
#include <Graphics/Camera.h>
|
#include <Graphics/Camera.h>
|
||||||
#include <Graphics/MeshRenderer.h>
|
#include <Graphics/MeshRenderer.h>
|
||||||
|
|||||||
@@ -7,7 +7,25 @@
|
|||||||
|
|
||||||
struct Archive;
|
struct Archive;
|
||||||
|
|
||||||
using serialize_fct_type = void (*)(Archive&, uint16 version, void* payload);
|
using serialize_fct_type = void(Archive&, uint16 version, void* payload);
|
||||||
|
using serialize_fct_ptr = serialize_fct_type*;
|
||||||
|
|
||||||
|
using initialize_fct_type = void(void* payload);
|
||||||
|
using initialize_fct_ptr = initialize_fct_type*;
|
||||||
|
|
||||||
|
template <typename Type>
|
||||||
|
void serialize_thunk(Archive& ar, uint16 version, void* payload)
|
||||||
|
{
|
||||||
|
Assert(payload);
|
||||||
|
serialize(ar, version, *((Type*)payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Type>
|
||||||
|
void initialize_thunk(void* payload)
|
||||||
|
{
|
||||||
|
auto& typed_val = *(Type*)payload;
|
||||||
|
typed_val = {};
|
||||||
|
}
|
||||||
|
|
||||||
struct Class
|
struct Class
|
||||||
{
|
{
|
||||||
@@ -15,7 +33,8 @@ struct Class
|
|||||||
uint8 kind;
|
uint8 kind;
|
||||||
uint16 version;
|
uint16 version;
|
||||||
const Class* base_class;
|
const Class* base_class;
|
||||||
serialize_fct_type serialize_fct;
|
initialize_fct_ptr initialize_fct;
|
||||||
|
serialize_fct_ptr serialize_fct;
|
||||||
size_t size_of;
|
size_t size_of;
|
||||||
size_t alignment;
|
size_t alignment;
|
||||||
|
|
||||||
@@ -26,13 +45,13 @@ struct Class
|
|||||||
|
|
||||||
#define DECLARE_CLASS() static Class* kind;
|
#define DECLARE_CLASS() static Class* kind;
|
||||||
|
|
||||||
#define DEFINE_CLASS_VERSIONED(cls, version, base_class, serialize_fct) \
|
#define DEFINE_CLASS_VERSIONED(cls, version, base_class) \
|
||||||
constexpr Class classKind##cls = \
|
constexpr Class class_kind_##cls = MakeClass(ConstString(#cls), 0, (version), (base_class), sizeof(cls), \
|
||||||
MakeClass(ConstString(#cls), 0, (version), (base_class), sizeof(cls), alignof(cls), (serialize_fct)); \
|
alignof(cls), (&initialize_thunk<cls>), (&serialize_thunk<cls>)); \
|
||||||
Class* cls::kind = const_cast<Class*>(&classKind##cls);
|
Class* cls::kind = const_cast<Class*>(&class_kind_##cls);
|
||||||
|
|
||||||
consteval Class MakeClass(String name, uint8 kind, uint16 version, const Class* base_class, size_t size, size_t align,
|
consteval Class MakeClass(String name, uint8 kind, uint16 version, const Class* base_class, size_t size, size_t align,
|
||||||
serialize_fct_type fct)
|
initialize_fct_ptr init_fct, serialize_fct_ptr serde_fct)
|
||||||
{
|
{
|
||||||
Class cls = {};
|
Class cls = {};
|
||||||
cls.CRC = crc32(name.Str, name.Size);
|
cls.CRC = crc32(name.Str, name.Size);
|
||||||
@@ -41,7 +60,8 @@ consteval Class MakeClass(String name, uint8 kind, uint16 version, const Class*
|
|||||||
cls.base_class = base_class;
|
cls.base_class = base_class;
|
||||||
cls.size_of = size;
|
cls.size_of = size;
|
||||||
cls.alignment = align;
|
cls.alignment = align;
|
||||||
cls.serialize_fct = fct;
|
cls.initialize_fct = init_fct;
|
||||||
|
cls.serialize_fct = serde_fct;
|
||||||
|
|
||||||
#if JULIET_DEBUG
|
#if JULIET_DEBUG
|
||||||
cls.Name = name;
|
cls.Name = name;
|
||||||
|
|||||||
@@ -63,20 +63,17 @@ namespace UnitTest
|
|||||||
float pos_x = 10.0f;
|
float pos_x = 10.0f;
|
||||||
};
|
};
|
||||||
|
|
||||||
internal void serialize_dummy_vehicle(Archive& ar, uint16 /*version*/, void* payload)
|
DEFINE_CLASS_VERSIONED(DummyVehicle, 1, nullptr)
|
||||||
|
|
||||||
|
internal void serialize(Archive& ar, uint16 /*version*/, DummyVehicle& vehicle)
|
||||||
{
|
{
|
||||||
Assert(payload != nullptr);
|
SERIALIZE(ar, max_speed, vehicle.max_speed);
|
||||||
auto* vehicle = static_cast<DummyVehicle*>(payload);
|
SERIALIZE(ar, gear_count, vehicle.gear_count);
|
||||||
|
SERIALIZE(ar, chassis_uuid, vehicle.chassis_uuid);
|
||||||
SERIALIZE(ar, max_speed, vehicle->max_speed);
|
SERIALIZE(ar, turbo, vehicle.turbo);
|
||||||
SERIALIZE(ar, gear_count, vehicle->gear_count);
|
SERIALIZE(ar, pos_x, vehicle.pos_x);
|
||||||
SERIALIZE(ar, chassis_uuid, vehicle->chassis_uuid);
|
|
||||||
SERIALIZE(ar, turbo, vehicle->turbo);
|
|
||||||
SERIALIZE(ar, pos_x, vehicle->pos_x);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_CLASS_VERSIONED(DummyVehicle, 1, nullptr, serialize_dummy_vehicle)
|
|
||||||
|
|
||||||
internal void test_default_value_retention()
|
internal void test_default_value_retention()
|
||||||
{
|
{
|
||||||
TempArena temp = scratch_begin(nullptr, 0);
|
TempArena temp = scratch_begin(nullptr, 0);
|
||||||
@@ -116,14 +113,12 @@ namespace UnitTest
|
|||||||
float base_damage = 25.0f;
|
float base_damage = 25.0f;
|
||||||
};
|
};
|
||||||
|
|
||||||
internal void serialize_dummy_weapon_base(Archive& ar, uint16 /*version*/, void* payload)
|
DEFINE_CLASS_VERSIONED(DummyWeaponBase, 1, nullptr)
|
||||||
{
|
|
||||||
Assert(payload != nullptr);
|
|
||||||
auto* base_weapon = static_cast<DummyWeaponBase*>(payload);
|
|
||||||
SERIALIZE(ar, base_damage, base_weapon->base_damage);
|
|
||||||
}
|
|
||||||
|
|
||||||
DEFINE_CLASS_VERSIONED(DummyWeaponBase, 1, nullptr, serialize_dummy_weapon_base)
|
internal void serialize(Archive& ar, uint16 /*version*/, DummyWeaponBase& base_weapon)
|
||||||
|
{
|
||||||
|
SERIALIZE(ar, base_damage, base_weapon.base_damage);
|
||||||
|
}
|
||||||
|
|
||||||
// Derived test class for testing version migration (v1 -> v2)
|
// Derived test class for testing version migration (v1 -> v2)
|
||||||
struct LegacyWeapon
|
struct LegacyWeapon
|
||||||
@@ -134,11 +129,11 @@ namespace UnitTest
|
|||||||
float velocity_y = 0.0f;
|
float velocity_y = 0.0f;
|
||||||
};
|
};
|
||||||
|
|
||||||
internal void serialize_legacy_weapon(Archive& ar, uint16 version, void* payload)
|
// Inherits from DummyWeaponBase (base_class != nullptr) to verify derived class_version handling
|
||||||
{
|
DEFINE_CLASS_VERSIONED(LegacyWeapon, 2, &class_kind_DummyWeaponBase)
|
||||||
Assert(payload != nullptr);
|
|
||||||
auto* weapon = static_cast<LegacyWeapon*>(payload);
|
|
||||||
|
|
||||||
|
internal void serialize(Archive& ar, uint16 version, LegacyWeapon& weapon)
|
||||||
|
{
|
||||||
if (ar.loading)
|
if (ar.loading)
|
||||||
{
|
{
|
||||||
if (version < 2)
|
if (version < 2)
|
||||||
@@ -147,26 +142,23 @@ namespace UnitTest
|
|||||||
float deprecated_speed = 0.0f;
|
float deprecated_speed = 0.0f;
|
||||||
if (SERIALIZE(ar, speed, deprecated_speed))
|
if (SERIALIZE(ar, speed, deprecated_speed))
|
||||||
{
|
{
|
||||||
weapon->velocity_x = deprecated_speed;
|
weapon.velocity_x = deprecated_speed;
|
||||||
weapon->velocity_y = 0.0f;
|
weapon.velocity_y = 0.0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
SERIALIZE(ar, velocity_x, weapon->velocity_x);
|
SERIALIZE(ar, velocity_x, weapon.velocity_x);
|
||||||
SERIALIZE(ar, velocity_y, weapon->velocity_y);
|
SERIALIZE(ar, velocity_y, weapon.velocity_y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
SERIALIZE(ar, velocity_x, weapon->velocity_x);
|
SERIALIZE(ar, velocity_x, weapon.velocity_x);
|
||||||
SERIALIZE(ar, velocity_y, weapon->velocity_y);
|
SERIALIZE(ar, velocity_y, weapon.velocity_y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inherits from DummyWeaponBase (base_class != nullptr) to verify derived class_version handling
|
|
||||||
DEFINE_CLASS_VERSIONED(LegacyWeapon, 2, &classKindDummyWeaponBase, serialize_legacy_weapon)
|
|
||||||
|
|
||||||
// Test 3: Deprecation migration from v1 to v2 via stack variable
|
// Test 3: Deprecation migration from v1 to v2 via stack variable
|
||||||
internal void test_deprecation_migration()
|
internal void test_deprecation_migration()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user