conversion to c by gemini 4 argon
This commit is contained in:
1 parent
b70e2d21d2
commit
f9356f9ba7
369 files changed
+18074
-107634
No files matched your search
@@ -1,4 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
constexpr index_t kPlayCamera = 0;
|
||||
constexpr index_t kDebugCamera = 1;
|
||||
#include <Juliet.h>
|
||||
|
||||
#define kPlayCamera ((index_t)0)
|
||||
#define kDebugCamera ((index_t)1)
|
||||
@@ -0,0 +1,163 @@
|
||||
#include <Controller/DebugCameraController.h>
|
||||
|
||||
#include <Controller/ControllerUtils.h>
|
||||
#include <Core/Common/CoreUtils.h>
|
||||
#include <Core/HAL/Keyboard/Keyboard.h>
|
||||
#include <Core/HAL/Mouse/Mouse.h>
|
||||
#include <Core/Memory/MemoryArena.h>
|
||||
#include <Graphics/Camera.h>
|
||||
#include <dcimgui.h>
|
||||
#include <math.h>
|
||||
|
||||
global bool8 gIsDebugCameraActive = false;
|
||||
global index_t gPreviousCameraIndex = 0;
|
||||
global float gPitch = 0.0f;
|
||||
global float gYaw = 0.0f;
|
||||
global bool8 gFirstUpdate = true;
|
||||
global bool8 gIsFpsModeActive = false;
|
||||
global bool8 gWasRightMouseButtonDown = false;
|
||||
|
||||
void ActivateDebugController(void)
|
||||
{
|
||||
Assert(gIsDebugCameraActive == false);
|
||||
|
||||
Camera* currentCam = GetCurrentCamera();
|
||||
gPreviousCameraIndex = currentCam->Index;
|
||||
|
||||
SetCurrentCamera(kDebugCamera);
|
||||
|
||||
gIsDebugCameraActive = true;
|
||||
gFirstUpdate = true;
|
||||
gIsFpsModeActive = false;
|
||||
gWasRightMouseButtonDown = false;
|
||||
}
|
||||
|
||||
void DeactivateDebugController(void)
|
||||
{
|
||||
Assert(gIsDebugCameraActive);
|
||||
|
||||
gIsDebugCameraActive = false;
|
||||
|
||||
SetCurrentCamera(gPreviousCameraIndex);
|
||||
}
|
||||
|
||||
bool8 IsDebugControllerActive(void)
|
||||
{
|
||||
return gIsDebugCameraActive;
|
||||
}
|
||||
|
||||
void UpdateDebugController(float dt)
|
||||
{
|
||||
Camera* currentCam = GetCurrentCamera();
|
||||
|
||||
if (gFirstUpdate)
|
||||
{
|
||||
Vector3 origin = { 0.0f, 0.0f, 0.0f };
|
||||
Vector3 dir = Vector3Sub(origin, currentCam->Position);
|
||||
dir = Normalize(dir);
|
||||
gPitch = asinf(dir.z);
|
||||
gYaw = atan2f(dir.y, dir.x);
|
||||
gFirstUpdate = false;
|
||||
|
||||
Vector3 forward = { cosf(gPitch) * cosf(gYaw), cosf(gPitch) * sinf(gYaw), sinf(gPitch) };
|
||||
Vector3 worldUp = { 0.0f, 0.0f, 1.0f };
|
||||
Vector3 right = Normalize(Cross(worldUp, forward));
|
||||
currentCam->Target = Vector3Add(currentCam->Position, forward);
|
||||
currentCam->Up = Cross(forward, right);
|
||||
}
|
||||
|
||||
bool8 isRightMouseButtonDown = IsMouseButtonDown(MouseButton_Right);
|
||||
if (isRightMouseButtonDown && !gWasRightMouseButtonDown)
|
||||
{
|
||||
gIsFpsModeActive = !gIsFpsModeActive;
|
||||
}
|
||||
gWasRightMouseButtonDown = isRightMouseButtonDown;
|
||||
|
||||
if (gIsFpsModeActive)
|
||||
{
|
||||
MousePosition mouseDelta = GetMouseDelta();
|
||||
|
||||
float sensitivity = 0.005f;
|
||||
gYaw += (float)mouseDelta.X * sensitivity;
|
||||
gPitch -= (float)mouseDelta.Y * sensitivity;
|
||||
|
||||
gPitch = Min(gPitch, 1.5f);
|
||||
gPitch = Max(gPitch, -1.5f);
|
||||
|
||||
if (IsKeyDown(ScanCode_Q))
|
||||
{
|
||||
gYaw -= 2.0f * dt;
|
||||
}
|
||||
if (IsKeyDown(ScanCode_E))
|
||||
{
|
||||
gYaw += 2.0f * dt;
|
||||
}
|
||||
|
||||
Vector3 forward = { cosf(gPitch) * cosf(gYaw), cosf(gPitch) * sinf(gYaw), sinf(gPitch) };
|
||||
Vector3 worldUp = { 0.0f, 0.0f, 1.0f };
|
||||
Vector3 right = Normalize(Cross(worldUp, forward));
|
||||
Vector3 defaultUp = Cross(forward, right);
|
||||
|
||||
const float kMovementPerFrame = 10.f; // 10m/s
|
||||
|
||||
float speedPerFrame = kMovementPerFrame;
|
||||
if (IsKeyDown(ScanCode_LeftShift))
|
||||
{
|
||||
speedPerFrame *= 10.f; // 100m/s
|
||||
}
|
||||
|
||||
if (IsKeyDown(ScanCode_W))
|
||||
{
|
||||
currentCam->Position = Vector3Add(currentCam->Position, Vector3Scale(forward, speedPerFrame * dt));
|
||||
}
|
||||
if (IsKeyDown(ScanCode_S))
|
||||
{
|
||||
currentCam->Position = Vector3Sub(currentCam->Position, Vector3Scale(forward, speedPerFrame * dt));
|
||||
}
|
||||
if (IsKeyDown(ScanCode_D))
|
||||
{
|
||||
currentCam->Position = Vector3Add(currentCam->Position, Vector3Scale(right, speedPerFrame * dt));
|
||||
}
|
||||
if (IsKeyDown(ScanCode_A))
|
||||
{
|
||||
currentCam->Position = Vector3Sub(currentCam->Position, Vector3Scale(right, speedPerFrame * dt));
|
||||
}
|
||||
if (IsKeyDown(ScanCode_Space))
|
||||
{
|
||||
currentCam->Position = Vector3Add(currentCam->Position, Vector3Scale(defaultUp, speedPerFrame * dt));
|
||||
}
|
||||
if (IsKeyDown(ScanCode_LeftControl))
|
||||
{
|
||||
currentCam->Position = Vector3Sub(currentCam->Position, Vector3Scale(defaultUp, speedPerFrame * dt));
|
||||
}
|
||||
|
||||
currentCam->Target = Vector3Add(currentCam->Position, forward);
|
||||
currentCam->Up = defaultUp;
|
||||
}
|
||||
}
|
||||
|
||||
#if JULIET_DEBUG
|
||||
void RenderImGuiDebugController(float dt)
|
||||
{
|
||||
Camera* currentCam = GetCurrentCamera();
|
||||
|
||||
ImGui_Text("Delta time: %f", (double)dt);
|
||||
|
||||
ImGui_Text("Camera:");
|
||||
ImGui_Indent();
|
||||
ImGui_BulletText("Index: %zu", (size_t)currentCam->Index);
|
||||
ImGui_BulletText("Position: (%.3f, %.3f, %.3f)", (double)currentCam->Position.x, (double)currentCam->Position.y,
|
||||
(double)currentCam->Position.z);
|
||||
ImGui_BulletText("Target: (%.3f, %.3f, %.3f)", (double)currentCam->Target.x, (double)currentCam->Target.y,
|
||||
(double)currentCam->Target.z);
|
||||
ImGui_BulletText("Up: (%.3f, %.3f, %.3f)", (double)currentCam->Up.x, (double)currentCam->Up.y,
|
||||
(double)currentCam->Up.z);
|
||||
ImGui_BulletText("FOV: %.3f rad", (double)currentCam->FOV);
|
||||
ImGui_BulletText("AspectRatio: %.3f", (double)currentCam->AspectRatio);
|
||||
ImGui_BulletText("NearPlane: %.3f", (double)currentCam->NearPlane);
|
||||
ImGui_BulletText("FarPlane: %.3f", (double)currentCam->FarPlane);
|
||||
ImGui_BulletText("Pitch: %.3f, Yaw: %.3f", (double)gPitch, (double)gYaw);
|
||||
ImGui_BulletText("FPS Mode: %s", gIsFpsModeActive ? "Active" : "Inactive");
|
||||
ImGui_Unindent();
|
||||
}
|
||||
#endif
|
||||
@@ -1,170 +0,0 @@
|
||||
#include <Controller/DebugCameraController.h>
|
||||
|
||||
#include <Controller/ControllerUtils.h>
|
||||
#include <Core/Common/CoreUtils.h>
|
||||
#include <Core/HAL/Keyboard/Keyboard.h>
|
||||
#include <Core/HAL/Mouse/Mouse.h>
|
||||
#include <Graphics/Camera.h>
|
||||
#include <imgui.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
bool gIsDebugCameraActive = false;
|
||||
index_t gPreviousCameraIndex = 0;
|
||||
|
||||
float gPitch = 0.0f;
|
||||
float gYaw = 0.0f;
|
||||
bool gFirstUpdate = true;
|
||||
bool gIsFpsModeActive = false;
|
||||
bool gWasRightMouseButtonDown = false;
|
||||
} // namespace
|
||||
|
||||
void ActivateDebugController()
|
||||
{
|
||||
Assert(gIsDebugCameraActive == false);
|
||||
|
||||
Camera* currentCam = GetCurrentCamera();
|
||||
gPreviousCameraIndex = currentCam->Index;
|
||||
|
||||
SetCurrentCamera(kDebugCamera);
|
||||
|
||||
gIsDebugCameraActive = true;
|
||||
gFirstUpdate = true;
|
||||
gIsFpsModeActive = false;
|
||||
gWasRightMouseButtonDown = false;
|
||||
}
|
||||
|
||||
void DeactivateDebugController()
|
||||
{
|
||||
Assert(gIsDebugCameraActive);
|
||||
|
||||
gIsDebugCameraActive = false;
|
||||
|
||||
SetCurrentCamera(gPreviousCameraIndex);
|
||||
}
|
||||
|
||||
bool IsDebugControllerActive()
|
||||
{
|
||||
return gIsDebugCameraActive;
|
||||
}
|
||||
|
||||
void UpdateDebugController(float dt)
|
||||
{
|
||||
Camera* currentCam = GetCurrentCamera();
|
||||
|
||||
if (gFirstUpdate)
|
||||
{
|
||||
Vector3 dir = Vector3{ 0.0f, 0.0f, 0.0f } - currentCam->Position;
|
||||
dir = Normalize(dir);
|
||||
gPitch = asinf(dir.z);
|
||||
gYaw = atan2f(dir.y, dir.x);
|
||||
gFirstUpdate = false;
|
||||
|
||||
Vector3 forward;
|
||||
forward.x = cosf(gPitch) * cosf(gYaw);
|
||||
forward.y = cosf(gPitch) * sinf(gYaw);
|
||||
forward.z = sinf(gPitch);
|
||||
Vector3 right = Normalize(Cross(Vector3{ 0.0f, 0.0f, 1.0f }, forward));
|
||||
currentCam->Target = currentCam->Position + forward;
|
||||
currentCam->Up = Cross(forward, right);
|
||||
}
|
||||
|
||||
bool isRightMouseButtonDown = IsMouseButtonDown(MouseButton::Right);
|
||||
if (isRightMouseButtonDown && !gWasRightMouseButtonDown)
|
||||
{
|
||||
gIsFpsModeActive = !gIsFpsModeActive;
|
||||
}
|
||||
gWasRightMouseButtonDown = isRightMouseButtonDown;
|
||||
|
||||
if (!gIsFpsModeActive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MousePosition mouseDelta = GetMouseDelta();
|
||||
|
||||
float sensitivity = 0.005f;
|
||||
gYaw += mouseDelta.X * sensitivity;
|
||||
gPitch -= mouseDelta.Y * sensitivity;
|
||||
|
||||
gPitch = std::min(gPitch, 1.5f);
|
||||
gPitch = std::max(gPitch, -1.5f);
|
||||
|
||||
if (IsKeyDown(ScanCode::Q))
|
||||
{
|
||||
gYaw -= 2.0f * dt;
|
||||
}
|
||||
if (IsKeyDown(ScanCode::E))
|
||||
{
|
||||
gYaw += 2.0f * dt;
|
||||
}
|
||||
|
||||
Vector3 forward;
|
||||
forward.x = cosf(gPitch) * cosf(gYaw);
|
||||
forward.y = cosf(gPitch) * sinf(gYaw);
|
||||
forward.z = sinf(gPitch);
|
||||
|
||||
Vector3 right = Normalize(Cross(Vector3{ 0.0f, 0.0f, 1.0f }, forward));
|
||||
Vector3 defaultUp = Cross(forward, right);
|
||||
|
||||
static const float kMovementPerFrame = 10.f; // 10m/s
|
||||
|
||||
float speedPerFrame = kMovementPerFrame;
|
||||
if (IsKeyDown(ScanCode::LeftShift))
|
||||
{
|
||||
speedPerFrame *= 10.f; // 100m/s
|
||||
}
|
||||
|
||||
if (IsKeyDown(ScanCode::W))
|
||||
{
|
||||
currentCam->Position = currentCam->Position + forward * (speedPerFrame * dt);
|
||||
}
|
||||
if (IsKeyDown(ScanCode::S))
|
||||
{
|
||||
currentCam->Position = currentCam->Position - forward * (speedPerFrame * dt);
|
||||
}
|
||||
if (IsKeyDown(ScanCode::D))
|
||||
{
|
||||
currentCam->Position = currentCam->Position + right * (speedPerFrame * dt);
|
||||
}
|
||||
if (IsKeyDown(ScanCode::A))
|
||||
{
|
||||
currentCam->Position = currentCam->Position - right * (speedPerFrame * dt);
|
||||
}
|
||||
if (IsKeyDown(ScanCode::Space))
|
||||
{
|
||||
currentCam->Position = currentCam->Position + defaultUp * (speedPerFrame * dt);
|
||||
}
|
||||
if (IsKeyDown(ScanCode::LeftControl))
|
||||
{
|
||||
currentCam->Position = currentCam->Position - defaultUp * (speedPerFrame * dt);
|
||||
}
|
||||
|
||||
currentCam->Target = currentCam->Position + forward;
|
||||
currentCam->Up = defaultUp;
|
||||
}
|
||||
|
||||
#if JULIET_DEBUG
|
||||
void RenderImGuiDebugController(float dt)
|
||||
{
|
||||
Camera* currentCam = GetCurrentCamera();
|
||||
|
||||
ImGui::Text("Delta time: %f", dt);
|
||||
|
||||
ImGui::Text("Camera:");
|
||||
ImGui::Indent();
|
||||
ImGui::BulletText("Index: %zu", (size_t)currentCam->Index);
|
||||
ImGui::BulletText("Position: (%.3f, %.3f, %.3f)", currentCam->Position.x, currentCam->Position.y,
|
||||
currentCam->Position.z);
|
||||
ImGui::BulletText("Target: (%.3f, %.3f, %.3f)", currentCam->Target.x, currentCam->Target.y, currentCam->Target.z);
|
||||
ImGui::BulletText("Up: (%.3f, %.3f, %.3f)", currentCam->Up.x, currentCam->Up.y, currentCam->Up.z);
|
||||
ImGui::BulletText("FOV: %.3f rad", currentCam->FOV);
|
||||
ImGui::BulletText("AspectRatio: %.3f", currentCam->AspectRatio);
|
||||
ImGui::BulletText("NearPlane: %.3f", currentCam->NearPlane);
|
||||
ImGui::BulletText("FarPlane: %.3f", currentCam->FarPlane);
|
||||
ImGui::BulletText("Pitch: %.3f, Yaw: %.3f", gPitch, gYaw);
|
||||
ImGui::BulletText("FPS Mode: %s", gIsFpsModeActive ? "Active" : "Inactive");
|
||||
ImGui::Unindent();
|
||||
}
|
||||
#endif
|
||||
@@ -1,11 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <Juliet.h>
|
||||
|
||||
// TODO : Should support Debug/Profile/Release properly.
|
||||
// I think debug profile should have this controller but not release
|
||||
void ActivateDebugController();
|
||||
void DeactivateDebugController();
|
||||
bool IsDebugControllerActive();
|
||||
void UpdateDebugController(float dt);
|
||||
void ActivateDebugController(void);
|
||||
void DeactivateDebugController(void);
|
||||
bool8 IsDebugControllerActive(void);
|
||||
void UpdateDebugController(float dt);
|
||||
|
||||
#if JULIET_DEBUG
|
||||
void RenderImGuiDebugController(float dt);
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
#include <Data/World.h>
|
||||
|
||||
#include <Core/Common/CoreUtils.h>
|
||||
#include <Core/Common/serialization.h>
|
||||
#include <Core/HAL/Filesystem/Filesystem.h>
|
||||
#include <Core/HAL/IO/IOStream.h>
|
||||
#include <Core/Logging/LogManager.h>
|
||||
#include <Core/Logging/LogTypes.h>
|
||||
#include <Core/Thread/ThreadContext.h>
|
||||
#include <Entity/EntityManager.h>
|
||||
#include <Graphics/MeshRenderer.h>
|
||||
|
||||
#ifdef JULIET_ENABLE_IMGUI
|
||||
#include <dcimgui.h>
|
||||
#endif
|
||||
|
||||
void InitWorld(World* world, Arena* arena)
|
||||
{
|
||||
Assert(world != NULL);
|
||||
Assert(arena != NULL);
|
||||
world->WorldArena = arena;
|
||||
}
|
||||
|
||||
void ShutdownWorld(World* world)
|
||||
{
|
||||
Assert(world != NULL);
|
||||
world->WorldArena = NULL;
|
||||
}
|
||||
|
||||
void AddToWorld(World* world, Entity* entity)
|
||||
{
|
||||
(void)world;
|
||||
(void)entity;
|
||||
}
|
||||
|
||||
void RemoveWorldEntity(World* world, size_t index)
|
||||
{
|
||||
(void)world;
|
||||
(void)index;
|
||||
}
|
||||
|
||||
void SerializeWorld(Archive* ar, World* world, String filename)
|
||||
{
|
||||
(void)world;
|
||||
Assert(ar != NULL);
|
||||
Assert(IsValidString(filename));
|
||||
|
||||
EntityManager* entityManager = GetEntityManager();
|
||||
|
||||
if (ar->loading)
|
||||
{
|
||||
// Load
|
||||
ByteBuffer fileBuffer = LoadFile(ar->arena, filename);
|
||||
|
||||
// TEST SERIALIZATION
|
||||
ParsedArchive archive = tokenize_archive(ar->arena, fileBuffer);
|
||||
#if JULIET_DEBUG
|
||||
audit_unconsumed_properties(&archive, ConstString("World"));
|
||||
#else
|
||||
(void)archive;
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
// Save
|
||||
IOStream* stream = IOFromFile(ar->arena, filename, WrapString("wb"));
|
||||
|
||||
index_t beginPos = ArenaPos(ar->arena);
|
||||
|
||||
// Headers
|
||||
WorldFileHeader* header = ArenaPushStruct(ar->arena, WorldFileHeader);
|
||||
header->Magic = kWorldMagic;
|
||||
|
||||
// TODO : Move to a one file per entity model
|
||||
for (uint8 i = 0; i < ENTITY(Count); ++i)
|
||||
{
|
||||
typed_entity_array* type = &entityManager->by_type[i];
|
||||
serialize_elem(ar, type->count);
|
||||
if (type->count > 0 && type->array != NULL)
|
||||
{
|
||||
EntityTemplate* element = type->array;
|
||||
uint8* rawElement = (uint8*)element;
|
||||
size_t stride = element->base->derived_kind->size_of;
|
||||
for (index_t idx = 0; idx < type->count; ++idx)
|
||||
{
|
||||
Entity* entity = ((EntityTemplate*)(rawElement + (idx * stride)))->base;
|
||||
serialize_entity(ar, entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write
|
||||
index_t endPos = ArenaPos(ar->arena);
|
||||
ByteBuffer writeBuffer = { .Data = (Byte*)header, .Size = endPos - beginPos };
|
||||
|
||||
size_t written = IOWrite(stream, writeBuffer);
|
||||
|
||||
Assert(writeBuffer.Size == written);
|
||||
|
||||
IOClose(stream);
|
||||
}
|
||||
}
|
||||
|
||||
bool8 LoadWorld(World* world, String filename)
|
||||
{
|
||||
(void)world;
|
||||
Assert(IsValidString(filename));
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef JULIET_EDITOR
|
||||
void RenderWorldEditorUI(World* world)
|
||||
{
|
||||
Assert(world != NULL);
|
||||
|
||||
if (ImGui_Begin("World Editor", NULL, ImGuiWindowFlags_None))
|
||||
{
|
||||
TempArena temp = scratch_begin(NULL, 0);
|
||||
static char worldFilePath[256] = "/world.bin";
|
||||
ImGui_InputText("World File", worldFilePath, sizeof(worldFilePath));
|
||||
|
||||
Path path = GetAssetPath(temp.Arena, AssetType_JAsset, WrapString(worldFilePath));
|
||||
|
||||
if (ImGui_Button("Save World"))
|
||||
{
|
||||
Archive data = { .arena = temp.Arena, .loading = false };
|
||||
SerializeWorld(&data, world, path.FullPath);
|
||||
}
|
||||
ImGui_SameLine(0.0f, -1.0f);
|
||||
if (ImGui_Button("Load World"))
|
||||
{
|
||||
Archive data = { .arena = temp.Arena, .loading = true };
|
||||
SerializeWorld(&data, world, path.FullPath);
|
||||
}
|
||||
|
||||
scratch_end(temp);
|
||||
}
|
||||
ImGui_End();
|
||||
}
|
||||
#endif
|
||||
@@ -1,322 +0,0 @@
|
||||
#include <Data/World.h>
|
||||
|
||||
#include <Core/Common/CoreUtils.h>
|
||||
#include <Core/Common/serialization.h>
|
||||
#include <Core/HAL/Filesystem/Filesystem.h>
|
||||
#include <Core/HAL/IO/IOStream.h>
|
||||
#include <Core/Logging/LogManager.h>
|
||||
#include <Core/Logging/LogTypes.h>
|
||||
#include <Core/Thread/ThreadContext.h>
|
||||
#include <Entity/EntityManager.h>
|
||||
#include <Graphics/MeshRenderer.h>
|
||||
|
||||
#ifdef JULIET_ENABLE_IMGUI
|
||||
#include <imgui.h>
|
||||
#endif
|
||||
|
||||
void InitWorld(NonNullPtr<World> world, NonNullPtr<Arena> arena)
|
||||
{
|
||||
world->WorldArena = arena.Get();
|
||||
}
|
||||
|
||||
void ShutdownWorld(NonNullPtr<World> world)
|
||||
{
|
||||
world->WorldArena = nullptr;
|
||||
}
|
||||
|
||||
void AddToWorld(NonNullPtr<World> /*world*/, NonNullPtr<Entity> /*entity*/)
|
||||
{
|
||||
// RegisterEntity(*world->EntityManager, entity.Get());
|
||||
}
|
||||
|
||||
void RemoveWorldEntity(World& /*world*/, size_t /*index*/) {}
|
||||
|
||||
void Serialize(Archive& ar, World& /*world*/, String filename)
|
||||
{
|
||||
Assert(IsValid(filename));
|
||||
|
||||
auto& entityManager = GetEntityManager();
|
||||
|
||||
if (ar.loading)
|
||||
{
|
||||
// Load
|
||||
ByteBuffer fileBuffer = LoadFile(ar.arena, filename);
|
||||
|
||||
// TEST SERIALIZATION
|
||||
ParsedArchive archive = tokenize_archive(ar.arena, fileBuffer);
|
||||
// ArchivePropertyNode* property = find_property(&archive, "Position"_crc32);
|
||||
audit_unconsumed_properties(&archive, ConstString("World"));
|
||||
|
||||
// if (fileBuffer.Size >= sizeof(WorldFileHeader))
|
||||
// {
|
||||
// ar.base_ptr = fileBuffer.Data;
|
||||
//
|
||||
// WorldFileHeader header;
|
||||
// serialize_elem(ar, header);
|
||||
// Assert(header.Magic == kWorldMagic);
|
||||
//
|
||||
// for (typed_entity_array& type : entityManager.by_type)
|
||||
// {
|
||||
// serialize_elem(ar, type.count);
|
||||
//
|
||||
// if (type.count > 0)
|
||||
// {
|
||||
//
|
||||
// // Unserialize the base entity to get informations
|
||||
// Entity entity;
|
||||
// // serialize(ar, &entity);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
else
|
||||
{
|
||||
// Save
|
||||
IOStream* stream = IOFromFile(ar.arena, filename, WrapString("wb"));
|
||||
|
||||
index_t beginPos = ArenaPos(ar.arena);
|
||||
|
||||
// Headers
|
||||
auto* header = ArenaPushStruct<WorldFileHeader>(ar.arena);
|
||||
header->Magic = kWorldMagic;
|
||||
|
||||
ar.base_ptr = header;
|
||||
|
||||
// TODO : Move to a one file per entity model
|
||||
for (typed_entity_array& type : entityManager.by_type)
|
||||
{
|
||||
serialize_elem(ar, type.count);
|
||||
auto* element = type.array;
|
||||
uint8* rawElement = reinterpret_cast<uint8*>(element);
|
||||
size_t stride = element->base->derived_kind->size_of;
|
||||
for (index_t idx = 0; idx < type.count; ++idx)
|
||||
{
|
||||
// Todo : utils
|
||||
// Get base entity from type
|
||||
Entity* entity = reinterpret_cast<EntityTemplate*>(rawElement + (idx * stride))->base;
|
||||
serialize(ar, entity);
|
||||
}
|
||||
}
|
||||
|
||||
// Write
|
||||
index_t endPos = ArenaPos(ar.arena);
|
||||
ByteBuffer writeBuffer = { .Data = reinterpret_cast<Byte*>(header), .Size = endPos - beginPos };
|
||||
|
||||
size_t written = IOWrite(stream, writeBuffer);
|
||||
|
||||
Assert(writeBuffer.Size == written);
|
||||
|
||||
IOClose(stream);
|
||||
}
|
||||
|
||||
//
|
||||
// uint32 entityCount = static_cast<uint32>(world.Entities.Size());
|
||||
// size_t totalBytes = sizeof(WorldFileHeader) + static_cast<size_t>(entityCount) * sizeof(WorldEntityDiskRecord);
|
||||
//
|
||||
// ArenaParams tempParams = { .ReserveSize = Megabytes(1), .Name = "WorldSaveArena" };
|
||||
// Arena* tempArena = ArenaAllocate(tempParams);
|
||||
// if (!tempArena)
|
||||
// {
|
||||
// LogError(LogCategory::Game, "SaveWorld: Failed to allocate memory for saving world.");
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// auto deferRelease = Defer([&]() { ArenaRelease(tempArena); });
|
||||
//
|
||||
// uint8* bufferData = ArenaPushArray<uint8, false>(tempArena, totalBytes);
|
||||
// if (!bufferData)
|
||||
// {
|
||||
// LogError(LogCategory::Game, "SaveWorld: Failed to allocate buffer in arena.");
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// auto* header = reinterpret_cast<WorldFileHeader*>(bufferData);
|
||||
// header->Magic = kWorldMagic;
|
||||
// header->Version = kWorldVersion;
|
||||
// header->EntityCount = entityCount;
|
||||
// header->Reserved = 0;
|
||||
//
|
||||
// auto* records = reinterpret_cast<WorldEntityDiskRecord*>(bufferData + sizeof(WorldFileHeader));
|
||||
// for (size_t i = 0; i < entityCount; ++i)
|
||||
// {
|
||||
// const Entity& ent = world.Entities[i];
|
||||
// records[i].X = ent.X;
|
||||
// records[i].Y = ent.Y;
|
||||
// records[i].Z = ent.Z;
|
||||
// }
|
||||
//
|
||||
// IOStream* stream = IOFromFile(tempArena, filename, WrapString("wb"));
|
||||
// if (!stream)
|
||||
// {
|
||||
// LogError(LogCategory::Game, "SaveWorld: Failed to open file for writing: %s", CStr(filename));
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// ByteBuffer writeBuffer = { .Data = reinterpret_cast<Byte*>(bufferData), .Size = totalBytes };
|
||||
//
|
||||
// size_t written = IOWrite(stream, writeBuffer);
|
||||
// IOClose(stream);
|
||||
//
|
||||
// if (written != totalBytes)
|
||||
// {
|
||||
// LogError(LogCategory::Game, "SaveWorld: Failed to write complete world data to %s (wrote %zu / %zu bytes)",
|
||||
// CStr(filename), written, totalBytes);
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// LogMessage(LogCategory::Game, "World successfully saved to %s (%u entities)", CStr(filename), entityCount);
|
||||
// return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool LoadWorld(World& /*world*/, String filename)
|
||||
{
|
||||
Assert(IsValid(filename));
|
||||
// Assert(world.WorldArena != nullptr);
|
||||
//
|
||||
// TempArena loadArena = scratch_begin(nullptr, 0);
|
||||
//
|
||||
// ByteBuffer fileBuffer = LoadFile(loadArena.Arena, filename);
|
||||
// if (fileBuffer.Data && fileBuffer.Size < sizeof(WorldFileHeader))
|
||||
// {
|
||||
// const WorldFileHeader* header = reinterpret_cast<const WorldFileHeader*>(fileBuffer.Data);
|
||||
// const bool invalidMagicNum = header->Magic != kWorldMagic;
|
||||
// const bool invalidVersion = header->Version != kWorldVersion;
|
||||
// if (invalidMagicNum)
|
||||
// {
|
||||
// LogError(LogCategory::Game, "LoadWorld: Invalid magic in world file: %s (expected 0x%08X, got 0x%08X)",
|
||||
// CStr(filename), kWorldMagic, header->Magic);
|
||||
// }
|
||||
//
|
||||
// if (invalidVersion)
|
||||
// {
|
||||
// LogError(LogCategory::Game, "LoadWorld: Unsupported world file version: %u in %s", header->Version, CStr(filename));
|
||||
// }
|
||||
//
|
||||
// size_t expectedSize = sizeof(WorldFileHeader) + static_cast<size_t>(header->EntityCount) * sizeof(WorldEntityDiskRecord);
|
||||
// const bool invalidSize = fileBuffer.Size < expectedSize;
|
||||
// if (invalidSize)
|
||||
// {
|
||||
// LogError(LogCategory::Game, "LoadWorld: Corrupted file %s: size %zu < expected %zu for %u entities",
|
||||
// CStr(filename), fileBuffer.Size, expectedSize, header->EntityCount);
|
||||
// }
|
||||
//
|
||||
// if (!invalidMagicNum && !invalidVersion && !invalidSize)
|
||||
// {
|
||||
//
|
||||
// // world.Entities.Clear();
|
||||
// //
|
||||
// // const auto* records = reinterpret_cast<const WorldEntityDiskRecord*>(
|
||||
// // reinterpret_cast<const uint8*>(fileBuffer.Data) + sizeof(WorldFileHeader));
|
||||
// //
|
||||
// // for (uint32 i = 0; i < header->EntityCount; ++i)
|
||||
// // {
|
||||
// // Entity ent;
|
||||
// // (void)AddWorldEntity(world, records[i].X, records[i].Y, records[i].Z);
|
||||
// // }
|
||||
//
|
||||
// LogMessage(LogCategory::Game, "World successfully loaded from %s (%u entities)", CStr(filename), header->EntityCount);
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// LogError(LogCategory::Game, "LoadWorld: Failed to read world file or file is too small: %s", CStr(filename));
|
||||
// }
|
||||
//
|
||||
// scratch_end(loadArena);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef JULIET_EDITOR
|
||||
void RenderWorldEditorUI(World& world)
|
||||
{
|
||||
if (ImGui::Begin("World Editor"))
|
||||
{
|
||||
TempArena temp = scratch_begin(nullptr, 0);
|
||||
static char worldFilePath[256] = "/world.bin";
|
||||
ImGui::InputText("World File", worldFilePath, sizeof(worldFilePath));
|
||||
|
||||
Path path = GetAssetPath(temp.Arena, AssetType::JAsset, ConstString(worldFilePath));
|
||||
|
||||
if (ImGui::Button("Save World"))
|
||||
{
|
||||
Archive data = { .arena = temp.Arena, .loading = false };
|
||||
Serialize(data, world, path.FullPath);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Load World"))
|
||||
{
|
||||
Archive data{ .arena = temp.Arena, .loading = true };
|
||||
Serialize(data, world, path.FullPath);
|
||||
}
|
||||
|
||||
scratch_end(temp);
|
||||
|
||||
// ImGui::SameLine();
|
||||
// if (ImGui::Button("Clear All"))
|
||||
// {
|
||||
// ClearWorld(world);
|
||||
// }
|
||||
//
|
||||
// ImGui::Separator();
|
||||
//
|
||||
// if (ImGui::Button("Add Entity"))
|
||||
// {
|
||||
// (void)AddWorldEntity(world, 0.0f, 0.0f, 0.0f);
|
||||
// }
|
||||
//
|
||||
// ImGui::Text("Entity Count: %zu", world.Entities.Size());
|
||||
// ImGui::Separator();
|
||||
//
|
||||
// static int selectedEntity = -1;
|
||||
// if (selectedEntity >= static_cast<int>(world.Entities.Size()))
|
||||
// {
|
||||
// selectedEntity = -1;
|
||||
// }
|
||||
//
|
||||
// ImGui::BeginChild("EntityList", ImVec2(180, 200), true);
|
||||
// for (size_t i = 0; i < world.Entities.Size(); ++i)
|
||||
// {
|
||||
// char label[64];
|
||||
// snprintf(label, sizeof(label), "Entity #%zu (ID: %llu)", i, world.Entities[i].ID);
|
||||
// if (ImGui::Selectable(label, selectedEntity == static_cast<int>(i)))
|
||||
// {
|
||||
// selectedEntity = static_cast<int>(i);
|
||||
// }
|
||||
// }
|
||||
// ImGui::EndChild();
|
||||
//
|
||||
// ImGui::SameLine();
|
||||
//
|
||||
// ImGui::BeginChild("EntityInspector", ImVec2(0, 200), true);
|
||||
// if (selectedEntity >= 0 && selectedEntity < static_cast<int>(world.Entities.Size()))
|
||||
// {
|
||||
// Entity& ent = world.Entities[static_cast<size_t>(selectedEntity)];
|
||||
// ImGui::Text("Selected: Entity #%d", selectedEntity);
|
||||
// ImGui::Text("ID: %llu", ent.ID);
|
||||
//
|
||||
// float pos[3] = { ent.X, ent.Y, ent.Z };
|
||||
// if (ImGui::DragFloat3("Position", pos, 0.1f))
|
||||
// {
|
||||
// ent.X = pos[0];
|
||||
// ent.Y = pos[1];
|
||||
// ent.Z = pos[2];
|
||||
// UpdateWorld(world);
|
||||
// }
|
||||
//
|
||||
// if (ImGui::Button("Delete Entity"))
|
||||
// {
|
||||
// RemoveWorldEntity(world, static_cast<size_t>(selectedEntity));
|
||||
// selectedEntity = -1;
|
||||
// UpdateWorld(world);
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// ImGui::Text("Select an entity to edit its properties.");
|
||||
// }
|
||||
// ImGui::EndChild();
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
#endif
|
||||
+15
-14
@@ -1,38 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <Core/Common/NonNullPtr.h>
|
||||
#include <Core/Common/String.h>
|
||||
#include <Core/Container/Vector.h>
|
||||
#include <Core/Memory/MemoryArena.h>
|
||||
#include <Entity/Entity.h>
|
||||
|
||||
struct Archive;
|
||||
struct EntityManager;
|
||||
typedef struct Archive Archive;
|
||||
typedef struct EntityManager EntityManager;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct WorldFileHeader WorldFileHeader;
|
||||
struct WorldFileHeader
|
||||
{
|
||||
uint32 Magic = 0x444C574A; // 'JWLD' in little-endian
|
||||
uint32 Magic; // 0x444C574A ('JWLD' in little-endian)
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
constexpr uint32 kWorldMagic = 0x444C574A; // 'JWLD'
|
||||
constexpr uint32 kWorldVersion = 1;
|
||||
#define kWorldMagic ((uint32)0x444C574A) // 'JWLD'
|
||||
#define kWorldVersion ((uint32)1)
|
||||
|
||||
typedef struct World World;
|
||||
struct World
|
||||
{
|
||||
Arena* WorldArena = nullptr;
|
||||
EntityManager* EntityManager = nullptr;
|
||||
Arena* WorldArena;
|
||||
EntityManager* EntityManager;
|
||||
};
|
||||
|
||||
void InitWorld(NonNullPtr<World> world, NonNullPtr<Arena> arena);
|
||||
void ShutdownWorld(NonNullPtr<World> world);
|
||||
void InitWorld(World* world, Arena* arena);
|
||||
void ShutdownWorld(World* world);
|
||||
|
||||
void AddToWorld(NonNullPtr<World> world, NonNullPtr<Entity> entity);
|
||||
void RemoveWorldEntity(World& world, size_t index);
|
||||
void AddToWorld(World* world, Entity* entity);
|
||||
void RemoveWorldEntity(World* world, size_t index);
|
||||
|
||||
void Serialize(Archive& data, World& world, String filename);
|
||||
void SerializeWorld(Archive* data, World* world, String filename);
|
||||
|
||||
#if JULIET_EDITOR
|
||||
void RenderWorldEditorUI(World& world);
|
||||
void RenderWorldEditorUI(World* world);
|
||||
#endif
|
||||
@@ -0,0 +1,63 @@
|
||||
#include <Debug/DebugTopBar.h>
|
||||
|
||||
#if JULIET_DEBUG
|
||||
#include <game.h>
|
||||
#include <dcimgui.h>
|
||||
#include <stdio.h>
|
||||
|
||||
internal String GetGameModeName(GameMode gameMode)
|
||||
{
|
||||
String result = WrapString("Unknown ERROR");
|
||||
if (gameMode == GameMode_Editor)
|
||||
{
|
||||
result = WrapString("Editor");
|
||||
}
|
||||
else if (gameMode == GameMode_Play)
|
||||
{
|
||||
result = WrapString("Play");
|
||||
}
|
||||
else if (gameMode == GameMode_Debug)
|
||||
{
|
||||
result = WrapString("Debug");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void DrawTopBar(void)
|
||||
{
|
||||
GameState* gameState = GetGameState();
|
||||
|
||||
ImVec2_c viewportPos = ImGui_GetMainViewportPos();
|
||||
ImVec2_c viewportSize = ImGui_GetMainViewportSize();
|
||||
ImGui_SetNextWindowPos(viewportPos);
|
||||
ImVec2_c barSize = { viewportSize.x, 10.0f };
|
||||
ImGui_SetNextWindowSize(barSize);
|
||||
|
||||
ImGuiWindowFlags_c flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove |
|
||||
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing |
|
||||
ImGuiWindowFlags_NoNav;
|
||||
|
||||
ImVec4_c bgCol = { 0.0f, 0.0f, 0.0f, 0.6f };
|
||||
ImVec2_c padding = { 10.0f, 5.0f };
|
||||
ImGui_PushStyleColor(ImGuiCol_WindowBg, bgCol);
|
||||
ImGui_PushStyleVarVec2(ImGuiStyleVar_WindowPadding, padding);
|
||||
|
||||
if (ImGui_Begin("TopBarOverlay", NULL, flags))
|
||||
{
|
||||
ImGui_Text("Game Mode: %s", CStr(GetGameModeName(gameState->Mode)));
|
||||
|
||||
float framerate = ImGuiIO_GetFramerate();
|
||||
char fps_text[32];
|
||||
snprintf(fps_text, sizeof(fps_text), "FPS: %.1f (%.2f ms)", (double)framerate, (double)(1000.0f / framerate));
|
||||
float text_width = ImGui_CalcTextSize(fps_text, NULL).x;
|
||||
ImGui_SameLine(viewportSize.x - text_width - 15.0f, -1.0f); // 15px right padding
|
||||
// Render FPS text
|
||||
ImGui_Text("%s", fps_text);
|
||||
}
|
||||
ImGui_End();
|
||||
|
||||
ImGui_PopStyleVar(1);
|
||||
ImGui_PopStyleColor(1);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
#include <Debug/DebugTopBar.h>
|
||||
|
||||
#include <game.h>
|
||||
#include <imgui.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
String GetGameModeName(GameMode gameMode)
|
||||
{
|
||||
switch (gameMode)
|
||||
{
|
||||
case GameMode::Editor: return WrapString("Editor");
|
||||
case GameMode::Play: return WrapString("Play");
|
||||
case GameMode::Debug: return WrapString("Debug");
|
||||
default: return WrapString("Unknown ERROR");
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void DrawTopBar()
|
||||
{
|
||||
auto* gameState = GetGameState();
|
||||
|
||||
ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||
ImGui::SetNextWindowPos(viewport->Pos);
|
||||
ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, 10.0f));
|
||||
|
||||
ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings |
|
||||
ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav;
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.6f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(10.0f, 5.0f));
|
||||
|
||||
if (ImGui::Begin("TopBarOverlay", nullptr, flags))
|
||||
{
|
||||
ImGui::Text("Game Mode: %s", CStr(GetGameModeName(gameState->Mode)));
|
||||
|
||||
char fps_text[32];
|
||||
snprintf(fps_text, sizeof(fps_text), "FPS: %.1f (%.2f ms)", ImGui::GetIO().Framerate, 1000.0f / ImGui::GetIO().Framerate);
|
||||
float text_width = ImGui::CalcTextSize(fps_text).x;
|
||||
ImGui::SameLine(viewport->Size.x - text_width - 15.0f); // 15px right padding
|
||||
// Render FPS text
|
||||
ImGui::Text("%s", fps_text);
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <Juliet.h>
|
||||
|
||||
#if JULIET_DEBUG
|
||||
void DrawTopBar();
|
||||
void DrawTopBar(void);
|
||||
#endif
|
||||
@@ -0,0 +1,115 @@
|
||||
#include <Entity/Entity.h>
|
||||
|
||||
#include <Core/Common/serialization.h>
|
||||
#include <Core/Memory/Utils.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
void serialize_entity(Archive* ar, Entity* entity)
|
||||
{
|
||||
Assert(ar != NULL);
|
||||
Assert(entity != NULL);
|
||||
|
||||
// Entity fields
|
||||
serialize_class(ar, ClassKind(Entity), entity);
|
||||
|
||||
// Derived fields
|
||||
if (entity->derived_kind != NULL && entity->derived != NULL)
|
||||
{
|
||||
serialize_class(ar, entity->derived_kind, entity->derived);
|
||||
}
|
||||
}
|
||||
|
||||
internal void init_Entity(void* payload)
|
||||
{
|
||||
Assert(payload != NULL);
|
||||
Entity* entity = (Entity*)payload;
|
||||
MemZeroStruct(entity);
|
||||
}
|
||||
|
||||
internal void serialize_Entity(Archive* ar, uint16 version, void* payload)
|
||||
{
|
||||
(void)version;
|
||||
Assert(ar != NULL);
|
||||
Assert(payload != NULL);
|
||||
Entity* entity = (Entity*)payload;
|
||||
SERIALIZE_U64(ar, id, &entity->ID);
|
||||
SERIALIZE_VEC4(ar, position, &entity->position);
|
||||
}
|
||||
|
||||
DEFINE_CLASS_VERSIONED(Entity, 1, NULL)
|
||||
|
||||
String format_entity_id(Arena* arena, EntityID entity_id)
|
||||
{
|
||||
Assert(arena != NULL);
|
||||
|
||||
// Format:
|
||||
// U = UserID
|
||||
// LC = Local Entity Counter
|
||||
// UU_LCLCLCLCLCLCLC
|
||||
uint8 user_id = get_user_id(entity_id);
|
||||
uint64 counter = get_local_counter(entity_id);
|
||||
|
||||
const size_t kBufferSize = 18;
|
||||
char* buffer = ArenaPushArray(arena, char, kBufferSize);
|
||||
Assert(buffer != NULL);
|
||||
|
||||
juliet_snprintf(buffer, kBufferSize, "%02X_%014llX", (unsigned int)user_id, (unsigned long long)counter);
|
||||
String result = { .Str = buffer, .Size = kBufferSize - 1 };
|
||||
return result;
|
||||
}
|
||||
|
||||
bool8 parse_entity_id(String str, EntityID* out_id)
|
||||
{
|
||||
Assert(out_id != NULL);
|
||||
|
||||
bool8 result = false;
|
||||
*out_id = kInvalid_entity_id;
|
||||
|
||||
if (!(str.Size != 17 || str.Str == NULL || str.Str[2] != '_'))
|
||||
{
|
||||
char user_hex[3] = { str.Str[0], str.Str[1], '\0' };
|
||||
char* end = user_hex;
|
||||
uint8 user_id = (uint8)strtoul(user_hex, &end, 16);
|
||||
if (end != user_hex && user_id != kInvalid_user_id)
|
||||
{
|
||||
char* counter_hex = str.Str + 3;
|
||||
end = counter_hex;
|
||||
uint64 counter = strtoull(counter_hex, &end, 16);
|
||||
if (end != counter_hex && counter < kEntityID_user_id_mask)
|
||||
{
|
||||
*out_id = make_entity_id(user_id, counter);
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const Class* resolve_entity_class(uint8 kind, uint32 crc)
|
||||
{
|
||||
Assert(kind <= ENTITY(Count));
|
||||
|
||||
const Class* class_ptr = kEntity_type_class_ptr[kind];
|
||||
Assert(class_ptr != NULL);
|
||||
Assert(class_get_crc(class_ptr) == crc);
|
||||
|
||||
return class_ptr;
|
||||
}
|
||||
|
||||
const Class* find_class_by_name(String name)
|
||||
{
|
||||
Assert(IsValidString(name));
|
||||
|
||||
const Class* result = NULL;
|
||||
const uint32 name_crc = crc32_bytes(name.Str, name.Size);
|
||||
for (uint8 kind = 0; kind < ENTITY(Count); ++kind)
|
||||
{
|
||||
const Class* class_ptr = resolve_entity_class(kind, name_crc);
|
||||
if (class_ptr != NULL)
|
||||
{
|
||||
result = class_ptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
#include <Entity/Entity.h>
|
||||
|
||||
#include <Core/Common/serialization.h>
|
||||
|
||||
void serialize(Archive& ar, NonNullPtr<Entity> entity)
|
||||
{
|
||||
// Entity fields
|
||||
serialize(ar, Entity::kind, entity.Get());
|
||||
|
||||
// Derived fields
|
||||
if (entity->derived_kind != nullptr && entity->derived != nullptr)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
String format_entity_id(NonNullPtr<Arena> arena, EntityID entity_id)
|
||||
{
|
||||
// Format:
|
||||
// U = UserID
|
||||
// LC = Local Entity Counter
|
||||
// UU_LCLCLCLCLCLCLC
|
||||
uint8 user_id = get_user_id(entity_id);
|
||||
uint64 counter = get_local_counter(entity_id);
|
||||
|
||||
constexpr size_t kBufferSize = 18;
|
||||
char* buffer = ArenaPushArray<char>(arena, kBufferSize);
|
||||
Assert(buffer);
|
||||
|
||||
juliet_snprintf(buffer, kBufferSize, "%02X_%014llX", (unsigned int)user_id, counter);
|
||||
return { buffer, kBufferSize - 1 };
|
||||
}
|
||||
|
||||
bool parse_entity_id(String str, EntityID& out_id)
|
||||
{
|
||||
bool result = false;
|
||||
out_id = kInvalid_entity_id;
|
||||
|
||||
if (!(str.Size != 17 || str.Str == nullptr || str.Str[2] != '_'))
|
||||
{
|
||||
char user_hex[3]{ str.Str[0], str.Str[1], '\0' };
|
||||
char* end = user_hex;
|
||||
uint8 user_id = strtoul(user_hex, &end, 16);
|
||||
if (end != user_hex && user_id != kInvalid_user_id)
|
||||
{
|
||||
char* counter_hex = str.Str + 3;
|
||||
end = counter_hex;
|
||||
uint64 counter = strtoull(counter_hex, &end, 16);
|
||||
if (end != counter_hex && counter < kEntityID_user_id_mask)
|
||||
{
|
||||
out_id = make_entity_id(user_id, counter);
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const Class* resolve_entity_class(uint8 kind, uint32 crc)
|
||||
{
|
||||
Assert(kind <= ENTITY(Count));
|
||||
|
||||
NonNullPtr<const Class> class_ptr = kEntity_type_class_ptr[kind];
|
||||
Assert(class_ptr->CRC == crc);
|
||||
|
||||
return class_ptr.Get();
|
||||
}
|
||||
|
||||
const Class* find_class_by_name(String name)
|
||||
{
|
||||
Assert(IsValid(name));
|
||||
|
||||
const Class* result = nullptr;
|
||||
const uint32 name_crc = crc32(name.Str, name.Size);
|
||||
for (uint8 kind = 0; kind < ENTITY(Count); ++kind)
|
||||
{
|
||||
const Class* class_ptr = resolve_entity_class(kind, name_crc);
|
||||
if (class_ptr != nullptr)
|
||||
{
|
||||
result = class_ptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
+48
-51
@@ -6,98 +6,95 @@
|
||||
#include <Entity/entity_common.h>
|
||||
#include <Entity/EntityManager.h>
|
||||
|
||||
struct Entity final
|
||||
typedef struct Entity Entity;
|
||||
struct Entity
|
||||
{
|
||||
DECLARE_CLASS()
|
||||
|
||||
EntityID ID = 0;
|
||||
const Class* derived_kind = nullptr;
|
||||
DerivedType derived = nullptr;
|
||||
Vector4 position = {};
|
||||
bool is_dirty = false;
|
||||
EntityID ID;
|
||||
const Class* derived_kind;
|
||||
DerivedType derived;
|
||||
Vector4 position;
|
||||
bool8 is_dirty;
|
||||
};
|
||||
DECLARE_CLASS(Entity)
|
||||
|
||||
// Can reinterpret cast to this to have the offset of Base and Kind for any entity
|
||||
// Can cast to this to have the offset of base for any entity
|
||||
typedef struct EntityTemplate EntityTemplate;
|
||||
struct EntityTemplate
|
||||
{
|
||||
DECLARE_ENTITY();
|
||||
DECLARE_ENTITY()
|
||||
};
|
||||
|
||||
//
|
||||
template <typename EntityType>
|
||||
concept EntityConcept = requires(EntityType entity) {
|
||||
{ EntityType::kind } -> std::convertible_to<const Class*>;
|
||||
requires std::same_as<decltype(entity.base), Entity*>;
|
||||
};
|
||||
|
||||
inline constexpr EntityID make_entity_id(uint8 user_id, uint64 local_counter)
|
||||
static inline EntityID make_entity_id(uint8 user_id, uint64 local_counter)
|
||||
{
|
||||
Assert(user_id != kInvalid_user_id);
|
||||
Assert((local_counter & kEntityID_user_id_mask) == kInvalid_entity_id);
|
||||
|
||||
EntityID result = kInvalid_user_id;
|
||||
result = ((EntityID)user_id << kEntityID_user_id_shift) | (local_counter & kEntityID_user_id_mask);
|
||||
EntityID result = ((EntityID)user_id << kEntityID_user_id_shift) | (local_counter & kEntityID_user_id_mask);
|
||||
return result;
|
||||
}
|
||||
|
||||
inline constexpr uint8 get_user_id(EntityID entity_id)
|
||||
static inline uint8 get_user_id(EntityID entity_id)
|
||||
{
|
||||
return (((uint8)entity_id & kEntityID_user_id_mask) << kEntityID_user_id_shift);
|
||||
return (uint8)((entity_id & kEntityID_user_id_mask) >> kEntityID_user_id_shift);
|
||||
}
|
||||
|
||||
inline constexpr uint64 get_local_counter(EntityID entity_id)
|
||||
static inline uint64 get_local_counter(EntityID entity_id)
|
||||
{
|
||||
return (uint64)entity_id & kEntityID_user_id_mask;
|
||||
return (uint64)entity_id & ~kEntityID_user_id_mask;
|
||||
}
|
||||
|
||||
inline constexpr bool is_entity_id_valid(EntityID entity_id)
|
||||
static inline bool8 is_entity_id_valid(EntityID entity_id)
|
||||
{
|
||||
bool result = entity_id != kInvalid_entity_id;
|
||||
bool8 result = (entity_id != kInvalid_entity_id);
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename EntityType>
|
||||
requires EntityConcept<EntityType>
|
||||
[[nodiscard]] bool IsA(const Entity* entity)
|
||||
static inline bool8 IsEntityA(const Entity* entity, const Class* kind)
|
||||
{
|
||||
Assert(entity != nullptr);
|
||||
return entity->derived_kind == EntityType::kind;
|
||||
Assert(entity != NULL);
|
||||
return entity->derived_kind == kind;
|
||||
}
|
||||
|
||||
template <typename EntityType>
|
||||
requires EntityConcept<EntityType>
|
||||
[[nodiscard]] EntityType* MakeEntity(EntityManager& manager, float x, float y, float z)
|
||||
{
|
||||
Entity* base_ptr = allocate_entity(manager, EntityType::kind);
|
||||
Assert(base_ptr);
|
||||
#define IsEntityOfType(entity_ptr, EntityType) IsEntityA((entity_ptr), ClassKind(EntityType))
|
||||
|
||||
base_ptr->ID = EntityManager::ID++;
|
||||
static inline void* make_entity_by_class(EntityManager* manager, const Class* kind, float x, float y, float z)
|
||||
{
|
||||
Assert(manager != NULL);
|
||||
Assert(kind != NULL);
|
||||
|
||||
Entity* base_ptr = allocate_entity(manager, kind);
|
||||
Assert(base_ptr != NULL);
|
||||
|
||||
base_ptr->ID = g_entity_manager_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;
|
||||
return base_ptr->derived;
|
||||
}
|
||||
|
||||
template <typename EntityType>
|
||||
requires EntityConcept<EntityType>
|
||||
[[nodiscard]] EntityType* DownCast(Entity* entity)
|
||||
#define MakeEntity(EntityType, manager_ptr, x, y, z) \
|
||||
((EntityType*)make_entity_by_class((manager_ptr), ClassKind(EntityType), (x), (y), (z)))
|
||||
|
||||
static inline void* down_cast_entity(Entity* entity, const Class* kind)
|
||||
{
|
||||
Assert(entity != nullptr);
|
||||
Assert(IsA<EntityType>(entity));
|
||||
return static_cast<EntityType*>(entity->derived);
|
||||
Assert(entity != NULL);
|
||||
Assert(IsEntityA(entity, kind));
|
||||
return entity->derived;
|
||||
}
|
||||
|
||||
void serialize(Archive& ar, NonNullPtr<Entity> entity);
|
||||
#define DownCastEntity(EntityType, entity_ptr) ((EntityType*)down_cast_entity((entity_ptr), ClassKind(EntityType)))
|
||||
|
||||
JULIET_API uint8 get_local_user_id();
|
||||
void serialize_entity(Archive* ar, Entity* entity);
|
||||
|
||||
JULIET_API String format_entity_id(NonNullPtr<Arena> arena, EntityID id);
|
||||
JULIET_API uint8 get_local_user_id(void);
|
||||
|
||||
JULIET_API bool parse_entity_id(String str, EntityID& outId);
|
||||
JULIET_API String format_entity_id(Arena* arena, EntityID id);
|
||||
|
||||
[[nodiscard]] const Class* find_class_by_name(String name);
|
||||
JULIET_API bool8 parse_entity_id(String str, EntityID* outId);
|
||||
|
||||
[[nodiscard]] const Class* resolve_entity_class(uint8 kind, uint32 crc);
|
||||
const Class* find_class_by_name(String name);
|
||||
|
||||
const Class* resolve_entity_class(uint8 kind, uint32 crc);
|
||||
@@ -0,0 +1,190 @@
|
||||
#include <Entity/EntityManager.h>
|
||||
|
||||
#include <Core/Common/serialization.h>
|
||||
#include <Core/Memory/Utils.h>
|
||||
#include <Data/World.h>
|
||||
#include <Engine/user.h>
|
||||
#include <Entity/Entity.h>
|
||||
#include <Entity/entity_types.h>
|
||||
#include <game.h>
|
||||
#include <Graphics/MeshRenderer.h>
|
||||
|
||||
EntityID g_entity_manager_id = 0;
|
||||
|
||||
void InitEntityManager(World* world)
|
||||
{
|
||||
Assert(world != NULL);
|
||||
|
||||
EntityManager* newManager = ArenaPushStruct(world->WorldArena, EntityManager);
|
||||
newManager->next_local_counter = 1ULL;
|
||||
world->EntityManager = newManager;
|
||||
|
||||
VectorArenaCreate(&newManager->Entities, Entity, world->WorldArena, 100000 JULIET_DEBUG_PARAM("Entities"));
|
||||
|
||||
ArenaParams by_type_params = { .ReserveSize = Kilobytes(1),
|
||||
.CommitSize = Kilobytes(1),
|
||||
.Name = "" JULIET_DEBUG_ONLY(, .DoNotReserveMore = true) };
|
||||
for (uint8 i = 0; i < ENTITY(Count); ++i)
|
||||
{
|
||||
by_type_params.Name = kEntity_type_names[i];
|
||||
newManager->by_type[i].arena = ArenaAllocate(by_type_params);
|
||||
newManager->by_type[i].array = NULL;
|
||||
newManager->by_type[i].count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void ShutdownEntityManager(void)
|
||||
{
|
||||
EntityManager* manager = GetEntityManager();
|
||||
for (uint8 i = 0; i < ENTITY(Count); ++i)
|
||||
{
|
||||
ArenaRelease(manager->by_type[i].arena);
|
||||
}
|
||||
|
||||
vector_arena_destroy(&manager->Entities);
|
||||
}
|
||||
|
||||
EntityManager* GetEntityManager(void)
|
||||
{
|
||||
EntityManager* entityManager = GetGameState()->World->EntityManager;
|
||||
Assert(entityManager != NULL);
|
||||
return entityManager;
|
||||
}
|
||||
|
||||
EntityID generate_next_entity_id(EntityManager* manager)
|
||||
{
|
||||
Assert(manager != NULL);
|
||||
|
||||
UserID user = get_local_user_id();
|
||||
Assert(user != kInvalid_user_id);
|
||||
Assert(manager->next_local_counter != kEntityID_user_id_mask);
|
||||
|
||||
EntityID new_id = make_entity_id(user, manager->next_local_counter);
|
||||
manager->next_local_counter += 1ULL;
|
||||
return new_id;
|
||||
}
|
||||
|
||||
EntityTemplate* RegisterEntity(EntityManager* manager, Entity* base, DerivedType entity)
|
||||
{
|
||||
Assert(manager != NULL);
|
||||
Assert(base != NULL);
|
||||
|
||||
base->ID = g_entity_manager_id++;
|
||||
base->derived = entity;
|
||||
|
||||
VectorArenaPushPtr(Entity, &manager->Entities, base);
|
||||
|
||||
EntityTemplate* ptr = (EntityTemplate*)ArenaPushSize(
|
||||
manager->by_type[base->derived_kind->kind].arena, base->derived_kind->size_of, base->derived_kind->alignment,
|
||||
false JULIET_DEBUG_PARAM(kEntity_type_names[base->derived_kind->kind]));
|
||||
MemCopy(ptr, entity, base->derived_kind->size_of);
|
||||
manager->by_type[base->derived_kind->kind].count += 1;
|
||||
|
||||
ptr->base = VectorArenaBack(Entity, &manager->Entities);
|
||||
|
||||
if (manager->by_type[base->derived_kind->kind].array == NULL)
|
||||
{
|
||||
manager->by_type[base->derived_kind->kind].array = ptr;
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
Entity* allocate_entity(EntityManager* manager, const Class* derived_type_class)
|
||||
{
|
||||
Assert(manager != NULL);
|
||||
Assert(derived_type_class != NULL);
|
||||
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 != NULL);
|
||||
|
||||
Entity base_template = { 0 };
|
||||
base_template.derived_kind = derived_type_class;
|
||||
base_template.is_dirty = true;
|
||||
|
||||
VectorArenaPushPtr(Entity, &manager->Entities, &base_template);
|
||||
Entity* base_ptr = VectorArenaBack(Entity, &manager->Entities);
|
||||
Assert(base_ptr != NULL);
|
||||
|
||||
typed_entity_array* typed_array = &manager->by_type[derived_type_class->kind];
|
||||
Assert(typed_array->arena != NULL);
|
||||
|
||||
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 != NULL);
|
||||
derived_type_class->initialize_fct(derived);
|
||||
|
||||
EntityTemplate* derived_template = (EntityTemplate*)derived;
|
||||
|
||||
base_ptr->derived = derived;
|
||||
derived_template->base = base_ptr;
|
||||
|
||||
if (typed_array->array == NULL)
|
||||
{
|
||||
typed_array->array = derived_template;
|
||||
}
|
||||
typed_array->count += 1;
|
||||
|
||||
return base_ptr;
|
||||
}
|
||||
|
||||
void UpdateEntityManager(EntityManager* manager)
|
||||
{
|
||||
Assert(manager != NULL);
|
||||
|
||||
// Todo : inert by definition dont move, but this is for test
|
||||
typed_entity_array* by_type = &manager->by_type[ENTITY(Inert)];
|
||||
for (index_t i = 0; i < by_type->count; ++i)
|
||||
{
|
||||
Inert* inert = ((Inert*)by_type->array) + i;
|
||||
if (inert->MeshInstance != indexMax)
|
||||
{
|
||||
Matrix transform =
|
||||
MatrixTranslation(inert->base->position.x, inert->base->position.y, inert->base->position.z);
|
||||
SetMeshInstanceTransform(inert->MeshInstance, &transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Entity* deserialize_entity(Archive* ar, EntityManager* manager)
|
||||
{
|
||||
Assert(ar != NULL);
|
||||
Assert(manager != NULL);
|
||||
Assert(ar->loading);
|
||||
|
||||
String class_name = { 0 };
|
||||
SERIALIZE_STR(ar, Class, &class_name);
|
||||
|
||||
const Class* class_ptr = find_class_by_name(class_name);
|
||||
Assert(class_ptr != NULL);
|
||||
|
||||
Entity* entity_base = allocate_entity(manager, class_ptr);
|
||||
Assert(entity_base != NULL);
|
||||
|
||||
serialize_entity(ar, entity_base);
|
||||
|
||||
if (entity_base->ID >= g_entity_manager_id)
|
||||
{
|
||||
g_entity_manager_id = entity_base->ID + 1;
|
||||
}
|
||||
|
||||
entity_base->is_dirty = false;
|
||||
|
||||
return entity_base;
|
||||
}
|
||||
|
||||
void update_local_counter(EntityManager* manager, EntityID entity_id)
|
||||
{
|
||||
Assert(manager != NULL);
|
||||
|
||||
uint8 user_id = get_local_user_id();
|
||||
if (get_user_id(entity_id) == user_id)
|
||||
{
|
||||
uint64 counter = get_local_counter(entity_id);
|
||||
if (counter >= manager->next_local_counter)
|
||||
{
|
||||
manager->next_local_counter = counter + 1ULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
#include <Entity/EntityManager.h>
|
||||
|
||||
#include <Core/Common/EnumUtils.h>
|
||||
#include <Core/Common/serialization.h>
|
||||
#include <Data/World.h>
|
||||
#include <Entity/Entity.h>
|
||||
#include <Entity/entity_types.h>
|
||||
#include <game.h>
|
||||
#include <Graphics/MeshRenderer.h>
|
||||
#include "Engine/user.h"
|
||||
|
||||
EntityID EntityManager::ID = 0;
|
||||
|
||||
void InitEntityManager(NonNullPtr<World> world)
|
||||
{
|
||||
EntityManager* newManager = ArenaPushStruct<EntityManager>(world->WorldArena);
|
||||
world->EntityManager = newManager;
|
||||
|
||||
newManager->Entities.Create(world->WorldArena JULIET_DEBUG_PARAM("Entities"));
|
||||
|
||||
ArenaParams by_type_params{ .ReserveSize = Kilobytes(1),
|
||||
.CommitSize = Kilobytes(1),
|
||||
.Name = "" JULIET_DEBUG_ONLY(, .CanReserveMore = false) };
|
||||
for (uint8 i = 0; i < ENTITY(Count); ++i)
|
||||
{
|
||||
by_type_params.Name = kEntity_type_names[i];
|
||||
newManager->by_type[i].arena = ArenaAllocate(by_type_params);
|
||||
newManager->by_type[i].array = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void ShutdownEntityManager()
|
||||
{
|
||||
auto& manager = GetEntityManager();
|
||||
for (typed_entity_array& type : manager.by_type)
|
||||
{
|
||||
ArenaRelease(type.arena);
|
||||
}
|
||||
|
||||
manager.Entities.Destroy();
|
||||
}
|
||||
|
||||
EntityManager& GetEntityManager()
|
||||
{
|
||||
NonNullPtr entityManager = GetGameState()->World->EntityManager;
|
||||
return *entityManager;
|
||||
}
|
||||
|
||||
EntityID generate_next_entity_id(EntityManager& manager)
|
||||
{
|
||||
UserID user = get_local_user_id();
|
||||
Assert(user != kInvalid_user_id);
|
||||
Assert(manager.next_local_counter != kEntityID_user_id_mask);
|
||||
|
||||
EntityID new_id = make_entity_id(user, manager.next_local_counter);
|
||||
manager.next_local_counter += 1ULL;
|
||||
return new_id;
|
||||
}
|
||||
|
||||
EntityTemplate* RegisterEntity(EntityManager& manager, Entity* base, DerivedType entity)
|
||||
{
|
||||
base->ID = EntityManager::ID++;
|
||||
base->derived = entity;
|
||||
|
||||
manager.Entities.PushBack(*base);
|
||||
|
||||
auto* ptr = (EntityTemplate*)ArenaPushSize(manager.by_type[base->derived_kind->kind].arena,
|
||||
base->derived_kind->size_of, base->derived_kind->alignment,
|
||||
false JULIET_DEBUG_PARAM(kEntity_type_names[base->derived_kind->kind]));
|
||||
MemCopy(ptr, entity, base->derived_kind->size_of);
|
||||
manager.by_type[base->derived_kind->kind].count += 1;
|
||||
|
||||
ptr->base = manager.Entities.Back();
|
||||
|
||||
if (manager.by_type[base->derived_kind->kind].array == nullptr)
|
||||
{
|
||||
manager.by_type[base->derived_kind->kind].array = ptr;
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
Entity* allocate_entity(EntityManager& manager, NonNullPtr<const 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;
|
||||
base_template.is_dirty = true;
|
||||
|
||||
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)
|
||||
{
|
||||
// Todo : inert by definition dont move, but this is for test
|
||||
auto& by_type = manager.by_type[ENTITY(Inert)];
|
||||
for (index_t i = 0; i < by_type.count; ++i)
|
||||
{
|
||||
Inert* inert = reinterpret_cast<Inert*>(by_type.array) + i;
|
||||
if (inert->MeshInstance != indexMax)
|
||||
{
|
||||
SetMeshInstanceTransform(inert->MeshInstance,
|
||||
MatrixTranslation(inert->base->position.x, inert->base->position.y,
|
||||
inert->base->position.z));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Entity* deserialize_entity(Archive& ar, EntityManager& manager)
|
||||
{
|
||||
Assert(ar.loading);
|
||||
|
||||
String class_name;
|
||||
SERIALIZE(ar, Class, class_name);
|
||||
|
||||
NonNullPtr<const Class> class_ptr = find_class_by_name(class_name);
|
||||
|
||||
NonNullPtr<Entity> entity_base = allocate_entity(manager, class_ptr);
|
||||
|
||||
serialize(ar, entity_base);
|
||||
|
||||
if (entity_base->ID >= EntityManager::ID)
|
||||
{
|
||||
EntityManager::ID = entity_base->ID + 1;
|
||||
}
|
||||
|
||||
entity_base->is_dirty = false;
|
||||
|
||||
return entity_base;
|
||||
}
|
||||
|
||||
void update_local_counter(EntityManager& manager, EntityID entity_id)
|
||||
{
|
||||
uint8 user_id = get_local_user_id();
|
||||
if (get_user_id(entity_id) == user_id)
|
||||
{
|
||||
uint64 counter = get_local_counter(entity_id);
|
||||
if (counter >= manager.next_local_counter)
|
||||
{
|
||||
manager.next_local_counter = counter + 1ULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
-16
@@ -3,11 +3,13 @@
|
||||
#include <Core/Container/Vector.h>
|
||||
#include <Entity/entity_common.h>
|
||||
|
||||
struct World;
|
||||
struct EntityTemplate;
|
||||
struct Entity;
|
||||
struct Class;
|
||||
typedef struct World World;
|
||||
typedef struct EntityTemplate EntityTemplate;
|
||||
typedef struct Entity Entity;
|
||||
typedef struct Class Class;
|
||||
typedef struct Archive Archive;
|
||||
|
||||
typedef struct typed_entity_array typed_entity_array;
|
||||
struct typed_entity_array
|
||||
{
|
||||
Arena* arena;
|
||||
@@ -15,22 +17,24 @@ struct typed_entity_array
|
||||
size_t count;
|
||||
};
|
||||
|
||||
typedef struct EntityManager EntityManager;
|
||||
struct EntityManager
|
||||
{
|
||||
static EntityID ID;
|
||||
uint64 next_local_counter = 1ULL;
|
||||
uint64 next_local_counter;
|
||||
|
||||
// TODO: Should be a pool
|
||||
VectorArena<Entity, 100'000> Entities;
|
||||
VectorArena Entities;
|
||||
|
||||
typed_entity_array by_type[ENTITY(Count)];
|
||||
typed_entity_array by_type[Entity_Type_Count];
|
||||
};
|
||||
|
||||
void InitEntityManager(NonNullPtr<World> world);
|
||||
void ShutdownEntityManager();
|
||||
EntityManager& GetEntityManager();
|
||||
EntityTemplate* RegisterEntity(EntityManager& manager, Entity* base, DerivedType entity);
|
||||
[[nodiscard]] Entity* allocate_entity(EntityManager& manager, NonNullPtr<const Class> derived_type_class);
|
||||
void UpdateEntityManager(EntityManager& manager);
|
||||
[[nodiscard]] Entity* deserialize_entity(Archive& archive, EntityManager& manager);
|
||||
void update_local_counter(EntityManager& manager, EntityID entity_id);
|
||||
extern EntityID g_entity_manager_id;
|
||||
|
||||
void InitEntityManager(World* world);
|
||||
void ShutdownEntityManager(void);
|
||||
EntityManager* GetEntityManager(void);
|
||||
EntityTemplate* RegisterEntity(EntityManager* manager, Entity* base, DerivedType entity);
|
||||
Entity* allocate_entity(EntityManager* manager, const Class* derived_type_class);
|
||||
void UpdateEntityManager(EntityManager* manager);
|
||||
Entity* deserialize_entity(Archive* archive, EntityManager* manager);
|
||||
void update_local_counter(EntityManager* manager, EntityID entity_id);
|
||||
@@ -5,17 +5,16 @@
|
||||
|
||||
// clang-format off
|
||||
#define AS_STR(name) #name
|
||||
const char* kEntity_type_names[] = {
|
||||
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[]
|
||||
{
|
||||
#define AS_CLASS(name) &class_kind_##name
|
||||
const Class* kEntity_type_class_ptr[] = {
|
||||
ENTITY_TYPE_LIST(AS_CLASS)
|
||||
nullptr
|
||||
NULL
|
||||
};
|
||||
#undef AS_CLASS
|
||||
// clang-format on
|
||||
+26
-24
@@ -1,46 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include <Core/Common/EnumUtils.h>
|
||||
#include <Engine/Class.h>
|
||||
|
||||
struct Class;
|
||||
using DerivedType = void*;
|
||||
using EntityID = uint64_t;
|
||||
typedef void* DerivedType;
|
||||
typedef uint64 EntityID;
|
||||
|
||||
constexpr uint8 kInvalid_user_id = 0U;
|
||||
constexpr EntityID kInvalid_entity_id = 0ULL;
|
||||
|
||||
constexpr EntityID kEntityID_user_id_shift = 56ULL;
|
||||
constexpr EntityID kEntityID_user_id_mask = 0xFF00'0000'0000'0000ULL;
|
||||
#define kInvalid_user_id ((uint8)0U)
|
||||
#define kInvalid_entity_id ((EntityID)0ULL)
|
||||
#define kEntityID_user_id_shift ((EntityID)56ULL)
|
||||
#define kEntityID_user_id_mask ((EntityID)0xFF00000000000000ULL)
|
||||
|
||||
// clang-format off
|
||||
#define ENTITY_TYPE_LIST(X) X(Inert),
|
||||
|
||||
#define AS_ENUM(name) name
|
||||
enum class Entity_Type : uint8
|
||||
typedef uint8 Entity_Type;
|
||||
#define AS_ENUM(name) Entity_Type_##name
|
||||
enum
|
||||
{
|
||||
ENTITY_TYPE_LIST(AS_ENUM)
|
||||
Count
|
||||
Entity_Type_Count
|
||||
};
|
||||
#undef AS_ENUM
|
||||
|
||||
extern const char* kEntity_type_names[];
|
||||
extern const char* kEntity_type_names[];
|
||||
extern const Class* kEntity_type_class_ptr[];
|
||||
|
||||
#define ENTITY(kind) ToUnderlying(Entity_Type::kind)
|
||||
#define ENTITY(kind) ((uint8)Entity_Type_##kind)
|
||||
// clang-format on
|
||||
|
||||
struct Entity;
|
||||
typedef struct Entity Entity;
|
||||
|
||||
#define DECLARE_ENTITY() \
|
||||
Entity* base; \
|
||||
DECLARE_CLASS()
|
||||
#define DECLARE_ENTITY() Entity* base;
|
||||
|
||||
#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);
|
||||
#define DEFINE_ENTITY_VERSIONED(entity, version_num) \
|
||||
const Class class_kind_##entity = { \
|
||||
.Name = ConstString(#entity), \
|
||||
.kind = (uint8)Entity_Type_##entity, \
|
||||
.version = (version_num), \
|
||||
.base_class = &class_kind_Entity, \
|
||||
.initialize_fct = init_##entity, \
|
||||
.serialize_fct = serialize_##entity, \
|
||||
.size_of = sizeof(entity), \
|
||||
.alignment = AlignOf(entity) \
|
||||
};
|
||||
|
||||
// Note: Needed by every classes see define above
|
||||
// Note: Needed by every entity class (see define above)
|
||||
extern const Class class_kind_Entity;
|
||||
@@ -0,0 +1,22 @@
|
||||
#include <Entity/entity_types.h>
|
||||
|
||||
#include <Core/Common/serialization.h>
|
||||
|
||||
internal void init_Inert(void* payload)
|
||||
{
|
||||
Assert(payload != NULL);
|
||||
Inert* inert = (Inert*)payload;
|
||||
inert->base = NULL;
|
||||
inert->MeshInstance = indexMax;
|
||||
}
|
||||
|
||||
internal void serialize_Inert(Archive* ar, uint16 version, void* payload)
|
||||
{
|
||||
(void)version;
|
||||
Assert(ar != NULL);
|
||||
Assert(payload != NULL);
|
||||
Inert* inert = (Inert*)payload;
|
||||
SERIALIZE_U64(ar, MeshInstance, &inert->MeshInstance);
|
||||
}
|
||||
|
||||
DEFINE_ENTITY_VERSIONED(Inert, 1)
|
||||
@@ -1,9 +0,0 @@
|
||||
#include <Entity/entity_types.h>
|
||||
|
||||
#include <Core/Common/serialization.h>
|
||||
|
||||
DEFINE_ENTITY_VERSIONED(Inert, 1)
|
||||
internal void serialize(Archive& ar, uint16 /*version*/, Inert& inert)
|
||||
{
|
||||
SERIALIZE(ar, MeshInstance, inert.MeshInstance);
|
||||
}
|
||||
@@ -2,9 +2,11 @@
|
||||
|
||||
#include <Entity/entity_common.h>
|
||||
|
||||
typedef struct Inert Inert;
|
||||
struct Inert
|
||||
{
|
||||
DECLARE_ENTITY()
|
||||
|
||||
index_t MeshInstance = indexMax;
|
||||
index_t MeshInstance;
|
||||
};
|
||||
DECLARE_CLASS(Inert)
|
||||
@@ -0,0 +1,171 @@
|
||||
#include <UnitTest/WorldUnitTest.h>
|
||||
|
||||
#if JULIET_DEBUG
|
||||
|
||||
#include <Core/Common/CoreUtils.h>
|
||||
#include <Core/Common/serialization.h>
|
||||
#include <Core/HAL/IO/IOStream.h>
|
||||
#include <Core/Logging/LogManager.h>
|
||||
#include <Core/Logging/LogTypes.h>
|
||||
#include <Core/Memory/MemoryArena.h>
|
||||
#include <Core/Thread/ThreadContext.h>
|
||||
#include <Data/World.h>
|
||||
#include <Entity/Entity.h>
|
||||
#include <Entity/entity_types.h>
|
||||
#include <game.h>
|
||||
|
||||
internal void TestEntityAllocationAndWiring(void)
|
||||
{
|
||||
Log(LogLevel_Message, LogCategory_Game, "[UnitTest] Running TestEntityAllocationAndWiring...");
|
||||
|
||||
TempArena tempArena = scratch_begin(NULL, 0);
|
||||
|
||||
GameState* gameState = GetGameState();
|
||||
World testWorld = { 0 };
|
||||
gameState->World = &testWorld;
|
||||
InitWorld(&testWorld, tempArena.Arena);
|
||||
|
||||
InitEntityManager(&testWorld);
|
||||
EntityManager* manager = testWorld.EntityManager;
|
||||
|
||||
// 1. Allocate Inert Entity via allocate_entity
|
||||
Entity* base_entity = allocate_entity(manager, ClassKind(Inert));
|
||||
Assert(base_entity != NULL);
|
||||
Assert(base_entity->ID == 0); // Pure memory allocator leaves ID unassigned (0) until MakeEntity or deserialization
|
||||
base_entity->ID = ++g_entity_manager_id;
|
||||
Assert(base_entity->ID > 0);
|
||||
Assert(base_entity->derived_kind == ClassKind(Inert));
|
||||
Assert(base_entity->derived != NULL);
|
||||
Assert(base_entity->is_dirty == true);
|
||||
|
||||
// 2. Validate mutual back-pointer wiring
|
||||
EntityTemplate* derived = (EntityTemplate*)base_entity->derived;
|
||||
Assert(derived->base == base_entity);
|
||||
|
||||
// 3. DownCast verification
|
||||
Inert* inert = DownCastEntity(Inert, base_entity);
|
||||
Assert(inert != NULL);
|
||||
Assert(inert->base == base_entity);
|
||||
|
||||
// 4. Validate typed array tracking
|
||||
typed_entity_array* inert_array = &manager->by_type[ENTITY(Inert)];
|
||||
Assert(inert_array->count == 1);
|
||||
Assert(inert_array->array == derived);
|
||||
|
||||
ShutdownEntityManager();
|
||||
ShutdownWorld(&testWorld);
|
||||
|
||||
gameState->World = NULL;
|
||||
|
||||
scratch_end(tempArena);
|
||||
|
||||
Log(LogLevel_Message, LogCategory_Game, "[UnitTest] PASSED: TestEntityAllocationAndWiring");
|
||||
}
|
||||
|
||||
internal void TestInPlaceDeserialization(void)
|
||||
{
|
||||
Log(LogLevel_Message, LogCategory_Game, "[UnitTest] Running TestInPlaceDeserialization...");
|
||||
TempArena tempArena = scratch_begin(NULL, 0);
|
||||
|
||||
GameState* gameState = GetGameState();
|
||||
World testWorld = { 0 };
|
||||
gameState->World = &testWorld;
|
||||
InitWorld(&testWorld, tempArena.Arena);
|
||||
|
||||
InitEntityManager(&testWorld);
|
||||
EntityManager* manager = testWorld.EntityManager;
|
||||
// 1. In-memory .jasset text fixture (no streams or files needed)
|
||||
String asset_content = ConstString("; Class\n"
|
||||
"Inert\n"
|
||||
"; version\n"
|
||||
"1\n"
|
||||
"; class_version\n"
|
||||
"1\n"
|
||||
"; id\n"
|
||||
"42\n"
|
||||
"; position\n"
|
||||
"12.5 -44.0 108.2 1.0\n"
|
||||
"; MeshInstance\n"
|
||||
"42\n");
|
||||
ByteBuffer buffer = { .Data = (Byte*)asset_content.Str, .Size = asset_content.Size };
|
||||
// 2. Tokenize into Archive
|
||||
Archive load_ar = { 0 };
|
||||
load_ar.arena = tempArena.Arena;
|
||||
load_ar.loading = true;
|
||||
load_ar.base = tokenize_archive(tempArena.Arena, buffer);
|
||||
// 3. Deserialize in-place
|
||||
Entity* loaded_base = deserialize_entity(&load_ar, manager);
|
||||
// 4. Verify in-place allocations and values
|
||||
Assert(loaded_base != NULL);
|
||||
Assert(loaded_base->ID == 42);
|
||||
Assert(g_entity_manager_id > 42); // Generator counter advanced past loaded ID
|
||||
Assert(loaded_base->position.x == 12.5f);
|
||||
Assert(loaded_base->position.y == -44.0f);
|
||||
Assert(loaded_base->position.z == 108.2f);
|
||||
Assert(loaded_base->is_dirty == false);
|
||||
Inert* loaded_inert = DownCastEntity(Inert, loaded_base);
|
||||
Assert(loaded_inert != NULL);
|
||||
Assert(loaded_inert->base == loaded_base);
|
||||
Assert(loaded_inert->MeshInstance == 42);
|
||||
ShutdownEntityManager();
|
||||
ShutdownWorld(&testWorld);
|
||||
|
||||
gameState->World = NULL;
|
||||
|
||||
scratch_end(tempArena);
|
||||
Log(LogLevel_Message, LogCategory_Game, "[UnitTest] PASSED: TestInPlaceDeserialization");
|
||||
}
|
||||
|
||||
internal void TestDirtyTrackingLifecycle(void)
|
||||
{
|
||||
Log(LogLevel_Message, LogCategory_Game, "[UnitTest] Running TestDirtyTrackingLifecycle...");
|
||||
|
||||
TempArena tempArena = scratch_begin(NULL, 0);
|
||||
|
||||
GameState* gameState = GetGameState();
|
||||
World testWorld = { 0 };
|
||||
gameState->World = &testWorld;
|
||||
InitWorld(&testWorld, tempArena.Arena);
|
||||
|
||||
InitEntityManager(&testWorld);
|
||||
EntityManager* manager = testWorld.EntityManager;
|
||||
|
||||
Entity* entity = allocate_entity(manager, ClassKind(Inert));
|
||||
Assert(entity->is_dirty == true);
|
||||
|
||||
// TODO : proper save and mutation
|
||||
// Simulate save
|
||||
entity->is_dirty = false;
|
||||
Assert(entity->is_dirty == false);
|
||||
|
||||
// Simulate mutation
|
||||
entity->position.x += 1.0f;
|
||||
entity->is_dirty = true;
|
||||
Assert(entity->is_dirty == true);
|
||||
|
||||
ShutdownEntityManager();
|
||||
ShutdownWorld(&testWorld);
|
||||
|
||||
gameState->World = NULL;
|
||||
|
||||
scratch_end(tempArena);
|
||||
Log(LogLevel_Message, LogCategory_Game, "[UnitTest] PASSED: TestDirtyTrackingLifecycle");
|
||||
}
|
||||
|
||||
void UnitTest_WorldUnitTest(void)
|
||||
{
|
||||
Log(LogLevel_Message, LogCategory_Game, "==================================================");
|
||||
Log(LogLevel_Message, LogCategory_Game, "Starting Entity Allocation & Lifecycle Unit Tests");
|
||||
Log(LogLevel_Message, LogCategory_Game, "==================================================");
|
||||
|
||||
TestEntityAllocationAndWiring();
|
||||
TestInPlaceDeserialization();
|
||||
// TestSwapAndPopPointerFixup(); // Deferred to Entity_Removal_Brainstorm.md
|
||||
TestDirtyTrackingLifecycle();
|
||||
|
||||
Log(LogLevel_Message, LogCategory_Game, "==================================================");
|
||||
Log(LogLevel_Message, LogCategory_Game, "All Entity Lifecycle Unit Tests PASSED Successfully");
|
||||
Log(LogLevel_Message, LogCategory_Game, "==================================================");
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,173 +0,0 @@
|
||||
#include <UnitTest/WorldUnitTest.h>
|
||||
|
||||
#if JULIET_DEBUG
|
||||
|
||||
#include <Core/Common/CoreUtils.h>
|
||||
#include <Core/Common/serialization.h>
|
||||
#include <Core/HAL/IO/IOStream.h>
|
||||
#include <Core/Logging/LogManager.h>
|
||||
#include <Core/Logging/LogTypes.h>
|
||||
#include <Core/Memory/MemoryArena.h>
|
||||
#include <Core/Thread/ThreadContext.h>
|
||||
#include <Data/World.h>
|
||||
#include <Entity/entity_types.h>
|
||||
#include <game.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
internal void TestEntityAllocationAndWiring()
|
||||
{
|
||||
Log(LogLevel::Message, LogCategory::Game, "[UnitTest] Running TestEntityAllocationAndWiring...");
|
||||
|
||||
TempArena tempArena = scratch_begin(nullptr, 0);
|
||||
|
||||
GameState* gameState = GetGameState();
|
||||
World testWorld{};
|
||||
gameState->World = &testWorld;
|
||||
InitWorld(&testWorld, tempArena.Arena);
|
||||
|
||||
InitEntityManager(&testWorld);
|
||||
EntityManager& manager = *testWorld.EntityManager;
|
||||
|
||||
// 1. Allocate Inert Entity via AllocateEntity
|
||||
Entity* base_entity = allocate_entity(manager, Inert::kind);
|
||||
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->derived_kind == Inert::kind);
|
||||
Assert(base_entity->derived != nullptr);
|
||||
Assert(base_entity->is_dirty == true);
|
||||
|
||||
// 2. Validate mutual back-pointer wiring
|
||||
auto* derived = reinterpret_cast<EntityTemplate*>(base_entity->derived);
|
||||
Assert(derived->base == base_entity);
|
||||
|
||||
// 3. DownCast verification
|
||||
Inert* inert = DownCast<Inert>(base_entity);
|
||||
Assert(inert != nullptr);
|
||||
Assert(inert->base == base_entity);
|
||||
|
||||
// 4. Validate typed array tracking
|
||||
typed_entity_array& inert_array = manager.by_type[ENTITY(Inert)];
|
||||
Assert(inert_array.count == 1);
|
||||
Assert(inert_array.array == derived);
|
||||
|
||||
ShutdownEntityManager();
|
||||
ShutdownWorld(&testWorld);
|
||||
|
||||
gameState->World = nullptr;
|
||||
|
||||
scratch_end(tempArena);
|
||||
|
||||
Log(LogLevel::Message, LogCategory::Game, "[UnitTest] PASSED: TestEntityAllocationAndWiring");
|
||||
}
|
||||
|
||||
internal void TestInPlaceDeserialization()
|
||||
{
|
||||
Log(LogLevel::Message, LogCategory::Game, "[UnitTest] Running TestInPlaceDeserialization...");
|
||||
TempArena tempArena = scratch_begin(nullptr, 0);
|
||||
|
||||
GameState* gameState = GetGameState();
|
||||
World testWorld{};
|
||||
gameState->World = &testWorld;
|
||||
InitWorld(&testWorld, tempArena.Arena);
|
||||
|
||||
InitEntityManager(&testWorld);
|
||||
EntityManager& manager = *testWorld.EntityManager;
|
||||
// 1. In-memory .jasset text fixture (no streams or files needed)
|
||||
String asset_content = ConstString("; Class\n"
|
||||
"Inert\n"
|
||||
"; version\n"
|
||||
"1\n"
|
||||
"; class_version\n"
|
||||
"1\n"
|
||||
"; id\n"
|
||||
"42\n"
|
||||
"; position\n"
|
||||
"12.5 -44.0 108.2 1.0\n"
|
||||
"; MeshInstance\n"
|
||||
"42\n");
|
||||
ByteBuffer buffer = { .Data = reinterpret_cast<Byte*>(asset_content.Str), .Size = asset_content.Size };
|
||||
// 2. Tokenize into Archive
|
||||
Archive load_ar = {};
|
||||
load_ar.arena = tempArena.Arena;
|
||||
load_ar.loading = true;
|
||||
load_ar.base = tokenize_archive(tempArena.Arena, buffer);
|
||||
// 3. Deserialize in-place
|
||||
Entity* loaded_base = deserialize_entity(load_ar, manager);
|
||||
// 4. Verify in-place allocations and values
|
||||
Assert(loaded_base != nullptr);
|
||||
Assert(loaded_base->ID == 42);
|
||||
Assert(EntityManager::ID > 42); // Generator counter advanced past loaded ID
|
||||
Assert(loaded_base->position.x == 12.5f);
|
||||
Assert(loaded_base->position.y == -44.0f);
|
||||
Assert(loaded_base->position.z == 108.2f);
|
||||
Assert(loaded_base->is_dirty == false);
|
||||
Inert* loaded_inert = DownCast<Inert>(loaded_base);
|
||||
Assert(loaded_inert != nullptr);
|
||||
Assert(loaded_inert->base == loaded_base);
|
||||
Assert(loaded_inert->MeshInstance == 42);
|
||||
ShutdownEntityManager();
|
||||
ShutdownWorld(&testWorld);
|
||||
|
||||
gameState->World = nullptr;
|
||||
|
||||
scratch_end(tempArena);
|
||||
Log(LogLevel::Message, LogCategory::Game, "[UnitTest] PASSED: TestInPlaceDeserialization");
|
||||
}
|
||||
|
||||
internal void TestDirtyTrackingLifecycle()
|
||||
{
|
||||
Log(LogLevel::Message, LogCategory::Game, "[UnitTest] Running TestDirtyTrackingLifecycle...");
|
||||
|
||||
TempArena tempArena = scratch_begin(nullptr, 0);
|
||||
|
||||
GameState* gameState = GetGameState();
|
||||
World testWorld{};
|
||||
gameState->World = &testWorld;
|
||||
InitWorld(&testWorld, tempArena.Arena);
|
||||
|
||||
InitEntityManager(&testWorld);
|
||||
EntityManager& manager = *testWorld.EntityManager;
|
||||
|
||||
Entity* entity = allocate_entity(manager, Inert::kind);
|
||||
Assert(entity->is_dirty == true);
|
||||
|
||||
// TODO : proper save and mutation
|
||||
// Simulate save
|
||||
entity->is_dirty = false;
|
||||
Assert(entity->is_dirty == false);
|
||||
|
||||
// Simulate mutation
|
||||
entity->position.x += 1.0f;
|
||||
entity->is_dirty = true;
|
||||
Assert(entity->is_dirty == true);
|
||||
|
||||
ShutdownEntityManager();
|
||||
ShutdownWorld(&testWorld);
|
||||
|
||||
gameState->World = nullptr;
|
||||
|
||||
scratch_end(tempArena);
|
||||
Log(LogLevel::Message, LogCategory::Game, "[UnitTest] PASSED: TestDirtyTrackingLifecycle");
|
||||
}
|
||||
|
||||
void WorldUnitTest()
|
||||
{
|
||||
Log(LogLevel::Message, LogCategory::Game, "==================================================");
|
||||
Log(LogLevel::Message, LogCategory::Game, "Starting Entity Allocation & Lifecycle Unit Tests");
|
||||
Log(LogLevel::Message, LogCategory::Game, "==================================================");
|
||||
|
||||
TestEntityAllocationAndWiring();
|
||||
TestInPlaceDeserialization();
|
||||
// TestSwapAndPopPointerFixup(); // Deferred to Entity_Removal_Brainstorm.md
|
||||
TestDirtyTrackingLifecycle();
|
||||
|
||||
Log(LogLevel::Message, LogCategory::Game, "==================================================");
|
||||
Log(LogLevel::Message, LogCategory::Game, "All Entity Lifecycle Unit Tests PASSED Successfully");
|
||||
Log(LogLevel::Message, LogCategory::Game, "==================================================");
|
||||
}
|
||||
} // namespace UnitTest
|
||||
|
||||
#endif
|
||||
@@ -4,9 +4,6 @@
|
||||
|
||||
#if JULIET_DEBUG
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
void WorldUnitTest();
|
||||
} // namespace UnitTest
|
||||
void UnitTest_WorldUnitTest(void);
|
||||
|
||||
#endif
|
||||
+48
-42
@@ -13,28 +13,26 @@
|
||||
#include <Entity/EntityManager.h>
|
||||
#include <Graphics/Camera.h>
|
||||
#include <Graphics/MeshRenderer.h>
|
||||
#include <imgui.h>
|
||||
#include <dcimgui.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#if JULIET_DEBUG
|
||||
#include <Debug/DebugTopBar.h>
|
||||
#include <UnitTest/WorldUnitTest.h>
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
GameState* gGameState = nullptr;
|
||||
}
|
||||
global GameState* gGameState = NULL;
|
||||
|
||||
GameState* GetGameState()
|
||||
GameState* GetGameState(void)
|
||||
{
|
||||
return gGameState;
|
||||
}
|
||||
|
||||
extern "C" JULIET_API void __cdecl GameShutdown()
|
||||
JULIET_API void __cdecl GameShutdown(void)
|
||||
{
|
||||
printf("Shutting down game...\n");
|
||||
|
||||
if (gGameState && gGameState->World)
|
||||
if (gGameState != NULL && gGameState->World != NULL)
|
||||
{
|
||||
ShutdownWorld(gGameState->World);
|
||||
}
|
||||
@@ -42,47 +40,55 @@ extern "C" JULIET_API void __cdecl GameShutdown()
|
||||
ShutdownEntityManager();
|
||||
}
|
||||
|
||||
extern "C" JULIET_API void __cdecl GameUpdate(GameData* params, [[maybe_unused]] float deltaTime)
|
||||
JULIET_API void __cdecl GameUpdate(GameData* params, float deltaTime)
|
||||
{
|
||||
Assert(params != NULL);
|
||||
|
||||
gGameState = params->GameState;
|
||||
if (!gGameState)
|
||||
if (gGameState == NULL)
|
||||
{
|
||||
Arena* gameStateArena = ArenaAllocate({ .ReserveSize = Megabytes(1), .Name = "Game Total Arena" });
|
||||
auto* gameState = ArenaPushStruct<GameState>(gameStateArena);
|
||||
ArenaParams gameStateParams = { .ReserveSize = Megabytes(1), .Name = "Game Total Arena" };
|
||||
Arena* gameStateArena = ArenaAllocate(gameStateParams);
|
||||
GameState* gameState = ArenaPushStruct(gameStateArena, GameState);
|
||||
gGameState = params->GameState = gameState;
|
||||
gameState->TotalArena = gameStateArena;
|
||||
gameState->Mode = GameMode_Editor;
|
||||
|
||||
gameState->TotalTime = 0.0f;
|
||||
gameState->Score = 0;
|
||||
|
||||
printf("Game Arena Allocated: %p\n", static_cast<void*>(gameState));
|
||||
printf("Game Arena Allocated: %p\n", (void*)gameState);
|
||||
|
||||
// Reserve cameras. Lets go with 4 for now
|
||||
ReserveCamera(4);
|
||||
|
||||
#if JULIET_DEBUG
|
||||
UnitTest::WorldUnitTest();
|
||||
UnitTest_WorldUnitTest();
|
||||
#endif
|
||||
|
||||
// Bootstrap world
|
||||
auto* worldArena = ArenaAllocate({ .ReserveSize = Megabytes(1), .Name = "World Arena" });
|
||||
World* world = ArenaPushStruct<World>(worldArena JULIET_DEBUG_PARAM("World"));
|
||||
gameState->World = world;
|
||||
ArenaParams worldParams = { .ReserveSize = Megabytes(1), .Name = "World Arena" };
|
||||
Arena* worldArena = ArenaAllocate(worldParams);
|
||||
World* world = ArenaPushStructTag(worldArena, World, "World");
|
||||
gameState->World = world;
|
||||
InitWorld(gameState->World, worldArena);
|
||||
|
||||
// Entity Use case
|
||||
InitEntityManager(gameState->World);
|
||||
auto& manager = GetEntityManager();
|
||||
EntityManager* manager = GetEntityManager();
|
||||
|
||||
NonNullPtr mesh = MakeEntity<Inert>(manager, 1.f, 2.f, 0.f);
|
||||
Inert* mesh = MakeEntity(Inert, manager, 1.f, 2.f, 0.f);
|
||||
Assert(mesh != NULL);
|
||||
|
||||
MeshAssetID cubeAsset = GetCubePrimitiveMeshAssetID();
|
||||
MeshInstanceID meshInst = CreateMeshInstance(cubeAsset, 0, MatrixIdentity());
|
||||
Matrix identity = MatrixIdentity();
|
||||
MeshInstanceID meshInst = CreateMeshInstance(cubeAsset, 0, &identity);
|
||||
mesh->MeshInstance = meshInst;
|
||||
|
||||
NonNullPtr mesh2 = MakeEntity<Inert>(manager, 4.f, 0.f, 2.f);
|
||||
Inert* mesh2 = MakeEntity(Inert, manager, 4.f, 0.f, 2.f);
|
||||
Assert(mesh2 != NULL);
|
||||
|
||||
MeshInstanceID meshInst2 = CreateMeshInstance(cubeAsset, 0, MatrixIdentity());
|
||||
MeshInstanceID meshInst2 = CreateMeshInstance(cubeAsset, 0, &identity);
|
||||
mesh2->MeshInstance = meshInst2;
|
||||
|
||||
// Summer at 2pm lighting
|
||||
@@ -97,64 +103,64 @@ extern "C" JULIET_API void __cdecl GameUpdate(GameData* params, [[maybe_unused]]
|
||||
#endif
|
||||
|
||||
GameMode previousFrameMode = gGameState->Mode;
|
||||
if (IsKeyPressed(ScanCode::F1))
|
||||
if (IsKeyPressed(ScanCode_F1))
|
||||
{
|
||||
if (previousFrameMode == GameMode::Play)
|
||||
if (previousFrameMode == GameMode_Play)
|
||||
{
|
||||
gGameState->Mode = GameMode::Debug;
|
||||
gGameState->Mode = GameMode_Debug;
|
||||
}
|
||||
else if (previousFrameMode == GameMode::Debug)
|
||||
else if (previousFrameMode == GameMode_Debug)
|
||||
{
|
||||
gGameState->Mode = GameMode::Play;
|
||||
gGameState->Mode = GameMode_Play;
|
||||
}
|
||||
}
|
||||
|
||||
if (gGameState->Mode == GameMode::Editor)
|
||||
if (gGameState->Mode == GameMode_Editor)
|
||||
{
|
||||
if (IsKeyPressed(ScanCode::F5))
|
||||
if (IsKeyPressed(ScanCode_F5))
|
||||
{
|
||||
gGameState->Mode = GameMode::Play;
|
||||
gGameState->Mode = GameMode_Play;
|
||||
}
|
||||
|
||||
#if JULIET_EDITOR
|
||||
RenderWorldEditorUI(*gGameState->World);
|
||||
RenderWorldEditorUI(gGameState->World);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (gGameState->Mode == GameMode::Play)
|
||||
if (gGameState->Mode == GameMode_Play)
|
||||
{
|
||||
if (IsKeyPressed(ScanCode::Escape))
|
||||
if (IsKeyPressed(ScanCode_Escape))
|
||||
{
|
||||
gGameState->Mode = GameMode::Editor;
|
||||
gGameState->Mode = GameMode_Editor;
|
||||
}
|
||||
|
||||
// update game
|
||||
}
|
||||
|
||||
auto& manager = GetEntityManager();
|
||||
EntityManager* manager = GetEntityManager();
|
||||
UpdateEntityManager(manager);
|
||||
|
||||
if (gGameState->Mode == GameMode::Debug)
|
||||
if (gGameState->Mode == GameMode_Debug)
|
||||
{
|
||||
if (previousFrameMode == GameMode::Play)
|
||||
if (previousFrameMode == GameMode_Play)
|
||||
{
|
||||
ActivateDebugController();
|
||||
}
|
||||
|
||||
if (IsKeyPressed(ScanCode::Escape))
|
||||
if (IsKeyPressed(ScanCode_Escape))
|
||||
{
|
||||
gGameState->Mode = GameMode::Editor;
|
||||
gGameState->Mode = GameMode_Editor;
|
||||
}
|
||||
|
||||
UpdateDebugController(deltaTime);
|
||||
|
||||
#if JULIET_DEBUG
|
||||
ImGui::Begin("Debug");
|
||||
ImGui_Begin("Debug", NULL, ImGuiWindowFlags_None);
|
||||
RenderImGuiDebugController(deltaTime);
|
||||
ImGui::End();
|
||||
ImGui_End();
|
||||
#endif
|
||||
}
|
||||
if (gGameState->Mode != GameMode::Debug && previousFrameMode == GameMode::Debug)
|
||||
if (gGameState->Mode != GameMode_Debug && previousFrameMode == GameMode_Debug)
|
||||
{
|
||||
DeactivateDebugController();
|
||||
}
|
||||
+13
-11
@@ -3,25 +3,27 @@
|
||||
#include <Core/Memory/MemoryArena.h>
|
||||
#include <Data/World.h>
|
||||
|
||||
struct EntityManager;
|
||||
typedef struct EntityManager EntityManager;
|
||||
|
||||
enum class GameMode
|
||||
typedef uint32 GameMode;
|
||||
enum
|
||||
{
|
||||
Editor,
|
||||
Play,
|
||||
Debug
|
||||
GameMode_Editor = 0,
|
||||
GameMode_Play,
|
||||
GameMode_Debug
|
||||
};
|
||||
|
||||
typedef struct GameState GameState;
|
||||
struct GameState
|
||||
{
|
||||
Arena* TotalArena = nullptr;
|
||||
Arena* TotalArena;
|
||||
|
||||
World* World = nullptr;
|
||||
World* World;
|
||||
|
||||
GameMode Mode = GameMode::Editor;
|
||||
GameMode Mode;
|
||||
|
||||
float TotalTime = 0.0f;
|
||||
int Score = 0;
|
||||
float TotalTime;
|
||||
int Score;
|
||||
};
|
||||
|
||||
[[nodiscard]] extern GameState* GetGameState();
|
||||
extern GameState* GetGameState(void);
|
||||
Reference in new issue
Block a user