Compare commits

...
20 Commits
Author SHA1 Message Date
Patedam 58b79fb499 wip entity deserialization phase 1. Unit test not passing because of globals that assume only one entity manager / game state. 2026-09-10 22:50:18 -04:00
Patedam 615d36b09c Serialization : started to unify how to allocate entity whether its from MakeEntity or during deserialization. 2026-09-08 23:24:48 -04:00
Patedam 3123d2f2e6 updating serialization tdd with latest changes 2026-09-07 20:05:17 -04:00
Patedam b1de6ccc49 finishing first step of serialization : the basics 2026-09-07 19:42:15 -04:00
Patedam a462575af4 ongoing refactor of serialization and various cleanup 2026-09-07 16:58:57 -04:00
Patedam 1a045c3578 updating coding guidelines + plan for full juliet serialization and entity system 2026-09-06 12:08:57 -04:00
Patedam 62e6284976 preparing serialization of entities 2026-09-06 12:08:31 -04:00
Patedam d7825270ee crc32: moving to constexpr to prepare for the juliet serializer 2026-09-06 12:04:13 -04:00
Patedam cb615091ca Improving entity manager and entity support for a simpler version, made some cleanup on the road to support serialization of world 2026-08-31 23:05:20 -04:00
Patedam 4180622d6a Removing named namespace from code base. 2026-08-22 21:39:21 -04:00
Patedam 678795b793 calloc to arena 2026-08-16 22:44:52 -04:00
Patedam 4d803fdf77 conversion to arena, missing get asset filename 2026-08-16 22:37:31 -04:00
Patedam a50e3c75cb conversion to arena of filesystem 2026-08-16 22:30:40 -04:00
Patedam 262d91dd49 Various conversion to memory arena and misc clean up 2026-08-16 22:10:51 -04:00
Patedam d1b7c5dbbe Adding scratch arena for main thread (and any thread in the future)
Added some function based on raddbg code for string conversion and more.
2026-08-16 18:06:46 -04:00
Patedam 3e7216f57d Modified build system to detect pch changes *gemini 2026-08-16 13:13:51 -04:00
Patedam 5566f76a94 Cleaning game.cpp removing old test code.
Created static mesh entity
Created debug bar
2026-08-10 22:12:31 -04:00
Patedam ee20c903b2 improved the shader of mesh renderer to make the global lighting better 2026-08-09 18:04:46 -04:00
Patedam e14931b27b Merge remote-tracking branch 'origin/main' 2026-08-09 18:00:16 -04:00
Patedam 5bc4375265 Added way to set global light from game.
+ Some clean up in the mesh renderer
2026-08-09 18:00:05 -04:00
189 changed files with 18000 additions and 12580 deletions
+13 -5
View File
@@ -5,12 +5,20 @@ trigger: always_on
Code compiles with all warning active and warning as errors.
use static_cast or reinterpret_cast but not parenthesis for casting.
No exceptions
Use [[nodiscard]]
Use [[nodiscard]] when risk of memory leak (anything returning pointer)
auto is allowed but when its a pointer add the * and when reference adds the &
Member variable are CamelCase
Types are CamelCase
Functions are CamelCase.
Add Assert to make sure all assumptions are good. Parameters of functions for example should be verified with Assert.
Code should be self commented using proper variable names, types and functions. No need to add comments most of the time, unless the algorithm is very complex and hard to read.
When creating a new system framework, make a unit test. To make the unit test we should not modify the framework code for special unit test case.
Always put braces for if,else,for,while etc.
Always put braces for if,else,for,while etc.
Coding Style:
Note: The code base do not fully use those, if in doubt, use this style and not legacy style.
Types (Structs/Enums/Unions) : PascalCase. Exemple : Arena, String8, DbgTarget, EvalValue
Primitive Types : lower_snake_case. Exemple: uint8, uint16, uint32, uint64, size_t, index_t, int32...
Function Names: lower_snake_case. Exemple: str16_from_8.
Namespace: do not use c++ namespace, add PascalCase prefix to function names. Exemple: W32_do_something
Local Variables and parameters: lower_snake_case. Exemple: arena, str, data.
Struct members: lower_snake_case. Exemple: data, node, next.
global/static variables: lower_snake_case prefixed. Exemple: g_my_global.
const variables: lower_snake_case prefixed. Exemple: k_my_const_var.
Defines and Macros: ALL_CAPS_SNAKE. Exemple: MY_MACRO
@@ -11,7 +11,7 @@ trusted_commands:
You are a senior engine architect for the Juliet project. Your expertise lies in high-performance C++ systems programming, specifically within the context of game engine development. You value performance, memory efficiency, and maintainability.
## Coding Guidelines
You must follow the project's `coding-guidelines.md` (always loaded). Do not deviate from any rule. Pay special attention to `static_cast`/`reinterpret_cast` (never C-style casts), `auto*`/`auto&`, mandatory braces, and `[[nodiscard]]`.
You must follow the project's `coding-guidelines.md` (always loaded). Do not deviate from any rule. Pay special attention to `static_cast`/`reinterpret_cast` (never C-style casts), `auto*`/`auto&`, mandatory braces, and `[[nodiscard]]` when needed.
## Focus Areas
1. **High Performance**: Always consider cache locality and CPU cycle cost.
+1 -1
View File
@@ -11,7 +11,7 @@ trusted_commands:
You are a senior engine architect for the Juliet project. Your expertise lies in debugging C++ game engines. You add logs and use debug tricks to find the root cause of issues.
## Coding Guidelines
You must follow the project's `coding-guidelines.md` (always loaded). Do not deviate from any rule. Even in debug/diagnostic code, use proper casts, braces, and `[[nodiscard]]`.
You must follow the project's `coding-guidelines.md` (always loaded). Do not deviate from any rule. Even in debug/diagnostic code, use proper casts, braces, and `[[nodiscard]]` when needed.
## Workflows
- **Building**: Use the `/build` workflow to compile the project.
Binary file not shown.
+8 -3
View File
@@ -11,11 +11,13 @@ float4 main(Input input) : SV_Target0
{
float3 normal = normalize(input.WorldNormal);
// Initial ambient component
float3 result = input.Color.rgb * GlobalLightColor * GlobalAmbientIntensity;
// Initial ambient component (Soft blue sky ambient)
float3 ambientColor = float3(0.6f, 0.7f, 0.9f);
float3 result = input.Color.rgb * ambientColor * GlobalAmbientIntensity;
// Directional light contribution
float ndotl = max(dot(normal, -GlobalLightDirection), 0.0);
float3 sunDir = normalize(-GlobalLightDirection);
float ndotl = max(dot(normal, sunDir), 0.0);
result += input.Color.rgb * GlobalLightColor * ndotl;
// Point lights
@@ -42,5 +44,8 @@ float4 main(Input input) : SV_Target0
}
}
// Simple Gamma correction
result = pow(result, 1.0 / 2.2);
return float4(result, input.Color.a);
}
-2
View File
@@ -1,6 +1,4 @@
#pragma once
#include <Core/Common/CoreTypes.h>
constexpr index_t kPlayCamera = 0;
constexpr index_t kDebugCamera = 1;
+25 -25
View File
@@ -1,4 +1,4 @@
#include <Controller/DebugCameraController.h>
#include <Controller/DebugCameraController.h>
#include <Controller/ControllerUtils.h>
#include <Core/Common/CoreUtils.h>
@@ -24,10 +24,10 @@ void ActivateDebugController()
{
Assert(gIsDebugCameraActive == false);
Juliet::Camera* currentCam = Juliet::GetCurrentCamera();
Camera* currentCam = GetCurrentCamera();
gPreviousCameraIndex = currentCam->Index;
Juliet::SetCurrentCamera(kDebugCamera);
SetCurrentCamera(kDebugCamera);
gIsDebugCameraActive = true;
gFirstUpdate = true;
@@ -41,7 +41,7 @@ void DeactivateDebugController()
gIsDebugCameraActive = false;
Juliet::SetCurrentCamera(gPreviousCameraIndex);
SetCurrentCamera(gPreviousCameraIndex);
}
bool IsDebugControllerActive()
@@ -51,26 +51,26 @@ bool IsDebugControllerActive()
void UpdateDebugController(float dt)
{
Juliet::Camera* currentCam = Juliet::GetCurrentCamera();
Camera* currentCam = GetCurrentCamera();
if (gFirstUpdate)
{
Juliet::Vector3 dir = Juliet::Vector3{ 0.0f, 0.0f, 0.0f } - currentCam->Position;
dir = Juliet::Normalize(dir);
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;
Juliet::Vector3 forward;
Vector3 forward;
forward.x = cosf(gPitch) * cosf(gYaw);
forward.y = cosf(gPitch) * sinf(gYaw);
forward.z = sinf(gPitch);
Juliet::Vector3 right = Juliet::Normalize(Juliet::Cross(Juliet::Vector3{ 0.0f, 0.0f, 1.0f }, forward));
Vector3 right = Normalize(Cross(Vector3{ 0.0f, 0.0f, 1.0f }, forward));
currentCam->Target = currentCam->Position + forward;
currentCam->Up = Juliet::Cross(forward, right);
currentCam->Up = Cross(forward, right);
}
bool isRightMouseButtonDown = Juliet::IsMouseButtonDown(Juliet::MouseButton::Right);
bool isRightMouseButtonDown = IsMouseButtonDown(MouseButton::Right);
if (isRightMouseButtonDown && !gWasRightMouseButtonDown)
{
gIsFpsModeActive = !gIsFpsModeActive;
@@ -82,7 +82,7 @@ void UpdateDebugController(float dt)
return;
}
Juliet::MousePosition mouseDelta = Juliet::GetMouseDelta();
MousePosition mouseDelta = GetMouseDelta();
float sensitivity = 0.005f;
gYaw += mouseDelta.X * sensitivity;
@@ -91,52 +91,52 @@ void UpdateDebugController(float dt)
gPitch = std::min(gPitch, 1.5f);
gPitch = std::max(gPitch, -1.5f);
if (Juliet::IsKeyDown(Juliet::ScanCode::Q))
if (IsKeyDown(ScanCode::Q))
{
gYaw -= 2.0f * dt;
}
if (Juliet::IsKeyDown(Juliet::ScanCode::E))
if (IsKeyDown(ScanCode::E))
{
gYaw += 2.0f * dt;
}
Juliet::Vector3 forward;
Vector3 forward;
forward.x = cosf(gPitch) * cosf(gYaw);
forward.y = cosf(gPitch) * sinf(gYaw);
forward.z = sinf(gPitch);
Juliet::Vector3 right = Juliet::Normalize(Juliet::Cross(Juliet::Vector3{ 0.0f, 0.0f, 1.0f }, forward));
Juliet::Vector3 defaultUp = Juliet::Cross(forward, right);
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 (Juliet::IsKeyDown(Juliet::ScanCode::LeftShift))
if (IsKeyDown(ScanCode::LeftShift))
{
speedPerFrame *= 10.f; // 100m/s
}
if (Juliet::IsKeyDown(Juliet::ScanCode::W))
if (IsKeyDown(ScanCode::W))
{
currentCam->Position = currentCam->Position + forward * (speedPerFrame * dt);
}
if (Juliet::IsKeyDown(Juliet::ScanCode::S))
if (IsKeyDown(ScanCode::S))
{
currentCam->Position = currentCam->Position - forward * (speedPerFrame * dt);
}
if (Juliet::IsKeyDown(Juliet::ScanCode::D))
if (IsKeyDown(ScanCode::D))
{
currentCam->Position = currentCam->Position + right * (speedPerFrame * dt);
}
if (Juliet::IsKeyDown(Juliet::ScanCode::A))
if (IsKeyDown(ScanCode::A))
{
currentCam->Position = currentCam->Position - right * (speedPerFrame * dt);
}
if (Juliet::IsKeyDown(Juliet::ScanCode::Space))
if (IsKeyDown(ScanCode::Space))
{
currentCam->Position = currentCam->Position + defaultUp * (speedPerFrame * dt);
}
if (Juliet::IsKeyDown(Juliet::ScanCode::LeftControl))
if (IsKeyDown(ScanCode::LeftControl))
{
currentCam->Position = currentCam->Position - defaultUp * (speedPerFrame * dt);
}
@@ -148,7 +148,7 @@ void UpdateDebugController(float dt)
#if JULIET_DEBUG
void RenderImGuiDebugController(float dt)
{
Juliet::Camera* currentCam = Juliet::GetCurrentCamera();
Camera* currentCam = GetCurrentCamera();
ImGui::Text("Delta time: %f", dt);
+322
View File
@@ -0,0 +1,322 @@
#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));
String path = GetAssetPath(temp.Arena, WrapString(worldFilePath));
if (ImGui::Button("Save World"))
{
Archive data = { .arena = temp.Arena, .loading = false };
Serialize(data, world, path);
}
ImGui::SameLine();
if (ImGui::Button("Load World"))
{
Archive data{ .arena = temp.Arena, .loading = true };
Serialize(data, world, path);
}
scratch_end(temp);
// ImGui::SameLine();
// if (ImGui::Button("Clear All"))
// {
// ClearWorld(world);
// }
//
// ImGui::Separator();
//
// if (ImGui::Button("Add Entity"))
// {
// (void)AddWorldEntity(world, 0.0f, 0.0f, 0.0f);
// }
//
// ImGui::Text("Entity Count: %zu", world.Entities.Size());
// ImGui::Separator();
//
// static int selectedEntity = -1;
// if (selectedEntity >= static_cast<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
+38
View File
@@ -0,0 +1,38 @@
#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;
#pragma pack(push, 1)
struct WorldFileHeader
{
uint32 Magic = 0x444C574A; // 'JWLD' in little-endian
};
#pragma pack(pop)
constexpr uint32 kWorldMagic = 0x444C574A; // 'JWLD'
constexpr uint32 kWorldVersion = 1;
struct World
{
Arena* WorldArena = nullptr;
EntityManager* EntityManager = nullptr;
};
void InitWorld(NonNullPtr<World> world, NonNullPtr<Arena> arena);
void ShutdownWorld(NonNullPtr<World> world);
void AddToWorld(NonNullPtr<World> world, NonNullPtr<Entity> entity);
void RemoveWorldEntity(World& world, size_t index);
void Serialize(Archive& data, World& world, String filename);
#if JULIET_EDITOR
void RenderWorldEditorUI(World& world);
#endif
+49
View File
@@ -0,0 +1,49 @@
#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();
}
+5
View File
@@ -0,0 +1,5 @@
#pragma once
#if JULIET_DEBUG
void DrawTopBar();
#endif
+50
View File
@@ -0,0 +1,50 @@
#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);
}
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;
}
+39 -36
View File
@@ -1,68 +1,71 @@
#pragma once
#include <Core/Common/CoreUtils.h>
#include <Core/Memory/Allocator.h>
#include <Core/Memory/MemoryArena.h>
#include <Core/Math/Vector.h>
#include <Engine/Class.h>
#include <Entity/entity_common.h>
#include <Entity/EntityManager.h>
#define DECLARE_ENTITY() \
Entity* Base; \
static const Juliet::Class* Kind;
// Will register the class globally at launch
#define DEFINE_ENTITY(entity) \
constexpr Juliet::Class entityKind##entity(#entity, sizeof(#entity) / sizeof(char)); \
const Juliet::Class* entity::Kind = &entityKind##entity;
using DerivedType = void*;
struct Entity final
{
EntityID ID;
const Juliet::Class* Kind;
DerivedType Derived;
float X, Y;
index_t MeshInstance = indexMax;
DECLARE_CLASS()
EntityID ID = 0;
const Class* derived_kind = nullptr;
DerivedType derived = nullptr;
Vector4 position = {};
bool is_dirty = false;
};
// Can reinterpret cast to this to have the offset of Base and Kind for any entity
struct EntityTemplate
{
DECLARE_ENTITY();
};
//
template <typename EntityType>
concept EntityConcept = requires(EntityType entity) {
requires std::same_as<decltype(entity.Kind), const Juliet::Class*>;
requires std::same_as<decltype(entity.Base), Entity*>;
{ EntityType::kind } -> std::convertible_to<const Class*>;
requires std::same_as<decltype(entity.base), Entity*>;
};
template <typename EntityType>
requires EntityConcept<EntityType>
bool IsA(const Entity* entity)
[[nodiscard]] bool IsA(const Entity* entity)
{
return entity->Kind == EntityType::Kind;
Assert(entity != nullptr);
return entity->derived_kind == EntityType::kind;
}
template <typename EntityType>
requires EntityConcept<EntityType>
EntityType* MakeEntity(EntityManager& manager, float x, float y)
[[nodiscard]] EntityType* MakeEntity(EntityManager& manager, float x, float y, float z)
{
auto* arena = manager.Arena;
EntityType* result = Juliet::ArenaPushStruct<EntityType>(arena);
Entity base;
base.X = x;
base.Y = y;
base.Derived = result;
base.Kind = EntityType::Kind;
manager.Entities.PushBack(base);
Entity* base_ptr = allocate_entity(manager, EntityType::kind);
Assert(base_ptr);
result->Base = manager.Entities.Back();
base_ptr->ID = EntityManager::ID++;
RegisterEntity(manager, &base);
base_ptr->position.x = x;
base_ptr->position.y = y;
base_ptr->position.z = z;
base_ptr->position.w = 1.0f;
return result;
return (EntityType*)base_ptr->derived;
}
template <typename EntityType>
requires EntityConcept<EntityType>
EntityType* DownCast(Entity* entity)
[[nodiscard]] EntityType* DownCast(Entity* entity)
{
Assert(entity != nullptr);
Assert(IsA<EntityType>(entity));
return static_cast<EntityType*>(entity->Derived);
return static_cast<EntityType*>(entity->derived);
}
void serialize(Archive& ar, NonNullPtr<Entity> entity);
[[nodiscard]] const Class* find_class_by_name(String name);
[[nodiscard]] const Class* resolve_entity_class(uint8 kind, uint32 crc);
+119 -7
View File
@@ -1,31 +1,143 @@
#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>
EntityID EntityManager::ID = 0;
void InitEntityManager(Juliet::NonNullPtr<World> world)
void InitEntityManager(NonNullPtr<World> world)
{
EntityManager* newManager = Juliet::ArenaPushStruct<EntityManager>(world->WorldArena);
EntityManager* newManager = ArenaPushStruct<EntityManager>(world->WorldArena);
world->EntityManager = newManager;
newManager->Entities.Create(world->WorldArena JULIET_DEBUG_PARAM("Entities"));
newManager->Arena = Juliet::ArenaAllocate({} JULIET_DEBUG_PARAM("Entity Arena"));
ArenaParams by_type_params{ .ReserveSize = Kilobytes(1),
.CommitSize = Kilobytes(1),
.Name = "" JULIET_DEBUG_ONLY(, .CanReserveMore = false) };
for (uint8 i = 0; i < ENTITY(Count); ++i)
{
by_type_params.Name = kEntity_type_names[i];
newManager->by_type[i].arena = ArenaAllocate(by_type_params);
newManager->by_type[i].array = nullptr;
}
}
void ShutdownEntityManager()
{
GetEntityManager().Entities.Destroy();
auto& manager = GetEntityManager();
for (typed_entity_array& type : manager.by_type)
{
ArenaRelease(type.arena);
}
manager.Entities.Destroy();
}
EntityManager& GetEntityManager()
{
Juliet::NonNullPtr entityManager = GetGameState()->World->EntityManager;
NonNullPtr entityManager = GetGameState()->World->EntityManager;
return *entityManager;
}
void RegisterEntity(EntityManager& /*manager*/, Entity* entity)
EntityTemplate* RegisterEntity(EntityManager& manager, Entity* base, DerivedType entity)
{
entity->ID = EntityManager::ID++;
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;
}
+22 -11
View File
@@ -1,23 +1,34 @@
#pragma once
#include <Core/Common/CoreTypes.h>
#include <Core/Container/Vector.h>
#include <game.h>
using EntityID = uint64_t;
#include <Entity/entity_common.h>
struct World;
struct EntityTemplate;
struct Entity;
struct Class;
struct typed_entity_array
{
Arena* arena;
EntityTemplate* array;
size_t count;
};
struct EntityManager
{
static EntityID ID;
Juliet::Arena* Arena;
// TODO: Should be a pool
Juliet::VectorArena<Entity, 1024> Entities;
VectorArena<Entity, 100'000> Entities;
typed_entity_array by_type[ENTITY(Count)];
};
void InitEntityManager(Juliet::NonNullPtr<World> world);
void ShutdownEntityManager();
EntityManager& GetEntityManager();
void RegisterEntity(EntityManager& manager, Entity* entity);
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);
[[nodisacrd]] Entity* deserialize_entity(Archive& archive, EntityManager& manager);
+21
View File
@@ -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
+40
View File
@@ -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;
+4
View File
@@ -0,0 +1,4 @@
#include <Entity/entity_types.h>
DEFINE_ENTITY_VERSIONED(Inert, 1)
internal void serialize(Archive& /*ar*/, uint16 /*version*/, Inert& /*inert*/) {}
+10
View File
@@ -0,0 +1,10 @@
#pragma once
#include <Entity/entity_common.h>
struct Inert
{
DECLARE_ENTITY()
index_t MeshInstance = indexMax;
};
@@ -0,0 +1,439 @@
# Juliet Engine: Serialization & Text Archive Architecture
## Technical Specification & Architectural Design
---
## 1. Executive Summary & Architecture Goals
### 1.1 Context & Motivation
Juliet historically relied on monolithic, packed binary blobs for world and entity persistence. While fast to read as raw byte offsets, binary serialization suffers from three major flaws:
1. **Merge Incompatibility**: Binary assets cannot be merged or diffed in version control systems (Git / Perforce), causing unresolvable binary conflicts and data loss.
2. **Schema Rigidity**: Adding, removing, or reordering a single struct field invalidates all existing binary files unless complex manual byte-offset mapping tables are maintained.
3. **Opacity**: Designers and engineers cannot inspect, debug, or patch asset properties in a standard text editor.
### 1.2 Architectural Goals
The `.jasset` text archive framework replaces legacy binary blobs with a human-readable, diff-friendly property serialization pipeline adhering to Juliet's systems programming principles:
- **Zero Dynamic Heap Allocations**: Parsing, tokenization, formatting, and buffer transformations execute entirely within Juliet memory arenas (`Arena`, `TempArena`, `scratch_begin` / `scratch_end`). Standard library containers (`std::string`, `std::vector`) and raw heap allocators (`malloc`, `new`) are forbidden.
- **Zero-Copy In-Memory Tokenization**: Files are loaded into arena memory once via `LoadFile`. The tokenizer parses properties into lightweight slices represented by Juliet's `String` (`char* Str; size_t Size;`), referencing existing file buffer bytes without string duplication.
- **$O(1)$ Property Lookup via Compile-Time & Runtime CRC32**: Property keys in text files are hashed once during tokenization into 32-bit CRC values. In code, lookups utilize compile-time hashed literals (`operator""_crc32`). Property resolution is a single 32-bit integer comparison.
- **Symmetric Single-Function Serialization**: A single `serialize` implementation per entity or data structure handles both Save and Load paths, guaranteeing read and write schemas never diverge.
- **Graceful Forward/Backward Compatibility**: Missing keys during load automatically preserve struct default values. Unknown keys present in newer asset files are safely ignored without parse failure.
- **Two-Tier Decoupled Versioning via Generalized `Class`**: Core engine entity properties (`; version`) and derived gameplay class properties (`; class_version`) are versioned independently through their respective `Class` descriptors.
- **In-Place Schema Migration**: Deprecated fields no longer present in C++ structs are read into temporary stack variables during load using standard `SERIALIZE` calls guarded by `if (ar.loading && version < N)`, seamlessly converting legacy data without struct pollution or persisting obsolete keys on subsequent saves.
- **Clean Warning-Free C++**: Fully conforming to Juliet coding guidelines: strict assertions (`Assert`), `[[nodiscard]]`, `auto*`/`auto&`, mandatory braces, and `static_cast`/`reinterpret_cast`.
---
## 2. The `.jasset` Text Format Specification
### 2.1 Grammar & Structural Rules
The `.jasset` format uses a line-oriented, key-value property hierarchy designed for visual clarity and clean Git diffs.
```ebnf
AssetFile ::= { CommentLine | EmptyLine | PropertyDeclaration } ;
CommentLine ::= ( "#" | "//" ) { Character } LineEnding ;
EmptyLine ::= { Whitespace } LineEnding ;
PropertyDeclaration ::= KeyHeader LineEnding ValueBlock ;
KeyHeader ::= ";" { Whitespace } Identifier ;
ValueBlock ::= { ValueLine LineEnding } ;
ValueLine ::= { Whitespace } ValueString { Whitespace } ;
LineEnding ::= "\r\n" | "\n" ;
Identifier ::= [a-zA-Z_][a-zA-Z0-9_]* ;
```
#### Formatting Rules:
1. **Key Declarations**: A property begins with a semicolon `;` followed by optional whitespace and a case-sensitive identifier (e.g. `; position`).
2. **Value Blocks**: The line(s) immediately following a key header contain its value payload.
3. **Comments**: Any line whose first non-whitespace character is `#` or `//` is treated as a comment and ignored. Inline comments on property lines are forbidden.
4. **Whitespace**: Leading and trailing spaces or tabs on both keys and values are stripped during tokenization.
5. **Line Endings**: Both Windows CRLF (`\r\n`) and Linux LF (`\n`) are transparently accepted.
### 2.2 Formatting Specifications
#### Scalar Types
Scalars are formatted as decimal representations on a single line:
```
; max_speed
180.500000
; gear_count
6
; turbo
true
```
- Floats: Output via `%.9g` or `%f`.
- Integers: Signed (`%d`, `%lld`) and unsigned (`%u`, `%llu`).
- Booleans: Case-insensitive `true` / `false` or `1` / `0`.
#### Vector Types (`Vector4`)
Multi-component vectors are space-delimited on a single value line:
```
; position
10.0 20.0 30.0 1.0
```
#### String Types (`String`)
Strings containing spaces are wrapped in double quotes `"..."`. Quotes are automatically stripped upon loading and emitted during saving when spaces are present:
```
; name
"Paladin Hero"
```
#### Asset File Example (`Entity_01.jasset`)
```
# Juliet Entity Asset File
; version
1
; id
1001
; position
12.500000 0.000000 45.200000 1.000000
; class
Inert
; class_version
1
; mesh_instance
42
```
---
## 3. Zero-Copy Tokenization & Fast Property Lookup
### 3.1 Data Structures (`Core/Common/serialization.h`)
To eliminate heap fragmentation, the parser loads the entire `.jasset` file into contiguous arena memory and parses it into a flat array of lightweight slices.
```cpp
struct ArchivePropertyNode
{
String key;
String value;
uint32 key_crc;
bool consumed;
};
struct ParsedArchive
{
ArchivePropertyNode* nodes = nullptr;
uint32 property_count = 0;
};
```
- `key`: Sliced `String` referencing the key name.
- `value`: Sliced `String` directly referencing file buffer bytes (zero-copy).
- `key_crc`: 32-bit CRC hash computed once during tokenization.
- `consumed`: Initialized to `false`. Set to `true` whenever queried by `find_property`.
### 3.2 Dual-Mode CRC32 (`Core/Common/CRC32.h`)
Lookups rely on compile-time string hashing via `constexpr` / `consteval`:
```cpp
[[nodiscard]] constexpr uint32 crc32(const char* str, size_t length);
[[nodiscard]] constexpr uint32 crc32(String str);
[[nodiscard]] consteval uint32 operator""_crc32(const char* str, size_t length);
```
### 3.3 Tokenization API (`tokenize_archive`)
```cpp
JULIET_API ParsedArchive tokenize_archive(NonNullPtr<Arena> arena, ByteBuffer file_buffer);
```
**Algorithm Invariants**:
1. **Pass 1 (Count)**: Scans the buffer to count `;` key headers at line starts, allocating the exact node array in `arena`.
2. **Pass 2 (Extract)**: Slices key and value `String`s, trims whitespace, computes `key_crc = crc32(key)`, and populates nodes. Skips `#` and `//` comments.
### 3.4 Property Lookup & Audit API
```cpp
JULIET_API ArchivePropertyNode* find_property(NonNullPtr<ParsedArchive> archive, uint32 property_crc);
#if JULIET_DEBUG
JULIET_API void audit_unconsumed_properties(NonNullPtr<ParsedArchive> archive, String context_name);
#endif
```
- `find_property`: Performs an $O(1)$ integer comparison against `key_crc`. When found, marks `node->consumed = true`.
- `audit_unconsumed_properties`: Iterates through all nodes in debug builds and logs warnings for any property with `consumed == false`, catching typos or abandoned schema fields.
---
## 4. The `Archive` Context Struct & Streaming I/O
### 4.1 Struct Definition (`Core/Common/serialization.h`)
The `Archive` struct unifies loading and saving state into a single decoupled context:
```cpp
struct Archive
{
Arena* arena;
bool loading;
ParsedArchive base = {};
IOStream* stream = nullptr;
// Legacy binary support fields (to be deprecated)
void* base_ptr = nullptr;
index_t offset = 0;
};
```
- When `loading == true`: Reads properties from `base.nodes` via `find_property`.
- When `loading == false`: Writes formatted key-value pairs directly to `stream`.
### 4.2 Property Header Formatting
```cpp
JULIET_API void write_property_header(Archive& archive, String property_name);
```
Emits `; <property_name>\n` directly to `ar.stream` with zero intermediate heap buffers.
---
## 5. Property Serialization API & Helpers
### 5.1 Unified Serialization Idiom
All property serialization uses a single template function:
```cpp
template <typename Type>
bool serialize(Archive& ar, String property_name, uint32 property_crc, Type& value)
{
Assert(IsValid(property_name));
bool result = false;
if (ar.loading)
{
if (auto* prop = find_property(&ar.base, property_crc))
{
if (read_prop(ar, prop->value, value))
{
result = true;
}
}
}
else
{
write_property_header(ar, property_name);
write(ar.stream, value);
result = true;
}
return result;
}
```
### 5.2 Convenience Macros
```cpp
#define SERIALIZE(ar, name, var) serialize((ar), ConstString(#name), #name##_crc32, (var))
#define SERIALIZE_SIMPLE(ar, var) SERIALIZE(ar, var, var)
```
- `SERIALIZE(ar, id, entity->ID)`: Serializes property named `"id"` with `"id"_crc32`.
- `SERIALIZE_SIMPLE(ar, position)`: Uses variable identifier as property name.
### 5.3 Supported Type Conversions
Conversion between text and memory is handled by overloaded `read` and `write` primitives:
| C++ Type | Text Format | Conversion Primitive |
| :--- | :--- | :--- |
| `float` | `180.500000` | `strtof` / `IOPrintf("%.9g")` |
| `int8`, `int16`, `int32`, `int64` | `42` / `-100` | `strtol`, `strtoll` / `IOPrintf("%d")` |
| `uint8`, `uint16`, `uint32`, `uint64` | `1001` / `0x...` | `strtoul`, `strtoull` / `IOPrintf("%u")` |
| `bool` | `true` / `false` | `true/false/1/0` string compare / `IOPrintf` |
| `Vector4` | `10.0 20.0 30.0 1.0` | Space-delimited float parse / `IOPrintf` |
| `String` | `"Paladin Hero"` | Arena-allocated copy, quote strip / `IOPrintf` |
---
## 6. Two-Tier Versioning & Generalized `Class` Architecture
### 6.1 Architectural Principle
To prevent monolithic engine updates from forcing all gameplay assets to re-version, schema versions are decoupled into two tiers:
1. **Base Version (`; version`)**: Managed by root classes (e.g. `Entity::kind->version`). Governs core engine properties (`id`, `position`).
2. **Derived Version (`; class_version`)**: Managed by derived classes (e.g. `Inert::kind->version`). Governs gameplay-specific component properties.
### 6.2 The `Class` Descriptor (`Engine/Class.h`)
Every serializable entity or component is described by an immutable `Class` instance:
```cpp
using serialize_fct_type = void (*)(Archive& ar, uint16 version, void* payload);
using default_init_fct_type = void (*)(void* payload);
struct Class
{
uint32 CRC;
uint8 kind;
uint16 version;
const Class* base_class;
serialize_fct_type serialize_fct;
default_init_fct_type default_init_fct;
size_t size_of;
size_t alignment;
#if JULIET_DEBUG
String Name;
#endif
};
```
### 6.3 Class Registration Macros
```cpp
#define DECLARE_CLASS() \
static Class* kind;
#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 = \
MakeClass(ConstString(#cls), 0, (version), (base_class), sizeof(cls), alignof(cls), \
(serialize_fct), default_init_##cls); \
Class* cls::kind = const_cast<Class*>(&classKind##cls);
```
For derived entity types, `DECLARE_ENTITY()` and `DEFINE_ENTITY_VERSIONED` compose cleanly:
```cpp
#define DECLARE_ENTITY() \
Entity* base; \
DECLARE_CLASS()
#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);
```
### 6.4 Universal Class Serializer (`Engine/class.cpp`)
```cpp
void serialize(Archive& ar, NonNullPtr<Class> cls, void* instance)
{
Assert(instance != nullptr);
uint16 version = cls->version;
if (cls->base_class)
{
serialize(ar, ConstString("class_version"), "class_version"_crc32, version);
}
else
{
serialize(ar, ConstString("version"), "version"_crc32, version);
}
if (cls->serialize_fct)
{
cls->serialize_fct(ar, version, instance);
}
}
```
### 6.5 Runtime Type Queries (`IsA`)
Polymorphic type safety is resolved without virtual tables or RTTI:
```cpp
bool IsA(const Class& query, const Class* target);
template <typename TargetType>
bool IsA(const Class& cls)
{
return IsA(cls, TargetType::kind);
}
```
### 6.6 Entity Serialization Composition
An entity instance composes base `Entity` properties and derived component properties:
```cpp
void serialize_entity(Archive& ar, uint16 /*version*/, void* payload)
{
Assert(payload != nullptr);
auto* entity = static_cast<Entity*>(payload);
SERIALIZE(ar, id, entity->ID);
SERIALIZE(ar, position, entity->position);
}
DEFINE_CLASS_VERSIONED(Entity, 1, nullptr, serialize_entity)
void serialize(Archive& ar, NonNullPtr<Entity> entity)
{
// 1. Serialize base Entity properties (reads/writes '; version')
serialize(ar, Entity::kind, entity.Get());
// 2. Serialize derived component properties (reads/writes '; class_version')
if (entity->derived_kind != nullptr && entity->derived != nullptr)
{
serialize(ar, entity->derived_kind, entity->derived);
}
}
```
---
## 7. In-Place Schema Migration & Deprecation
### 7.1 Deprecation Principle
When gameplay code evolves, obsolete member variables are deleted from active C++ structs to avoid memory waste and code clutter. Obsolete properties are migrated exclusively during loading using temporary local stack variables.
### 7.2 The Stack-Allocated Migration Idiom
In the class's `serialize_fct(Archive& ar, uint16 version, void* payload)`:
1. When `ar.loading == true` and `version < N`:
- Declare a temporary variable on the stack matching the legacy type.
- Call `SERIALIZE(ar, old_field_name, deprecated_var)`.
- If present, transform the legacy data into the modern struct field(s).
2. When saving (`ar.loading == false`):
- The migration block is skipped. Only modern struct properties are written.
- On the next save, obsolete keys are automatically purged from disk.
```cpp
void serialize_projectile(Archive& ar, uint16 version, void* payload)
{
Assert(payload != nullptr);
auto* projectile = static_cast<Projectile*>(payload);
SERIALIZE(ar, damage, projectile->damage);
if (ar.loading && version < 2)
{
// Migrating v1 scalar 'speed' into modern Vector4 'velocity'
float deprecated_speed = 0.0f;
if (SERIALIZE(ar, speed, deprecated_speed))
{
projectile->velocity = Vector4{ deprecated_speed, 0.0f, 0.0f, 0.0f };
}
}
else
{
SERIALIZE(ar, velocity, projectile->velocity);
}
}
```
---
## 8. Verification & Unit Testing Framework
### 8.1 Engine-Level Test Runner (`Juliet/src/UnitTest/`)
Unit testing lives inside the Juliet engine layer (`Juliet/src/UnitTest/serialization_test.cpp`) and executes during engine startup in debug builds via `UnitTest::RunUnitTests()` in `RunUnitTests.cpp`.
### 8.2 Test Coverage Matrix
| Test Function | Target Feature | Validation Criteria |
| :--- | :--- | :--- |
| `test_parser_tokenization` | Zero-copy text parser | Validates handling of `#` and `//` comments, whitespace trimming, mixed CRLF/LF, and fast CRC property lookups. |
| `test_default_value_retention` | Partial schema loading | Validates that missing properties in partial files preserve existing struct default values without corruption. |
| `test_deprecation_migration` | Stack-based schema migration | Loads a v1 asset containing obsolete `; speed`, verifies `version = 1` is received, and validates migration into modern fields. |
| `test_class_inheritance` | Runtime `IsA` queries | Validates polymorphic inheritance checks across base and derived `Class` instances. |
| `test_string_and_vector4` | Primitives & text quoting | Verifies parsing and quote handling of `String` and multi-component `Vector4`. |
---
## 9. Deliverables & File Summary
| File | Responsibilities |
| :--- | :--- |
| [`Juliet/include/Core/Common/serialization.h`](file:///w:/Classified/Juliet/Juliet/include/Core/Common/serialization.h) | `ArchivePropertyNode`, `ParsedArchive`, `Archive` struct, `serialize` template, and `SERIALIZE` macros. |
| [`Juliet/src/Core/Common/serialization.cpp`](file:///w:/Classified/Juliet/Juliet/src/Core/Common/serialization.cpp) | `tokenize_archive`, `find_property`, `audit_unconsumed_properties`, `read_prop`, `read`, and `write` primitives. |
| [`Juliet/include/Engine/Class.h`](file:///w:/Classified/Juliet/Juliet/include/Engine/Class.h) | `Class` struct, `MakeClass`, `DECLARE_CLASS()`, `DEFINE_CLASS_VERSIONED`, and `IsA` declarations. |
| [`Juliet/src/Engine/class.cpp`](file:///w:/Classified/Juliet/Juliet/src/Engine/class.cpp) | Universal `serialize(Archive&, NonNullPtr<Class>, void*)` and runtime `IsA` traversal. |
| [`Juliet/src/UnitTest/serialization_test.cpp`](file:///w:/Classified/Juliet/Juliet/src/UnitTest/serialization_test.cpp) | Exhaustive unit tests for tokenization, defaults retention, version migration, and type queries. |
| [`Game/Entity/Entity.h`](file:///w:/Classified/Juliet/Game/Entity/Entity.h) | `DECLARE_ENTITY()`, `DEFINE_ENTITY_VERSIONED`, and `Entity` struct definition. |
| [`Game/Entity/Entity.cpp`](file:///w:/Classified/Juliet/Game/Entity/Entity.cpp) | `serialize_entity` registration and two-tier `serialize(Archive&, NonNullPtr<Entity>)` composition. |
@@ -0,0 +1,818 @@
# Juliet Game Engine Architecture Specification
## Document: 02 - Entity Allocation & In-Place Lifecycle
**Document ID:** JULIET-SPEC-002
**Status:** Approved for Implementation
**Author:** Senior Engine Architect
**Subsystems:** `Game/Entity`, `Game/Data`, `Game/UnitTest`
**Target Files:**
- [`Entity.h`](file:///w:/Classified/Juliet/Game/Entity/Entity.h)
- [`Entity.cpp`](file:///w:/Classified/Juliet/Game/Entity/Entity.cpp)
- [`EntityManager.h`](file:///w:/Classified/Juliet/Game/Entity/EntityManager.h)
- [`EntityManager.cpp`](file:///w:/Classified/Juliet/Game/Entity/EntityManager.cpp)
- [`World.h`](file:///w:/Classified/Juliet/Game/Data/World.h)
- [`World.cpp`](file:///w:/Classified/Juliet/Game/Data/World.cpp)
- [`WorldUnitTest.h`](file:///w:/Classified/Juliet/Game/UnitTest/WorldUnitTest.h)
- [`WorldUnitTest.cpp`](file:///w:/Classified/Juliet/Game/UnitTest/WorldUnitTest.cpp)
---
## 1. Executive Summary & Problem Statement
### 1.1 Background & Context
The Juliet game engine organizes game entities using a hybrid data-oriented architecture:
1. A flat array of **Base Entities** (`Entity`) encapsulating universal properties: unique 64-bit ID, runtime type reflection pointer (`Class* Kind`), spatial coordinates (`X, Y, Z`), and an opaque pointer to the derived payload (`DerivedType Derived`).
2. Type-segregated contiguous arrays of **Derived Entities** (`Inert`, etc.) stored in dedicated per-type memory arenas (`typed_entity_array`).
This design is intended to provide maximum cache efficiency during spatial and general-purpose entity processing, while retaining dense SIMD-friendly streaming for type-specific systems (e.g., transform updates on `Inert` mesh instances).
### 1.2 Root-Cause Analysis of `Game/Data/World.cpp` Loading Flaws
In the initial implementation of entity serialization in [`Game/Data/World.cpp`](file:///w:/Classified/Juliet/Game/Data/World.cpp#L40-L68), loading was fundamentally broken and incomplete:
```cpp
// Existing flawed deserialization in World.cpp
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);
RegisterBaseEntity(entityManager, entity);
}
}
```
This implementation suffers from several fatal defects:
1. **Single-Element Iteration Bug:** It uses `if (type.count > 0)` instead of a loop `for (size_t i = 0; i < type.count; ++i)`, deserializing at most one single entity per type bucket, leaving all subsequent entities in the stream unread and corrupting the archive read offset.
2. **Missing Derived Allocation:** It invokes `RegisterBaseEntity(entityManager, entity)`, which merely pushes the stack-allocated `Entity` into `manager.Entities`. The derived payload arena (`type.arena`) is completely untouched: `type.array` remains null, `type.count` in the manager is desynchronized, and `entity.Derived` remains unassigned or points to an invalid address.
3. **Invalid Pointer in `RegisterEntity`:** In [`Game/Entity/EntityManager.cpp`](file:///w:/Classified/Juliet/Game/Entity/EntityManager.cpp#L46-L66), `RegisterEntity` assigns `base->Derived = entity` before pushing `*base` into `manager.Entities`. The parameter `entity` is a pointer to caller-provided memory (often stack-allocated in helper functions like `MakeEntity`). When `ArenaPushSize` later allocates the persistent derived memory block, `base->Derived` stored inside `manager.Entities.Back()` is **never updated**—it remains dangling, pointing to the transient caller stack!
### 1.3 The "Chicken-and-Egg" Stack Allocation Dilemma
The existing registration API requires a pre-existing derived instance:
```cpp
entity_template* RegisterEntity(EntityManager& manager, Entity* base, DerivedType entity);
```
During runtime programmatic creation via `MakeEntity<T>()`, a temporary instance of `T` is created on the stack and passed by pointer:
```cpp
template <typename EntityType>
EntityType* MakeEntity(EntityManager& manager, float x, float y, float z)
{
EntityType result; // Stack allocation
Entity base; // Stack allocation
base.X = x;
base.Y = y;
base.Z = z;
base.Kind = EntityType::Kind;
return (EntityType*)RegisterEntity(manager, &base, &result);
}
```
When loading an entity from a stream or disk file, **the type is not known at compile time**. The engine reads a runtime type tag (`uint8 kind` or `uint32 CRC`), looks up the reflection metadata (`Class*`), and must instantiate the entity dynamically.
Because C++ does not permit allocating a dynamic struct of unknown type on the stack, and because Juliet strictly forbids heap allocations (`malloc`, `new`, `std::vector`), deserialization cannot construct a temporary instance on the stack to pass to `RegisterEntity`.
This is the classic **chicken-and-egg memory problem**:
- `RegisterEntity` requires an existing instance in memory to copy from.
- Deserialization requires an allocated memory buffer to deserialize into.
### 1.4 Architectural Objectives
This specification establishes a robust in-place lifecycle pipeline that completely eliminates stack temporaries and dynamic heap allocations:
1. **Direct In-Place Allocation:** Introduce `AllocateEntity(EntityManager& manager, Class* classPtr)` which allocates both the base `Entity` and the derived struct directly within their respective engine memory arenas.
2. **Bidirectional Pointer Integrity:** Wire mutual pointers (`base->derived` and `derived->base`) at allocation time before any field deserialization begins.
3. **In-Place Stream Deserialization:** Read class reflection metadata first, invoke `AllocateEntity`, and stream base and derived properties directly into arena-resident memory.
4. **Isolated Entity Assets (`.jasset`):** Transition from a monolithic `world.bin` to a modular one-file-per-entity architecture (`Assets/Entities/{ID}.jasset`).
5. **Dirty Tracking & Optimal Saves:** Introduce an `is_dirty` flag on `Entity` to avoid rewriting unchanged entity files, minimizing disk I/O and eliminating spurious Git repository modifications.
6. **Robust Deletion Lifecycle:** Decouple in-memory removal (`RemoveAtFast` with pointer fixup) from disk synchronization using `World::PendingDeletions`.
---
## 2. The Dual Arena Memory Model in `EntityManager`
### 2.1 The Need for Dual Storage
Game engines execute systems with vastly different cache locality profiles:
- **Spatial / Frustum Culling / Transform Sync:** Iterates every entity in the world, needing only `X, Y, Z`, bounding spheres, and base status flags.
- **Specialized Logic / Render Updates:** Iterates only entities possessing specific components (e.g., `Inert` static meshes requiring instance transform updates to the GPU bindless descriptor table).
Storing large monolithic polymorphic structs in a single array causes severe cache line pollution during spatial passes. Conversely, storing entities in fragmented individual allocations introduces cache misses and pointer-chasing overhead.
Juliet resolves this with a **Dual Arena Memory Model**:
```
+---------------------------------------------------------------------------------------------+
| EntityManager |
+---------------------------------------------------------------------------------------------+
| |
| manager.Entities (VectorArena<Entity, 100'000>) |
| +------------------------------------+------------------------------------+ |
| | Entity 0 (ID=1001, X, Y, Z) | Entity 1 (ID=1002, X, Y, Z) | ... |
| | Derived ------------------------+ | Derived ---------------------+ | |
| +---------------------------------|--+------------------------------|-----+ |
| | | |
| v v |
| manager.by_type[ENTITY(Inert)].arena |
| +------------------------------------+------------------------------------+ |
| | Inert 0 (MeshInstance=4) | Inert 1 (MeshInstance=12) | ... |
| | Base ---------------------------+ | Base ------------------------+ | |
| +---------------------------------|--+------------------------------|-----+ |
| +---------------------------------+ |
+---------------------------------------------------------------------------------------------+
```
### 2.2 `manager.Entities`: Cache-Friendly Base Entity Vector
Base entities reside in a pre-reserved contiguous array:
```cpp
VectorArena<Entity, 100'000> Entities;
```
- **Capacity:** Fixed reserve of 100,000 entities allocated from the `WorldArena`.
- **Memory Footprint:**
$$\text{sizeof(Entity)} = 8\text{ (ID)} + 8\text{ (derived\_kind)} + 8\text{ (derived)} + 16\text{ (position)} + 1\text{ (is\_dirty)} + 7\text{ (Padding)} = 48\text{ bytes}$$
Total reserved space: $100{,}000 \times 48\text{ bytes} \approx 4.8\text{ MB}$.
- **Access Speed:** O(1) random access by index; sequential streaming utilizes L1/L2 hardware prefetchers with zero cache line waste.
### 2.3 `manager.by_type[kind].arena`: Typed Component Arenas
Derived structs reside in per-type contiguous memory arenas:
```cpp
struct typed_entity_array
{
Arena* arena;
entity_template* array;
size_t count;
};
```
- Each `Entity_Type` index owns an isolated `Arena*` allocated during `InitEntityManager`.
- Allocations are packed linearly with alignment specified by `classPtr->alignment`.
- `array` points directly to the first element in the arena, permitting typed array indexing:
```cpp
Inert* inertArray = reinterpret_cast<Inert*>(manager.by_type[ENTITY(Inert)].array);
```
### 2.4 Mutual Back-Pointer Architecture & Invariants
Every entity instance consists of two mutually linked allocations:
1. `basePtr->Derived`: Points from `Entity` in `manager.Entities` to the derived struct in `by_type[kind].arena`.
2. `derivedPtr->Base`: Points from the derived struct (via `DECLARE_ENTITY()`) back to `Entity` in `manager.Entities`.
#### Invariant Rules:
1. **Non-Null Invariant:** For any active entity, `basePtr->Derived != nullptr` and `reinterpret_cast<entity_template*>(basePtr->Derived)->Base == basePtr`.
2. **Type Coherence Invariant:** `basePtr->Kind->kind == derivedTypeId`.
3. **Array Count Invariant:**
$$\sum_{k=0}^{\text{ENTITY(Count)}-1} \text{manager.by\_type}[k].\text{count} == \text{manager.Entities.Size()}$$
### 2.5 Pointer Stability in `VectorArena`
`VectorArena::Create` executes `Reserve(ReserveSize)` on creation:
```cpp
newManager->Entities.Create(world->WorldArena JULIET_DEBUG_PARAM("Entities"));
```
Because capacity ($100{,}000$) is fully reserved upfront in virtual address space, `VectorArena::PushBack` **never reallocates or moves existing memory**. Therefore:
- Pointers to `Entity` elements in `manager.Entities` remain absolutely stable across allocations.
- Derived struct `Base` pointers remain valid indefinitely unless an element is deleted.
- Element removal via swap-and-pop alters memory positions, requiring systematic pointer fixups (addressed in Section 5.2).
---
## 3. The Solution: `AllocateEntity(EntityManager& manager, Class* classPtr)`
### 3.1 Function Signature & Contract
The canonical allocation function is defined in `EntityManager.h`:
```cpp
[[nodiscard]] Entity* AllocateEntity(EntityManager& manager, Class* classPtr);
```
#### Preconditions:
- `classPtr != nullptr`.
- `classPtr->kind < ENTITY(Count)`.
- `classPtr->size_of >= sizeof(entity_template)`.
- `manager.Entities.Size() < manager.Entities.Capacity`.
- `manager.by_type[classPtr->kind].arena != nullptr`.
#### Postconditions:
- 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`.
- 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.
- `derived->base` points to the base `Entity`.
- `base->derived_kind` is assigned to `class_ptr`.
- `base->ID` is initialized to `0` (unassigned; populated by `MakeEntity` or deserialization).
- `base->is_dirty` is initialized to `true`.
- `typed_entity_array::count` is incremented.
- `typed_entity_array::array` is initialized if this is the first entity of this type.
### 3.2 Detailed Step-by-Step Implementation
The implementation replaces the flawed `RegisterEntity` routine in [`Game/Entity/EntityManager.cpp`](file:///w:/Classified/Juliet/Game/Entity/EntityManager.cpp):
```cpp
[[nodiscard]] Entity* AllocateEntity(EntityManager& manager, Class* derivedClassPtr)
{
Assert(derivedClassPtr != nullptr);
Assert(derivedClassPtr->kind < ENTITY(Count));
Assert(derivedClassPtr->size_of >= sizeof(entity_template));
Assert(derivedClassPtr->alignment > 0);
// 1. Allocate Base Entity in the contiguous VectorArena (ID is 0 until assigned by MakeEntity or deserialization)
Entity baseTemplate{};
baseTemplate.ID = 0;
baseTemplate.derived_kind = derivedClassPtr;
baseTemplate.derived = nullptr;
baseTemplate.position = {};
baseTemplate.is_dirty = true;
manager.Entities.PushBack(baseTemplate);
Entity* basePtr = manager.Entities.Back();
Assert(basePtr != nullptr);
// 2. Allocate derived component memory in the typed arena
typed_entity_array& typedArray = manager.by_type[derivedClassPtr->kind];
Assert(typedArray.arena != nullptr);
void* rawMemory = ArenaPushSize(
typedArray.arena,
derivedClassPtr->size_of,
derivedClassPtr->alignment,
false JULIET_DEBUG_PARAM(kEntity_type_names[derivedClassPtr->kind]));
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);
// 4. Establish mutual back-pointers
basePtr->derived = rawMemory;
derivedTemplate->base = basePtr;
// 5. Update typed array tracking
if (typedArray.array == nullptr)
{
typedArray.array = derivedTemplate;
}
typedArray.count += 1;
return basePtr;
}
```
### 3.3 Default Struct Initialization via `Class::default_init_fct`
#### The Problem with Zero-Only Initialization
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
using default_init_fct_type = void (*)(void* payload);
struct Class
{
...
default_init_fct_type default_init_fct = nullptr;
};
```
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
#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);
```
#### 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
Notice the sequence:
1. `manager.Entities.PushBack(baseTemplate)` places the struct at its final, fixed arena address.
2. `base_ptr = manager.Entities.Back()` retrieves the persistent memory pointer.
3. `derived_class_ptr->default_init_fct(raw_memory)` initializes canonical struct defaults.
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.
### 3.5 Updating Type Counts and Array Cache
`typed_entity_array` maintains:
- `typedArray.count`: The exact count of active entities of this type.
- `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 `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
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
template <typename EntityType>
requires EntityConcept<EntityType>
[[nodiscard]] EntityType* MakeEntity(EntityManager& manager, float x, float y, float z)
{
Entity* base_ptr = AllocateEntity(manager, EntityType::kind);
Assert(base_ptr != nullptr);
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 static_cast<EntityType*>(base_ptr->derived);
}
```
---
## 4. In-Place Deserialization Pipeline
### 4.1 Asset Format Specification (`.jasset`)
To achieve robust version control and modular streaming, Juliet adopts a human-readable, Git-diffable **one-file-per-entity** disk format with extension `.jasset`.
```ini
; asset_type
entity_instance
; id
0x0100000000000042
; class
Inert
; version
1
; class_version
1
; position
0.43 0.32 1.56
; mesh_instance
12
```
#### Important: No Header Structs for Derived Types
- **Derived types NEVER require their own file header**: You do **not** write an `InertHeader`, `DoorHeader`, or `PlayerHeader`. Derived types only serialize their own member variables.
- **Universal `; version` + optional `; class_version`**: Every `.jasset` file has a universal `; version` tag. For entity assets, `; version` governs base entity properties (`kEntityBaseVersion`), while an optional `; class_version` governs derived class properties (`Class::Version`). Non-entity assets like `WorldSettings.jasset` only have `; version`.
- **No binary `EntityFileHeader` struct is needed**: Under the `; variable_name\nvalues` text format, there is no packed binary C-struct header at all. The common properties (`; id`, `; class`, `; version`, `; class_version`, `; position`) are standard text Key-Value nodes read by the exact same `Archive` parser.
### 4.2 Eliminating Intermediate Stack Allocations
Under the new pipeline:
1. The `.jasset` text file is read into memory onto a `TempArena` via `LoadFile`.
2. The property nodes are parsed into a `ParsedArchive` via `tokenize_archive(tempArena.Arena, fileBuffer, &ar.base)`.
3. The engine reads `; class` (e.g. `"Inert"`) and resolves `Class* class_ptr = find_class_by_name(class_name)`.
4. `AllocateEntity(manager, class_ptr)` is called immediately. Memory for both base and derived components is allocated in-place in their permanent engine arenas.
5. `serialize(ar, NonNullPtr<Entity>(base_ptr))` is called. Base (`Entity::kind`) and derived (`base_ptr->derived_kind`) fields stream **directly into their permanent memory arenas** without temporary staging buffers or stack copies.
### 4.3 Runtime Class Resolution
To ensure fast and safe type lookup during file deserialization, Juliet provides two tiers of type resolution:
1. **Low-Level Indexed Validation (`ResolveEntityClass`)**: An $O(1)$ array lookup into `kEntity_type_class_ptr[kind]` that validates the class pointer and verifies schema compatibility against `classPtr->CRC`. This is the core validation primitive used by binary streaming, network replication, and internal lookups.
2. **High-Level Name Resolution (`find_class_by_name`)**: Bridges the human-readable text archive format (`; class\nInert`) to the entity class registry. It computes the `crc32` of the parsed class name and queries `ResolveEntityClass` across registered entity kinds.
```cpp
[[nodiscard]] const Class* ResolveEntityClass(uint8 kind, uint32 crc)
{
if (kind >= ENTITY(Count))
{
return nullptr;
}
const Class* class_ptr = kEntity_type_class_ptr[kind];
if (class_ptr == nullptr)
{
return nullptr;
}
if (class_ptr->CRC != crc)
{
return nullptr;
}
return class_ptr;
}
[[nodiscard]] Class* find_class_by_name(String name)
{
if (!IsValid(name))
{
return nullptr;
}
const uint32 name_crc = crc32(name.Str, name.Size);
for (uint8 kind = 0; kind < ENTITY(Count); ++kind)
{
const Class* class_ptr = ResolveEntityClass(kind, name_crc);
if (class_ptr != nullptr)
{
return const_cast<Class*>(class_ptr);
}
}
return nullptr;
}
```
### 4.4 In-Place Deserialization Algorithm
```
+---------------------------------------------------------------------------------------+
| In-Place Deserialization Flowchart |
+---------------------------------------------------------------------------------------+
| |
| 1. LoadFile(scratch.Arena, filepath) into ByteBuffer |
| | |
| v |
| 2. tokenize_archive(scratch.Arena, file_buffer, &ar.base) |
| | |
| v |
| 3. Read "; class" & Resolve Class* via find_class_by_name(class_name) |
| | |
| v |
| 4. base_ptr = AllocateEntity(manager, class_ptr) |
| | |
| +--> [manager.Entities]: Allocates base Entity |
| +--> [manager.by_type[kind].arena]: Allocates derived struct |
| +--> Mutual Back-Pointers Wired In-Place |
| | |
| v |
| 5. serialize(ar, NonNullPtr<Entity>(base_ptr)) |
| | |
| +--> Streams Base Entity (Entity::kind, version, ID, position) |
| +--> Streams Derived Component (base_ptr->derived_kind, class_version) |
| | |
| v |
| 6. Clear Dirty Flag: base_ptr->is_dirty = false |
| |
+---------------------------------------------------------------------------------------+
```
```cpp
[[nodiscard]] Entity* deserialize_entity_in_place(EntityManager& manager, Archive& ar)
{
Assert(ar.loading);
// 1. Read class name and resolve Class*
String class_name = {};
SERIALIZE(ar, class, class_name);
Class* class_ptr = find_class_by_name(class_name);
if (!class_ptr)
{
return nullptr;
}
// 2. Allocate persistent memory for base and derived components in their respective arenas
Entity* base_ptr = AllocateEntity(manager, class_ptr);
Assert(base_ptr != nullptr);
// 3. Serialize Base Entity and Derived in-place (loads ID and properties from disk)
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
base_ptr->is_dirty = false;
return base_ptr;
}
```
---
## 5. Entity Deletion Lifecycle & Disk Synchronization (Extracted for Rework)
> [!WARNING]
> **Status: Extracted for Rework**
> The original swap-and-pop in-memory removal logic (`DestroyEntity`, `RemoveDerivedComponent`, and mutual back-pointer fixups) was determined to be overly complex and has been extracted to [`Game/Plans/Entity_Removal_Brainstorm.md`](file:///w:/Classified/Juliet/Game/Plans/Entity_Removal_Brainstorm.md) for further brainstorming and redesign.
>
> Simpler alternative architectures under consideration include:
> - **Active / Tombstone Flag (`is_active` bool):** Retaining entities in-place without moving memory during frame simulation, eliminating pointer invalidation entirely.
> - **Intrusive Free List:** Linking inactive slots via an intrusive linked list to find the first free slot in $O(1)$ without memory shifting.
> - **Generational Handles / Slot Map:** Enabling safe, non-dangling entity references across systems.
> - **Deferred Compaction:** Batch-compacting memory during level loads or scene transitions rather than per-frame swap-and-pop.
---
## 6. Dirty Tracking for Optimal Saves
### 6.1 The Cost of Naive Monolithic & Full-Directory Writes
In a level containing $10{,}000$ entities:
- **Monolithic `world.bin` Save:** Modifying a single entity's $X$ coordinate requires re-serializing all $10{,}000$ entities and overwriting a multi-megabyte binary file. This introduces a huge Git diff and constant merge conflicts.
- **Full-Directory `.jasset` Save:** Iterating through all $10{,}000$ entities and unconditionally writing $10{,}000$ `.jasset` files incurs massive OS file-system overhead (directory table locks, I/O bandwidth) and changes the file timestamps of every asset. Git reports thousands of modified files even when only one entity changed!
### 6.2 The `is_dirty` Flag on `Entity`
To solve this, `Entity` in [`Game/Entity/Entity.h`](file:///w:/Classified/Juliet/Game/Entity/Entity.h#L28-L36) is augmented with an explicit dirty flag:
```cpp
struct Entity final
{
DECLARE_CLASS() // static Class* kind; (Entity's own Class descriptor)
EntityID ID = 0;
Class* derived_kind = nullptr; // Pointer to derived class descriptor (e.g. Inert::kind)
DerivedType derived = nullptr; // Pointer to derived component memory
Vector4 position = {};
bool is_dirty = false;
};
```
### 6.3 Granular State Transitions
The `is_dirty` flag obeys a strict lifecycle state machine:
```
+-----------------------------------+
| Entity Created |
| (AllocateEntity / Editor) |
+-----------------+-----------------+
|
v
+---------------+
+------->|is_dirty: TRUE |<-------+
| +-------+-------+ |
| | |
Entity Mutated | SaveWorld
(Position, Component) | Completed
| v |
| +---------------+ |
+--------+is_dirty: FALSE+--------+
+-------+-------+
^
|
Deserialization
(LoadWorld / Asset)
```
1. **Entity Creation:** Newly spawned entities in the editor have `is_dirty = true`.
2. **Property Mutation:** Any modification to `position` or derived component payload sets `entity->is_dirty = true`.
3. **Successful Deserialization:** Entities loaded from disk initialize with `is_dirty = false`.
4. **Successful Save:** Upon successfully writing an entity to its `.jasset` file, the engine resets `entity->is_dirty = false`.
### 6.4 Version Control Benefits (Git Friendly Assets)
By coupling the one-file-per-entity `.jasset` format with dirty tracking:
- Only modified entities are touched on disk.
- Git status displays only the exact `.jasset` files that were altered by the designer.
- Team members can work concurrently in the same game scene without encountering binary merge conflicts.
### 6.5 Editor Integration (`RenderWorldEditorUI` Hooks)
In [`Game/Data/World.cpp`](file:///w:/Classified/Juliet/Game/Data/World.cpp#L291-L311), editor UI widgets automatically set the dirty flag upon receiving user input:
```cpp
float pos[4] = { ent.position.x, ent.position.y, ent.position.z, ent.position.w };
if (ImGui::DragFloat3("Position", pos, 0.1f))
{
ent.position.x = pos[0];
ent.position.y = pos[1];
ent.position.z = pos[2];
ent.is_dirty = true; // Mark dirty for persistence
UpdateWorld(world);
}
```
---
## 7. Step-by-Step Implementation Roadmap
### Phase 1: Data Structures & Header Definitions
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`.
- Update `MakeEntity<EntityType>` to assign `base_ptr->ID = EntityManager::ID++;` and delegate allocation and defaults cleanly to `AllocateEntity`.
- Declare `[[nodiscard]] const Class* ResolveEntityClass(uint8 kind, uint32 crc);` and `[[nodiscard]] Class* find_class_by_name(String name);` in `Entity.h`.
2. **Update `EntityManager.h`:**
- Declare `[[nodiscard]] Entity* AllocateEntity(EntityManager& manager, Class* class_ptr);`.
- *(Note: `DestroyEntity` and `RemoveDerivedComponent` deferred to `Entity_Removal_Brainstorm.md`)*
3. **Update `World.h`:**
- Add `VectorArena<EntityID, 1024> PendingDeletions;` to `struct World`.
- Update `SaveWorld` and `LoadWorld` signatures to take directory paths.
### Phase 2: Core Memory Allocation & Wiring in `EntityManager.cpp`
1. Implement `AllocateEntity`:
- Enforce parameter assertions.
- Push to `manager.Entities` with `baseTemplate.ID = 0` (unassigned).
- 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`).
- Increment `typedArray.count` and initialize `typedArray.array`.
2. *(Deferred for rework)* `DestroyEntity` & `RemoveDerivedComponent` (See [`Game/Plans/Entity_Removal_Brainstorm.md`](file:///w:/Classified/Juliet/Game/Plans/Entity_Removal_Brainstorm.md)).
### Phase 3: In-Place Deserialization & Serialization Pipeline
1. In `Entity.cpp`:
- Implement `ResolveEntityClass` and `find_class_by_name`.
- `serialize(Archive& ar, NonNullPtr<Entity> entity)` handles both base and derived class serialization.
2. In `World.cpp`:
- Implement `serialize_entity_asset(Archive& ar, NonNullPtr<Entity> entity, 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
1. In `World.cpp`:
- Implement `ProcessPendingDeletions(World& world, NonNullPtr<Arena> scratchArena)`.
- Implement `SaveWorld(World& world, String worldDirectory)`:
- Process pending deletions.
- Iterate `manager.Entities`, skipping entities where `!entity.is_dirty`.
- Write dirty entities to `.jasset` files and clear `is_dirty`.
- Implement `LoadWorld(World& world, String worldDirectory)`:
- Enumerate `.jasset` files in directory.
- Call `deserialize_entity_asset` for each file.
### Phase 5: Editor Integration
1. In `RenderWorldEditorUI`:
- Hook `ImGui::DragFloat3` and property inspectors to set `is_dirty = true`.
- Hook "Add Entity" button to call `MakeEntity<Inert>(*world.EntityManager, 0.0f, 0.0f, 0.0f)`.
- Hook "Delete Entity" button to call `RemoveWorldEntity(world, selectedEntityId)`.
---
## 8. Unit Testing & Verification Plan
### 8.1 Test Philosophy & Constraints
Following Juliet coding guidelines:
> "When creating a new system framework, make a unit test. To make the unit test we should not modify the framework code for special unit test case."
Testing is isolated in [`Game/UnitTest/WorldUnitTest.cpp`](file:///w:/Classified/Juliet/Game/UnitTest/WorldUnitTest.cpp) and executed during engine initialization in debug builds.
### 8.2 Comprehensive Test Suite (`WorldUnitTest.cpp`)
The test suite validates every guarantee made in this specification:
```cpp
#include <UnitTest/WorldUnitTest.h>
#if JULIET_DEBUG
#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/Memory/MemoryArena.h>
#include <Core/Thread/ThreadContext.h>
#include <Data/World.h>
#include <Entity/Entity.h>
#include <Entity/EntityManager.h>
namespace UnitTest
{
namespace
{
void TestEntityAllocationAndWiring()
{
Log(LogLevel::Message, LogCategory::Game, "[UnitTest] Running TestEntityAllocationAndWiring...");
TempArena tempArena = scratch_begin(nullptr, 0);
World testWorld{};
InitWorld(&testWorld, tempArena.Arena);
InitEntityManager(&testWorld);
EntityManager& manager = *testWorld.EntityManager;
// 1. Allocate Inert Entity via AllocateEntity
Entity* base_entity = AllocateEntity(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<entity_template*>(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);
scratch_end(tempArena);
Log(LogLevel::Message, LogCategory::Game, "[UnitTest] PASSED: TestEntityAllocationAndWiring");
}
void TestInPlaceDeserialization()
{
Log(LogLevel::Message, LogCategory::Game, "[UnitTest] Running TestInPlaceDeserialization...");
TempArena tempArena = scratch_begin(nullptr, 0);
World testWorld{};
InitWorld(&testWorld, tempArena.Arena);
InitEntityManager(&testWorld);
EntityManager& manager = *testWorld.EntityManager;
// 1. Create and populate entity
Inert* created_inert = MakeEntity<Inert>(manager, 12.5f, -44.0f, 108.2f);
Assert(created_inert != nullptr);
created_inert->MeshInstance = 42;
Entity* original_base = created_inert->base;
EntityID original_id = original_base->ID;
// 2. Serialize to text archive memory stream
MemoryStream mem_stream = MakeMemoryStream(tempArena.Arena);
Archive save_ar{ .arena = tempArena.Arena, .loading = false, .stream = &mem_stream };
serialize(save_ar, NonNullPtr<Entity>(original_base));
// 3. Clear manager to simulate fresh load
ShutdownEntityManager();
InitEntityManager(&testWorld);
EntityManager& fresh_manager = *testWorld.EntityManager;
// 4. Tokenize and deserialize in-place
Archive load_ar{ .arena = tempArena.Arena, .loading = true };
tokenize_archive(tempArena.Arena, mem_stream.buffer, &load_ar.base);
Entity* loaded_base = deserialize_entity_in_place(fresh_manager, load_ar);
Assert(loaded_base != nullptr);
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.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);
scratch_end(tempArena);
Log(LogLevel::Message, LogCategory::Game, "[UnitTest] PASSED: TestInPlaceDeserialization");
}
void TestDirtyTrackingLifecycle()
{
Log(LogLevel::Message, LogCategory::Game, "[UnitTest] Running TestDirtyTrackingLifecycle...");
TempArena tempArena = scratch_begin(nullptr, 0);
World testWorld{};
InitWorld(&testWorld, tempArena.Arena);
InitEntityManager(&testWorld);
EntityManager& manager = *testWorld.EntityManager;
Entity* entity = AllocateEntity(manager, Inert::kind);
Assert(entity->is_dirty == true);
// 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);
scratch_end(tempArena);
Log(LogLevel::Message, LogCategory::Game, "[UnitTest] PASSED: TestDirtyTrackingLifecycle");
}
} // namespace
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
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,934 @@
# Juliet Game Engine: Technical Specification & Implementation Plan
# 04: Entity Templates ("Blueprints / Prefabs") & Delta Inheritance
- **Document ID**: ENG-PLAN-04
- **Component**: Game Architecture / Entity Component System / Asset Pipeline / Editor
- **Author**: Senior Engine Architect
- **Status**: Draft / Approved for Implementation
- **Target Engine**: Juliet Engine (Milestone: 3D Platformer / World System)
- **Target File**: `w:\Classified\Juliet\Game\Plans\04_Entity_Templates_And_Inheritance.md`
---
## 1. Executive Summary & Architecture Goals
### 1.1 Motivation & Context
As the Juliet engine evolves toward supporting complex interactive worlds, populating scenes by manually constructing raw entity memory from hardcoded values or monolithic binary streams (`world.bin`) creates severe scalability bottlenecks. Designers and environment artists require reusable entity archetypes—such as foliage, dynamic destructibles, hazards, enemies, and interactive props—that can be authored once and instantiated thousands of times with custom overrides.
In modern commercial engines, this pattern is foundational:
- **Unreal Engine**: Blueprint classes (`.uasset`) serve as class archetypes, instanced as Actors with per-instance component property overrides.
- **Unity**: Prefabs (`.prefab`) serve as asset archetypes, instanced in scenes with explicit serialized property modification lists.
In the **Juliet Game Engine**, this feature is formalized as **Entity Templates**.
### 1.2 Architectural Goals
1. **Unified File Format (`.jasset`)**:
Templates and world entity instances must share the exact same human-readable, version-control-friendly Key-Value (KV) file format. A template is simply an entity `.jasset` file located within `Assets/Templates/`, while a world entity is a `.jasset` file located within `Assets/Worlds/<WorldName>/Entities/` that references a parent template via metadata.
2. **Zero Format Duplication**:
There must be no separate "prefab file format" versus "entity instance file format". The same serialization/deserialization code paths parse both archetypes and instances, completely eliminating format divergence, schema version desynchronization, and redundant parsing logic.
3. **Delta Property Overrides (Sparse Inheritance)**:
Instances only serialize the fields that intentionally deviate from their template archetype. Any property omitted in the instance `.jasset` retains the exact bitwise value defined by the template archetype.
4. **Zero Runtime Inheritance Overhead**:
No virtual table lookups, no runtime inheritance trees, and no pointer chasing at tick time. At instantiation time, the template archetype's memory footprint is blitted into contiguous engine arrays (`EntityManager::by_type`), and instance delta overrides are parsed directly into that memory block. Once instantiated, an entity created from a template executes with identical CPU cache locality and zero performance penalty compared to a hardcoded entity.
5. **Arena-Centric Memory Model**:
All template assets are loaded into a dedicated, isolated `TemplateArena`. World entity instances allocate their runtime state out of `World::WorldArena` and `EntityManager::by_type[kind].arena`. Scratch computations during parsing utilize thread-local scratch arenas (`scratch_begin` / `scratch_end`). No heap allocation (`malloc`, `calloc`, `new`) is permitted.
6. **Strict Engine Conformance**:
- Zero exceptions (`noexcept` by design).
- Strict warning compliance (`-Wall -Wextra -Werror` / `/W4 /WX`).
- Strict explicit casting (`static_cast`, `reinterpret_cast`; C-style casts strictly prohibited).
- Universal `[[nodiscard]]` on all value-returning queries and allocators.
- Consistent naming (`CamelCase` for types, functions, and member variables).
- Comprehensive precondition assertions via `Assert`.
- Mandatory curly braces `{}` across all control flow statements.
---
## 2. The Template Linking Mechanism
### 2.1 File Format Specification (`.jasset`)
All templates and entities in Juliet use the `.jasset` text format. The format consists of:
- **Comment Lines**: Lines starting with `#` or `//` are treated as comments and ignored.
- **Metadata Directives**: Lines starting with `; ` represent engine-level structural metadata (e.g., entity type, template references, versioning).
- **Key-Value Pairs**: Key name followed by a colon and a space (`<Key>: <Value>`).
```ini
# ==============================================================================
# Assets/Templates/RockLarge.jasset
# Entity Archetype: Large Mossy Rock
# ==============================================================================
; entity_type: Inert
Position: 0.0, 0.0, 0.0
MeshAsset: Assets/Meshes/Rock_01.obj
Scale: 1.0, 1.0, 1.0
Mass: 250.0
IsDestructible: true
Health: 100.0
```
### 2.2 The `; template` Directive
When an entity instance is authored for a world, it specifies its archetype via the `; template` directive:
```ini
# ==============================================================================
# Assets/Worlds/Level01/Entities/RockLarge_042.jasset
# Instance: Rock Large #042 in Level 01
# ==============================================================================
; template: Assets/Templates/RockLarge.jasset
Position: 142.5, 12.0, -84.2
Scale: 1.4, 1.4, 1.4
Health: 50.0
```
Notice the power of sparse delta overrides in this format:
- `Position` is set to the instance's unique world coordinates.
- `Scale` is enlarged to $1.4\times$.
- `Health` is damaged down to $50.0$.
- Properties omitted—`MeshAsset`, `Mass`, and `IsDestructible`—are not duplicated in the file. They automatically inherit the authoritative values from `RockLarge.jasset`.
### 2.3 Relative Path vs CRC Identifier
To balance human-readability in source control with blazing runtime lookup speeds:
- **Asset Authoring & Storage**: Files store canonical workspace-relative paths (e.g., `Assets/Templates/RockLarge.jasset`).
- **Runtime Representation**: The engine computes a 32-bit CRC (`crc32`) of the normalized relative path string.
- Runtime lookup into `EntityTemplateCache` operates via `uint32 TemplateCrc`.
- In `JULIET_DEBUG` builds, the original `String TemplatePath` is retained within the cached struct for diagnostic logging, error messages, and inspector UI display.
```cpp
constexpr uint32 kInvalidTemplateCrc = 0;
[[nodiscard]] inline uint32 HashTemplatePath(String path)
{
Assert(IsValid(path));
return crc32(path.Str, path.Size);
}
```
### 2.4 Standalone Entities vs Templated Instances
The engine architecture seamlessly unifies two entity categories:
| Entity Category | `; template` Directive Present? | Memory Initialization Source | Primary Use Case |
| :--- | :--- | :--- | :--- |
| **Standalone Entity** | **No** | Zero-initialized memory block (`ArenaPushStruct` / `MemSet`). All properties must be specified in the instance `.jasset`. | Unique, one-off actors (e.g., Level Script Trigger, Primary Player Spawn Marker, Boss Controller). |
| **Templated Instance** | **Yes** | Blitted directly from `CachedTemplate::DefaultDerivedMemory`. Instance `.jasset` only specifies delta property overrides. | Reusable archetypes (e.g., environmental props, foliage, pickups, enemy minions, projectiles). |
---
## 3. In-Engine Template Caching
### 3.1 Memory Layout & `TemplateArena` Isolation
Reading and parsing text files from storage is orders of magnitude slower than memory copying. In a world containing 10,000 instances of `GrassClump` and 2,000 instances of `RockLarge`, disk I/O and text tokenization must occur **exactly once per archetype**.
To achieve zero memory fragmentation and eliminate dynamic heap allocations:
1. The engine instantiates an isolated `TemplateArena` at subsystem startup.
2. When a template is requested, the system checks the `EntityTemplateCache`.
3. If not cached, the template `.jasset` file is loaded from disk into temporary scratch memory, parsed, and baked into a contiguous binary archetype memory snapshot inside `TemplateArena`.
4. The parsed archetype snapshot remains resident in `TemplateArena` for the lifetime of the application or world session.
```
+---------------------------------------------------------------------------------+
| TemplateArena |
+---------------------------------------------------------------------------------+
| [CachedTemplate #0] |
| - TemplateCrc: 0x9A4B12F0 ("Assets/Templates/RockLarge.jasset") |
| - EntityKind: Pointer to Inert::Kind |
| - DefaultBase: { ID=0, Kind=Inert::Kind, Derived=nullptr, X=0, Y=0, Z=0 } |
| - DefaultDerivedMemory: [ sizeof(Inert) binary snapshot: MeshInstance=... ] |
+---------------------------------------------------------------------------------+
| [CachedTemplate #1] |
| - TemplateCrc: 0x4D2E88C1 ("Assets/Templates/CoinPickup.jasset") |
| - EntityKind: Pointer to Collectible::Kind |
| - DefaultBase: { ID=0, Kind=Collectible::Kind, ... } |
| - DefaultDerivedMemory: [ sizeof(Collectible) binary snapshot: Value=100 ] |
+---------------------------------------------------------------------------------+
| ... Free Arena Capacity for Additional Archetypes ... |
+---------------------------------------------------------------------------------+
```
### 3.2 Data Structures
The template caching infrastructure is defined with explicit, strictly-typed C++ structures adhering to Juliet guidelines:
```cpp
#pragma once
#include <Core/Common/CoreTypes.h>
#include <Core/Common/CoreUtils.h>
#include <Core/Common/NonNullPtr.h>
#include <Core/Common/String.h>
#include <Core/Container/Vector.h>
#include <Core/Memory/MemoryArena.h>
#include <Engine/Class.h>
#include <Entity/Entity.h>
struct CachedTemplate
{
uint32 TemplateCrc = 0;
Class* EntityKind = nullptr;
Entity DefaultBase = {};
void* DefaultDerivedMemory = nullptr;
size_t DerivedMemorySize = 0;
#if JULIET_DEBUG
String SourcePath = {};
#endif
};
constexpr size_t kMaxCachedTemplates = 1024;
struct EntityTemplateCache
{
Arena* CacheArena = nullptr;
VectorArena<CachedTemplate, kMaxCachedTemplates> Templates;
};
// Subsystem API
[[nodiscard]] EntityTemplateCache* InitEntityTemplateCache(NonNullPtr<Arena> parentArena);
void ShutdownEntityTemplateCache(NonNullPtr<EntityTemplateCache> cache);
[[nodiscard]] CachedTemplate* GetOrLoadTemplate(NonNullPtr<EntityTemplateCache> cache, String relativePath);
[[nodiscard]] CachedTemplate* FindCachedTemplate(NonNullPtr<EntityTemplateCache> cache, uint32 templateCrc);
void InvalidateTemplateCache(NonNullPtr<EntityTemplateCache> cache);
```
### 3.3 Loading Pipeline: Disk to Archetype Memory Snapshot
When `GetOrLoadTemplate` is invoked with a relative path:
1. **Hash & Probe**: Compute `crc32` of `relativePath`. Search `cache->Templates` for an existing entry. If found, immediately return the cached pointer ($O(1)$ amortized).
2. **Scratch Allocation**: Open a scratch arena frame (`TempArena scratch = scratch_begin(nullptr, 0);`).
3. **Disk I/O**: Resolve the path via `GetAssetPath(scratch.Arena, relativePath)` and load the entire file into a raw byte buffer via `LoadFile(scratch.Arena, fullPath)`.
- Precondition check: `Assert(fileBuffer.Data != nullptr);`
4. **Header Parse**: Scan the file buffer for `; entity_type: <TypeName>`. Look up the corresponding `Class*` via the global entity registry (`kEntity_type_class_ptr`).
5. **Archetype Memory Allocation**: Allocate `DefaultDerivedMemory` directly out of `cache->CacheArena`:
```cpp
void* archetypeMem = ArenaPushSize(cache->CacheArena,
entityClass->size_of,
entityClass->alignment,
true JULIET_DEBUG_PARAM("CachedTemplateDerived"));
```
6. **Default Base Setup**: Initialize a local `Entity defaultBase`:
```cpp
Entity defaultBase = {};
defaultBase.derived_kind = entityClass;
defaultBase.derived = archetypeMem;
```
7. **Back-Pointer Linking**: Set `entity_template::base` in the archetype memory:
```cpp
auto* archetypeTemplate = reinterpret_cast<entity_template*>(archetypeMem);
archetypeTemplate->base = &cachedEntry->DefaultBase;
```
8. **KV Property Parsing**: Parse all key-value pairs in the template `.jasset` file and write their deserialized values directly into `archetypeMem` and `defaultBase`.
9. **Cache Insertion**: Store the fully baked `CachedTemplate` record in `cache->Templates`.
10. **Scratch Release**: Release the temporary file buffer (`scratch_end(scratch);`).
---
## 4. Instantiation & Delta Property Overrides
### 4.1 Memory Architecture: Base vs. Derived Entities
In Juliet, an entity is split into two tightly coupled structures:
1. **`Entity` (Base)**: Contains universal spatial and lifecycle fields:
```cpp
struct Entity final
{
DECLARE_CLASS()
EntityID ID = 0;
Class* derived_kind = nullptr;
DerivedType derived = nullptr; // Points to the specialized struct
Vector4 position = {};
bool is_dirty = false;
};
```
2. **`Derived` (Specialized Type)**: e.g., `Inert`, `Collectible`, `Player`. The first member is always `DECLARE_ENTITY()`, which expands to:
```cpp
Entity* base; // Back-pointer to the base Entity
DECLARE_CLASS() // static Class* kind;
```
Because `DerivedType` stores a back-pointer (`base`) to `Entity`, **a shallow memory copy of an archetype invalidates this pointer**! The loading pipeline must explicitly restore this invariant.
### 4.2 The 5-Step Instantiation Pipeline
When an entity instance is spawned or deserialized from a `.jasset` file, the engine executes this strict 5-step sequence:
```
[ Step 1: Pre-allocate ]
- manager.Entities.PushBack(Entity{})
- ArenaPushSize(manager.by_type[Kind].arena)
|
v
[ Step 2: Copy Archetype Memory ]
- MemCopy(instanceDerivedMem, template->DefaultDerivedMemory, size_of)
- instanceBase->X/Y/Z = template->DefaultBase.X/Y/Z
|
v
[ Step 3: Fixup Base Back-Pointer ] <--- CRITICAL ARCHITECTURAL STEP
- instanceDerived->Base = instanceBase
- instanceBase->Derived = instanceDerived
|
v
[ Step 4: Parse Instance KV Delta ]
- Scan instance .jasset tokens
- Match each key to struct property offset
- Overwrite only specified properties
|
v
[ Step 5: Finalize & Post-Init ]
- Assign Unique EntityID
- Register with Render/Physics systems
```
#### Step-by-Step Code Execution
```cpp
[[nodiscard]] Entity* InstantiateEntityFromTemplate(EntityManager& manager,
NonNullPtr<const CachedTemplate> templateArchetype,
NonNullPtr<Arena> scratchArena,
String instanceKvContent)
{
Assert(templateArchetype->EntityKind != nullptr);
Assert(templateArchetype->DefaultDerivedMemory != nullptr);
Class* entityKind = templateArchetype->EntityKind;
const size_t derivedSize = entityKind->size_of;
const size_t derivedAlign = entityKind->alignment;
// -------------------------------------------------------------------------
// STEP 1: Pre-allocate instance in EntityManager
// -------------------------------------------------------------------------
Entity baseEntity = {};
baseEntity.ID = EntityManager::ID++;
baseEntity.Kind = entityKind;
// Push into flat base vector
manager.Entities.PushBack(baseEntity);
Entity* instanceBase = manager.Entities.Back();
Assert(instanceBase != nullptr);
// Allocate memory in the typed array arena
auto* instanceDerived = reinterpret_cast<entity_template*>(
ArenaPushSize(manager.by_type[entityKind->kind].arena,
derivedSize,
derivedAlign,
false JULIET_DEBUG_PARAM(kEntity_type_names[entityKind->kind]))
);
Assert(instanceDerived != nullptr);
// Track array head if first element
if (manager.by_type[entityKind->kind].array == nullptr)
{
manager.by_type[entityKind->kind].array = instanceDerived;
}
manager.by_type[entityKind->kind].count += 1;
// -------------------------------------------------------------------------
// STEP 2: Copy cached template defaults into instance derived memory
// -------------------------------------------------------------------------
MemCopy(instanceDerived, templateArchetype->DefaultDerivedMemory, derivedSize);
// Inherit base spatial defaults
instanceBase->X = templateArchetype->DefaultBase.X;
instanceBase->Y = templateArchetype->DefaultBase.Y;
instanceBase->Z = templateArchetype->DefaultBase.Z;
// -------------------------------------------------------------------------
// STEP 3: Ensure derived back-pointer points to THIS instance's base
// -------------------------------------------------------------------------
instanceDerived->Base = instanceBase;
instanceBase->Derived = instanceDerived;
// -------------------------------------------------------------------------
// STEP 4: Parse instance .jasset KV nodes over the memory
// -------------------------------------------------------------------------
if (IsValid(instanceKvContent))
{
ApplyKvDeltaOverrides(instanceBase, instanceDerived, entityKind, instanceKvContent);
}
// -------------------------------------------------------------------------
// STEP 5: Post-Instantiation Initialization
// -------------------------------------------------------------------------
// If the entity is an Inert mesh, update its graphics transform
if (entityKind->kind == ENTITY(Inert))
{
auto* inert = reinterpret_cast<Inert*>(instanceDerived);
if (inert->MeshInstance != indexMax)
{
SetMeshInstanceTransform(inert->MeshInstance,
MatrixTranslation(instanceBase->X, instanceBase->Y, instanceBase->Z));
}
}
return instanceBase;
}
```
### 4.3 Key-Value Parsing & Reflection Binding
To apply delta overrides, the engine maps parsed string keys to memory offsets. Juliet utilizes lightweight property reflection metadata registered on each `Class`:
```cpp
enum class PropertyType : uint8
{
Float,
Int32,
Bool,
Vector3,
MeshAsset,
String
};
struct PropertyDescriptor
{
String Name;
size_t Offset;
PropertyType Type;
bool IsBaseProperty; // True if located on Entity, false if on Derived
};
void ApplyKvDeltaOverrides(Entity* base, void* derived, Class* cls, String kvContent)
{
Assert(base != nullptr);
Assert(derived != nullptr);
Assert(cls != nullptr);
TempArena scratch = scratch_begin(nullptr, 0);
KvParser parser = InitKvParser(kvContent);
KvPair pair = {};
while (NextKvPair(&parser, &pair))
{
// Directives like '; template' or '; entity_type' are skipped
if (pair.Key.Size > 0 && pair.Key.Str[0] == ';')
{
continue;
}
// Check base properties first
if (StringCompare(pair.Key, ConstString("Position")) == 0 || StringCompare(pair.Key, ConstString("position")) == 0)
{
Vector3 pos = ParseVector3(pair.Value);
base->position.x = pos.x;
base->position.y = pos.y;
base->position.z = pos.z;
continue;
}
// Look up property in Class reflection table
const PropertyDescriptor* prop = FindPropertyDescriptor(cls, pair.Key);
if (prop != nullptr)
{
void* targetField = static_cast<uint8*>(derived) + prop->Offset;
switch (prop->Type)
{
case PropertyType::Float:
{
*reinterpret_cast<float*>(targetField) = ParseFloat(pair.Value);
break;
}
case PropertyType::Int32:
{
*reinterpret_cast<int32*>(targetField) = ParseInt32(pair.Value);
break;
}
case PropertyType::Bool:
{
*reinterpret_cast<bool*>(targetField) = ParseBool(pair.Value);
break;
}
case PropertyType::MeshAsset:
{
String meshPath = TrimWhitespace(pair.Value);
MeshAssetID meshId = LoadMesh(meshPath);
*reinterpret_cast<MeshAssetID*>(targetField) = meshId;
break;
}
default:
{
break;
}
}
}
}
scratch_end(scratch);
}
```
---
## 5. Editor Workflow & Operations (Romeo / ImGui)
### 5.1 "Create Template from Entity" Workflow
An artist or designer often crafts an intricate entity in the active level (configuring mesh, collider, and scale) and decides it should become a reusable archetype.
#### Sequence Diagram / Workflow:
1. **User Action**: Right-click an entity in the Romeo World Editor Outliner $\rightarrow$ Select *"Convert to Template..."*.
2. **Modal Dialog**: The editor prompts for the template asset name (e.g., `SpikeTrap_Large`).
3. **Sanitize World Transform**:
- The world position (`X, Y, Z`) is sanitized to origin (`0.0, 0.0, 0.0`) for the template asset.
- Rotations and local scale are preserved.
4. **Serialize Archetype**:
- Write `Assets/Templates/SpikeTrap_Large.jasset` containing:
- `; entity_type: <KindName>`
- All authored property values.
5. **Convert Live Instance**:
- The selected world entity is transformed into an instance of the newly created template.
- The entity is assigned the template CRC: `entity->TemplateCrc = crc32("Assets/Templates/SpikeTrap_Large.jasset")`.
- When the world is saved, this entity serializes as a sparse delta referencing the template!
```cpp
#if JULIET_EDITOR
bool CreateTemplateFromEntity(World& world,
size_t entityIndex,
String templateName,
NonNullPtr<Arena> scratchArena)
{
auto& manager = *world.EntityManager;
Assert(entityIndex < manager.Entities.Size());
Entity* sourceEntity = &manager.Entities[entityIndex];
Class* entityKind = sourceEntity->derived_kind;
void* derivedMem = sourceEntity->derived;
// Format destination template path
String templatePath = Format(scratchArena, "Assets/Templates/{}.jasset", CStr(templateName));
String fullDiskPath = GetAssetPath(scratchArena, templatePath);
// Open IOStream for write
IOStream* fileStream = IOFromFile(scratchArena, fullDiskPath, ConstString("wb"));
if (fileStream == nullptr)
{
LogError(LogCategory::Game, "Failed to open file for template creation: %s", CStr(fullDiskPath));
return false;
}
// Write Header Directive
IOPrintf(fileStream, "; entity_type: %s\n\n", kEntity_type_names[entityKind->kind]);
// Write Origin Position
IOPrintf(fileStream, "Position: 0.0, 0.0, 0.0\n");
// Write Derived Properties via Reflection Table
SerializeDerivedPropertiesToKv(fileStream, entityKind, derivedMem);
IOClose(fileStream);
// Register with in-engine cache immediately
auto* templateCache = GetGameState()->TemplateCache;
if (templateCache != nullptr)
{
(void)GetOrLoadTemplate(templateCache, templatePath);
}
LogMessage(LogCategory::Game, "Successfully created template: %s", CStr(templatePath));
return true;
}
#endif
```
### 5.2 "Spawn Instance from Template" Workflow
1. In the Romeo Content Browser, browse `Assets/Templates/`.
2. Drag a `.jasset` template into the 3D viewport, or click *"Spawn Template"* in the World Editor toolbar.
3. The viewport raycasts against the collision mesh/floor to compute `hitPosition`.
4. The editor calls:
```cpp
CachedTemplate* archetype = GetOrLoadTemplate(templateCache, templatePath);
Entity* newInstance = InstantiateEntityFromTemplate(manager, archetype, scratchArena, {});
newInstance->X = hitPosition.X;
newInstance->Y = hitPosition.Y;
newInstance->Z = hitPosition.Z;
```
5. The entity is immediately live, selectable, and rendered.
### 5.3 Inspector Delta Highlighting & Property Diffing
To make template inheritance intuitive, the inspector visually flags overridden properties:
- **Default Property**: Rendered in standard gray text.
- **Overridden Property**: Rendered in **bold bright cyan** with an undo/revert icon button `[R]`.
```cpp
#if JULIET_EDITOR
void RenderEntityPropertyInspector(Entity* entity, CachedTemplate* archetype)
{
Assert(entity != nullptr);
const bool isTemplated = (archetype != nullptr);
ImGui::Text("Entity ID: %llu", entity->ID);
if (isTemplated)
{
ImGui::TextColored(ImVec2(0.4f, 0.8f, 1.0f, 1.0f), "Template: %s", archetype->SourcePath.Str);
ImGui::SameLine();
if (ImGui::SmallButton("Revert All to Template"))
{
RevertEntityToTemplate(entity, archetype);
}
ImGui::Separator();
}
// Iterate through properties
Class* cls = entity->derived_kind;
for (size_t i = 0; i < cls->PropertyCount; ++i)
{
const PropertyDescriptor& prop = cls->Properties[i];
void* instanceField = static_cast<uint8*>(entity->derived) + prop.Offset;
void* templateField = isTemplated ? (static_cast<uint8*>(archetype->DefaultDerivedMemory) + prop.Offset) : nullptr;
const bool isOverridden = isTemplated && (MemCompare(instanceField, templateField, GetPropertySize(prop.Type)) != 0);
if (isOverridden)
{
ImGui::PushStyleColor(ImGuiCol_Text, ImVec2(0.2f, 1.0f, 1.0f, 1.0f));
}
// Render editor widget (DragFloat, InputText, etc.)
RenderPropertyWidget(prop, instanceField);
if (isOverridden)
{
ImGui::PopStyleColor();
ImGui::SameLine();
ImGui::PushID(static_cast<int>(i));
if (ImGui::SmallButton("R"))
{
// Revert this single property
MemCopy(instanceField, templateField, GetPropertySize(prop.Type));
}
ImGui::PopID();
if (ImGui::IsItemHovered())
{
ImGui::SetTooltip("Revert property to template default");
}
}
}
}
#endif
```
### 5.4 "Revert to Template" Mechanics
Reverting an entire entity to its template archetype restores all properties while strictly preserving world positioning and the instance's unique `EntityID`:
```cpp
void RevertEntityToTemplate(NonNullPtr<Entity> instance, NonNullPtr<const CachedTemplate> archetype)
{
Assert(instance->derived_kind == archetype->EntityKind);
Assert(archetype->DefaultDerivedMemory != nullptr);
void* derivedMem = instance->derived;
const size_t derivedSize = archetype->EntityKind->size_of;
// Preserve the current Base pointer
Entity* basePtr = instance.Get();
// 1. Re-copy the archetype defaults
MemCopy(derivedMem, archetype->DefaultDerivedMemory, derivedSize);
// 2. Re-establish the Base back-pointer
auto* templateDerived = reinterpret_cast<entity_template*>(derivedMem);
templateDerived->base = basePtr;
// 3. Mark visual / physics state as updated
if (instance->derived_kind->kind == ENTITY(Inert))
{
auto* inert = reinterpret_cast<Inert*>(derivedMem);
if (inert->MeshInstance != indexMax)
{
SetMeshInstanceTransform(inert->MeshInstance,
MatrixTranslation(basePtr->position.x, basePtr->position.y, basePtr->position.z));
}
}
}
```
---
## 6. Step-by-Step Implementation Roadmap
```
+-----------------------------------------------------------------------------+
| Phase 1: Core KV Parser & File Serialization Architecture |
| - Fast, zero-allocation Key-Value streaming parser |
| - Property reflection table integration on Class struct |
+-----------------------------------------------------------------------------+
|
v
+-----------------------------------------------------------------------------+
| Phase 2: Template Cache Subsystem (`EntityTemplateCache`) |
| - Dedicated TemplateArena initialization |
| - Archetype loading, CRC hashing, and resident memory snapshot baking |
+-----------------------------------------------------------------------------+
|
v
+-----------------------------------------------------------------------------+
| Phase 3: Instantiation & Delta Overrides in `EntityManager` |
| - Implement 5-Step Instantiation Pipeline |
| - Derived back-pointer fixup validation |
| - Sparse delta serialization for world saving |
+-----------------------------------------------------------------------------+
|
v
+-----------------------------------------------------------------------------+
| Phase 4: Romeo Editor UI & Inspector Workflow |
| - Template browser window in ImGui |
| - "Create Template from Entity" context menu |
| - Inspector delta highlighting and per-property "Revert" action |
+-----------------------------------------------------------------------------+
|
v
+-----------------------------------------------------------------------------+
| Phase 5: Production Verification & Non-Invasive Unit Testing Suite |
| - Comprehensive test cases for cache, delta inheritance, and memory safety |
+-----------------------------------------------------------------------------+
```
### Detailed Milestone Tasks
| Phase | Target Files | Objective / Deliverable | Success Criteria |
| :--- | :--- | :--- | :--- |
| **Phase 1** | `Juliet/include/Engine/KvParser.h`<br>`Juliet/src/Engine/KvParser.cpp`<br>`Juliet/include/Engine/Class.h` | Build a zero-allocation streaming Key-Value parser operating entirely over `String` slices and `Arena*`. Add property descriptor tables to `Class`. | Parses full `.jasset` buffer in $<50\mu\text{s}$ without dynamic allocations. Zero exceptions. |
| **Phase 2** | `Game/Entity/TemplateCache.h`<br>`Game/Entity/TemplateCache.cpp` | Implement `EntityTemplateCache`, `CachedTemplate`, and on-demand disk loader backed by `TemplateArena`. | Repeated loads of same template return cached pointer in $O(1)$ time without disk reads. |
| **Phase 3** | `Game/Entity/EntityManager.h`<br>`Game/Entity/EntityManager.cpp`<br>`Game/Data/World.cpp` | Implement `InstantiateEntityFromTemplate` using the 5-step pipeline. Update world save/load to serialize sparse deltas when `; template` is present. | Instantiated entities correctly retain archetype defaults while applying deltas. `Base` pointer is guaranteed valid. |
| **Phase 4** | `Game/Data/World.cpp`<br>`Game/Debug/WorldEditorUI.cpp` | Add ImGui widgets for template authoring, template instantiation drag-and-drop, delta highlighting, and "Revert" actions. | Designer can convert an entity to a template and revert modified properties with instant visual update. |
| **Phase 5** | `Game/UnitTest/TemplateUnitTest.h`<br>`Game/UnitTest/TemplateUnitTest.cpp` | Author non-invasive unit tests validating KV parsing, caching, delta override correctness, back-pointer fixup, and revert operations. | 100% test pass rate with zero memory leaks and all assertions satisfied under `/WX`. |
---
## 7. Production-Grade Unit Testing Plan
### 7.1 Testing Philosophy & Non-Invasive Framework Rules
In accordance with Juliet engine guidelines:
- **No framework pollution**: Unit tests must not introduce test-only `#ifdef` branches or dummy parameters into production engine systems.
- **Isolated test arenas**: All tests create their own temporary scratch arena or sub-arena and release it upon completion.
- **Deterministic verification**: Every assumption (data alignment, pointer fixups, delta override values) is verified through explicit `Assert` statements.
### 7.2 Header Specification (`Game/UnitTest/TemplateUnitTest.h`)
```cpp
#pragma once
#include <Juliet.h>
#if JULIET_DEBUG
namespace UnitTest
{
void RunTemplateAndInheritanceUnitTests();
}
#endif
```
### 7.3 Complete Unit Test Suite Implementation (`Game/UnitTest/TemplateUnitTest.cpp`)
```cpp
#include <UnitTest/TemplateUnitTest.h>
#if JULIET_DEBUG
#include <Core/Common/CoreUtils.h>
#include <Core/Logging/LogManager.h>
#include <Core/Memory/MemoryArena.h>
#include <Core/Thread/ThreadContext.h>
#include <Data/World.h>
#include <Entity/Entity.h>
#include <Entity/EntityManager.h>
#include <Entity/TemplateCache.h>
namespace UnitTest
{
// =========================================================================
// Test 1: Key-Value Parser Verification
// =========================================================================
static void TestKvParser(NonNullPtr<Arena> testArena)
{
LogMessage(LogCategory::Game, "[UnitTest] Starting TestKvParser...");
const char* sampleKv =
"; entity_type: Inert\n"
"# This is a comment\n"
"Position: 10.5, -20.0, 30.25\n"
"Scale: 2.0, 2.0, 2.0\n"
"MeshAsset: Assets/Meshes/Cube.obj\n"
"IsActive: true\n";
KvParser parser = InitKvParser(WrapString(sampleKv));
KvPair pair = {};
// 1. Directive
Assert(NextKvPair(&parser, &pair));
Assert(StringCompare(pair.Key, ConstString("; entity_type")) == 0);
Assert(StringCompare(pair.Value, ConstString("Inert")) == 0);
// 2. Position
Assert(NextKvPair(&parser, &pair));
Assert(StringCompare(pair.Key, ConstString("Position")) == 0);
Vector3 pos = ParseVector3(pair.Value);
Assert(pos.X == 10.5f);
Assert(pos.Y == -20.0f);
Assert(pos.Z == 30.25f);
// 3. Scale
Assert(NextKvPair(&parser, &pair));
Assert(StringCompare(pair.Key, ConstString("Scale")) == 0);
// 4. MeshAsset
Assert(NextKvPair(&parser, &pair));
Assert(StringCompare(pair.Key, ConstString("MeshAsset")) == 0);
Assert(StringCompare(pair.Value, ConstString("Assets/Meshes/Cube.obj")) == 0);
// 5. IsActive
Assert(NextKvPair(&parser, &pair));
Assert(StringCompare(pair.Key, ConstString("IsActive")) == 0);
Assert(ParseBool(pair.Value) == true);
// End of stream
Assert(!NextKvPair(&parser, &pair));
LogMessage(LogCategory::Game, "[UnitTest] TestKvParser PASSED.");
}
// =========================================================================
// Test 2: Template Cache & Single-Load Invariant
// =========================================================================
static void TestTemplateCache(NonNullPtr<Arena> testArena)
{
LogMessage(LogCategory::Game, "[UnitTest] Starting TestTemplateCache...");
EntityTemplateCache* cache = InitEntityTemplateCache(testArena);
Assert(cache != nullptr);
String templatePath = ConstString("Assets/Templates/TestRock.jasset");
// First load: loads and caches
CachedTemplate* firstLoad = GetOrLoadTemplate(cache, templatePath);
Assert(firstLoad != nullptr);
Assert(firstLoad->TemplateCrc == HashTemplatePath(templatePath));
Assert(firstLoad->EntityKind == Inert::Kind);
// Second load: must return identical cached pointer without re-allocating
CachedTemplate* secondLoad = GetOrLoadTemplate(cache, templatePath);
Assert(secondLoad == firstLoad);
// Verify lookup by CRC
CachedTemplate* crcLookup = FindCachedTemplate(cache, firstLoad->TemplateCrc);
Assert(crcLookup == firstLoad);
ShutdownEntityTemplateCache(cache);
LogMessage(LogCategory::Game, "[UnitTest] TestTemplateCache PASSED.");
}
// =========================================================================
// Test 3: 5-Step Instantiation Pipeline & Derived Back-Pointer Fixup
// =========================================================================
static void TestInstantiationPipeline(NonNullPtr<Arena> testArena)
{
LogMessage(LogCategory::Game, "[UnitTest] Starting TestInstantiationPipeline...");
// Setup mock World and EntityManager
World world = {};
InitWorld(&world, testArena);
InitEntityManager(&world);
auto& manager = *world.EntityManager;
// Construct a synthetic template archetype
CachedTemplate mockTemplate = {};
mockTemplate.TemplateCrc = 0xABCD1234;
mockTemplate.EntityKind = Inert::Kind;
mockTemplate.DefaultBase.X = 1.0f;
mockTemplate.DefaultBase.Y = 2.0f;
mockTemplate.DefaultBase.Z = 3.0f;
Inert defaultInert = {};
defaultInert.MeshInstance = 42; // Template default
mockTemplate.DefaultDerivedMemory = &defaultInert;
mockTemplate.DerivedMemorySize = sizeof(Inert);
// Delta content overrides Position and leaves MeshInstance unspecified
String instanceKv = ConstString("Position: 100.0, 200.0, 300.0\n");
Entity* instance = InstantiateEntityFromTemplate(manager, &mockTemplate, testArena, instanceKv);
Assert(instance != nullptr);
// Verify Step 1 & 2: Base spatial delta applied, non-overridden derived property retained
Assert(instance->position.x == 100.0f);
Assert(instance->position.y == 200.0f);
Assert(instance->position.z == 300.0f);
auto* inertDerived = DownCast<Inert>(instance);
Assert(inertDerived != nullptr);
Assert(inertDerived->MeshInstance == 42); // Retained from template!
// Verify Step 3: CRITICAL back-pointer fixup check
Assert(inertDerived->base == instance);
Assert(instance->derived == inertDerived);
ShutdownEntityManager();
ShutdownWorld(&world);
LogMessage(LogCategory::Game, "[UnitTest] TestInstantiationPipeline PASSED.");
}
// =========================================================================
// Test 4: Delta Override & Revert Functionality
// =========================================================================
static void TestDeltaOverrideAndRevert(NonNullPtr<Arena> testArena)
{
LogMessage(LogCategory::Game, "[UnitTest] Starting TestDeltaOverrideAndRevert...");
World world = {};
InitWorld(&world, testArena);
InitEntityManager(&world);
auto& manager = *world.EntityManager;
CachedTemplate mockTemplate = {};
mockTemplate.TemplateCrc = 0x11223344;
mockTemplate.EntityKind = Inert::Kind;
mockTemplate.DefaultBase.X = 0.0f;
mockTemplate.DefaultBase.Y = 0.0f;
mockTemplate.DefaultBase.Z = 0.0f;
Inert defaultInert = {};
defaultInert.MeshInstance = 100;
mockTemplate.DefaultDerivedMemory = &defaultInert;
mockTemplate.DerivedMemorySize = sizeof(Inert);
// Instantiate with delta
String instanceKv = ConstString("MeshInstance: 999\nPosition: 5.0, 5.0, 5.0\n");
Entity* instance = InstantiateEntityFromTemplate(manager, &mockTemplate, testArena, instanceKv);
auto* inertDerived = DownCast<Inert>(instance);
Assert(inertDerived->MeshInstance == 999); // Delta applied
// Execute Revert to Template
RevertEntityToTemplate(instance, &mockTemplate);
// Verify property restored to template default
Assert(inertDerived->MeshInstance == 100);
// Verify world position is preserved across revert
Assert(instance->position.x == 5.0f);
Assert(instance->position.y == 5.0f);
Assert(instance->position.z == 5.0f);
// Verify back-pointer invariant preserved after revert
Assert(inertDerived->base == instance);
ShutdownEntityManager();
ShutdownWorld(&world);
LogMessage(LogCategory::Game, "[UnitTest] TestDeltaOverrideAndRevert PASSED.");
}
// =========================================================================
// Master Runner
// =========================================================================
void RunTemplateAndInheritanceUnitTests()
{
TempArena scratch = scratch_begin(nullptr, 0);
TestKvParser(scratch.Arena);
TestTemplateCache(scratch.Arena);
TestInstantiationPipeline(scratch.Arena);
TestDeltaOverrideAndRevert(scratch.Arena);
scratch_end(scratch);
LogMessage(LogCategory::Game, "[UnitTest] All Entity Template & Inheritance tests PASSED successfully.");
}
}
#endif
+273
View File
@@ -0,0 +1,273 @@
# Entity Removal & Deletion Lifecycle (Brainstorm & Rework)
> [!WARNING]
> **Status: Needs Rework**
> The entity deletion and removal logic below was extracted as-is from [`Game/Plans/02_Entity_Allocation_And_Lifecycle.md`](file:///w:/Classified/Juliet/Game/Plans/02_Entity_Allocation_And_Lifecycle.md) for future brainstorming.
> The original swap-and-pop approach required complex mutual back-pointer fixups across both base and derived memory buffers.
---
## 1. Brainstorming Notes & Alternative Approaches
The original design relied on `VectorArena::RemoveAtFast` (swap-and-pop) for both `Entities` and `by_type[kind].arena`. While this maintained dense contiguous memory, swapping arbitrary elements in memory invalidated pointers in both directions, requiring runtime pointer patching (`derived->Base = movedEntity; base->Derived = movedComponent`).
### Ideas for Simpler & More Robust Alternatives:
1. **Active Flag / Tombstone (`is_active` bool)**:
- Keep deleted entities in place rather than moving or swapping them.
- Simply toggle `entity->is_active = false;`.
- Iterators and simulation loops skip inactive entities (`if (!entity->is_active) continue;`).
- Pointers to entities and derived components remain stable for their entire lifetime.
2. **Intrusive Free List for Slot Recycling**:
- Instead of shifting memory on deletion, vacant slots are linked into an intrusive free list (`first_free_index`).
- When an entity is destroyed:
- `entity->is_active = false;`
- Overwrite unused slot memory with `next_free_index`.
- When allocating a new entity:
- Check if `first_free_index != indexMax`.
- Pop from the free list in $O(1)$; otherwise push back to the end of the arena.
- Zero pointer invalidation, zero memmove/memcpy overhead during deletion.
3. **Stable Generational Handles / Slot Map**:
- If external systems need references to entities that might be deleted, combine the free list with a generational counter to detect stale lookups safely.
4. **Deferred Compaction / Garbage Collection**:
- If contiguous packing is strictly necessary for SIMD/cache optimization, defer compaction to a designated level load or scene transition rather than doing it per-frame on individual deletes.
---
## 2. Extracted Original Removal Logic (As-Is from Plan 2)
### 2.1 In-Memory Removal vs. Immediate Disk Deletion Hazards
In game development, deleting an entity in the editor or during gameplay must **never synchronously invoke disk deletion**:
1. **Frame Rate Stutters:** Blocking on synchronous OS filesystem APIs (`DeleteFileA`) introduces multisecond frame freezes.
2. **Transactional Safety:** If the editor crashes or the user exits without saving, disk modifications cannot be rolled back.
3. **Undo/Redo Support:** An editor action stack must allow recovering deleted entities before changes are permanently committed to disk.
Therefore, Juliet enforces a strict separation:
- **Immediate in-memory destruction:** Releases the entity from active simulation and registers its identifier in `World::PendingDeletions`.
- **Deferred disk deletion:** Executed strictly during explicit `SaveWorld` operations.
### 2.2 Fast In-Memory Removal (`RemoveAtFast`) & Mutual Pointer Fixup
`VectorArena::RemoveAtFast` utilizes swap-and-pop: the element at the target index is replaced by the last element in the vector, and `Count` is decremented.
```cpp
void RemoveAtFast(index_t index)
{
Assert(Arena);
Assert(index < Count);
Assert(Count > 0);
Type* elementAdr = DataFirst + index;
if (DataLast != elementAdr)
{
Swap(DataLast, elementAdr);
}
--DataLast;
--Count;
}
```
#### The Pointer Invalidation Problem:
When `Entity A` (at `index`) is swapped with `Entity Z` (at `DataLast`), the physical address of `Entity Z` changes from `DataLast` to `elementAdr`.
If `Entity Z` has a derived struct `derivedZ`, `derivedZ->Base` previously pointed to `DataLast`. After `RemoveAtFast`, `derivedZ->Base` points to garbage or the freed slot!
#### The Pointer Fixup Protocol:
To preserve the mutual back-pointer invariant, `DestroyEntity` explicitly fixes up the swapped entity's derived back-pointer:
```cpp
void DestroyEntity(EntityManager& manager, EntityID id)
{
Entity* baseArray = manager.Entities.DataPtr();
size_t count = manager.Entities.Size();
size_t targetIndex = indexMax;
for (size_t i = 0; i < count; ++i)
{
if (baseArray[i].ID == id)
{
targetIndex = i;
break;
}
}
if (targetIndex == indexMax)
{
return;
}
Entity* targetEntity = &baseArray[targetIndex];
Class* classPtr = targetEntity->derived_kind;
Assert(classPtr != nullptr);
// 1. Remove derived component from typed array via swap-and-pop
RemoveDerivedComponent(manager, classPtr, targetEntity->derived);
// 2. Remove base entity via swap-and-pop in VectorArena
bool wasLast = (targetIndex == count - 1);
manager.Entities.RemoveAtFast(targetIndex);
// 3. Pointer fixup: If an element was swapped into targetIndex, fix its back-pointer!
if (!wasLast && targetIndex < manager.Entities.Size())
{
Entity* movedEntity = &manager.Entities[targetIndex];
auto* derivedTemp = reinterpret_cast<entity_template*>(movedEntity->derived);
Assert(derivedTemp != nullptr);
derivedTemp->base = movedEntity;
}
}
```
### 2.3 O(1) Component Removal in `typed_entity_array` via Swap-and-Pop
To keep derived components packed contiguously for SIMD/cache iteration:
1. Locate the component's index within `by_type[kind].arena`. Because components have uniform stride `classPtr->size_of`:
$$\text{componentIndex} = \frac{\text{reinterpret\_cast<uint8*>(derivedPtr)} - \text{reinterpret\_cast<uint8*>(typedArray.array)}}{\text{classPtr->size\_of}}$$
2. If the component is not the last one in the typed arena:
- Copy the last component into the slot occupied by the deleted component.
- Update the moved component's `Base->Derived` pointer to point to its new slot.
3. Decrement `typedArray.count`.
4. Pop the arena allocation if it was the top of the stack, or decrement count to mark slot reclamation.
```cpp
void RemoveDerivedComponent(EntityManager& manager, Class* classPtr, DerivedType derivedPtr)
{
Assert(classPtr != nullptr);
Assert(derivedPtr != nullptr);
typed_entity_array& typedArray = manager.by_type[classPtr->kind];
Assert(typedArray.count > 0);
Assert(typedArray.array != nullptr);
size_t stride = classPtr->size_of;
auto* targetByte = reinterpret_cast<uint8*>(derivedPtr);
auto* firstByte = reinterpret_cast<uint8*>(typedArray.array);
size_t componentIndex = static_cast<size_t>(targetByte - firstByte) / stride;
Assert(componentIndex < typedArray.count);
size_t lastIndex = typedArray.count - 1;
if (componentIndex != lastIndex)
{
uint8* lastByte = firstByte + (lastIndex * stride);
// Copy last component data into target slot
MemCopy(targetByte, lastByte, stride);
// Fixup the base pointer of the moved component
auto* movedDerived = reinterpret_cast<entity_template*>(targetByte);
Assert(movedDerived->base != nullptr);
movedDerived->base->derived = targetByte;
}
typedArray.count -= 1;
if (typedArray.count == 0)
{
typedArray.array = nullptr;
}
}
```
### 2.4 Tracking Deletions in `World::PendingDeletions`
In `World.h`, the `World` struct is extended with a pending deletions container:
```cpp
struct World
{
Arena* WorldArena = nullptr;
EntityManager* EntityManager = nullptr;
VectorArena<EntityID, 1024> PendingDeletions;
};
```
When an entity is deleted in the world:
```cpp
void RemoveWorldEntity(World& world, EntityID id)
{
Assert(world.EntityManager != nullptr);
// Record pending disk deletion
world.PendingDeletions.PushBack(id);
// Destroy in memory immediately
DestroyEntity(*world.EntityManager, id);
}
```
### 2.5 Disk File Cleanup during `SaveWorld`
During `SaveWorld`, before saving modified entities, the engine iterates over `world.PendingDeletions` and removes their associated `.jasset` files:
```cpp
void ProcessPendingDeletions(World& world, NonNullPtr<Arena> scratchArena)
{
for (size_t i = 0; i < world.PendingDeletions.Size(); ++i)
{
EntityID id = world.PendingDeletions[i];
// Format relative asset path: Assets/Entities/{ID}.jasset
char filenameBuffer[64];
juliet_snprintf(filenameBuffer, sizeof(filenameBuffer), "Entities/%llu.jasset", id);
String assetPath = GetAssetPath(scratchArena, WrapString(filenameBuffer));
if (PlatformDeleteFile(assetPath))
{
Log(LogLevel::Message, LogCategory::Game, "Deleted entity asset: %s", CStr(assetPath));
}
}
world.PendingDeletions.Clear();
}
```
### 2.6 Associated Swap-and-Pop Unit Test
```cpp
void TestSwapAndPopPointerFixup()
{
Log(LogLevel::Message, LogCategory::Game, "[UnitTest] Running TestSwapAndPopPointerFixup...");
TempArena tempArena = scratch_begin(nullptr, 0);
World testWorld{};
InitWorld(&testWorld, tempArena.Arena);
InitEntityManager(&testWorld);
EntityManager& manager = *testWorld.EntityManager;
// Allocate 3 entities: E0, E1, E2
Inert* e0 = MakeEntity<Inert>(manager, 1.0f, 0.0f, 0.0f);
Inert* e1 = MakeEntity<Inert>(manager, 2.0f, 0.0f, 0.0f);
Inert* e2 = MakeEntity<Inert>(manager, 3.0f, 0.0f, 0.0f);
EntityID id0 = e0->base->ID;
EntityID id1 = e1->base->ID;
EntityID id2 = e2->base->ID;
Assert(manager.Entities.Size() == 3);
// Delete middle entity E1 (triggers swap with E2)
DestroyEntity(manager, id1);
Assert(manager.Entities.Size() == 2);
// Verify E2's mutual back-pointers are still completely intact
Entity* remaining0 = &manager.Entities[0];
Entity* remaining1 = &manager.Entities[1];
Assert(remaining0->ID == id0);
Assert(remaining1->ID == id2);
auto* derived0 = reinterpret_cast<entity_template*>(remaining0->derived);
auto* derived1 = reinterpret_cast<entity_template*>(remaining1->derived);
Assert(derived0->base == remaining0);
Assert(derived1->base == remaining1);
ShutdownEntityManager();
ShutdownWorld(&testWorld);
scratch_end(tempArena);
Log(LogLevel::Message, LogCategory::Game, "[UnitTest] PASSED: TestSwapAndPopPointerFixup");
}
```
+154
View File
@@ -0,0 +1,154 @@
#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>
namespace UnitTest
{
internal void TestEntityAllocationAndWiring()
{
Log(LogLevel::Message, LogCategory::Game, "[UnitTest] Running TestEntityAllocationAndWiring...");
TempArena tempArena = scratch_begin(nullptr, 0);
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);
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);
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);
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);
World testWorld{};
InitWorld(&testWorld, tempArena.Arena);
InitEntityManager(&testWorld);
EntityManager& manager = *testWorld.EntityManager;
Entity* entity = allocate_entity(manager, Inert::kind);
Assert(entity->is_dirty == true);
// 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);
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
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#include <Juliet.h>
#if JULIET_DEBUG
namespace UnitTest
{
void WorldUnitTest();
} // namespace UnitTest
#endif
+88 -69
View File
@@ -1,66 +1,64 @@
#include <Windows.h> // TODO: remove because our dll should not be platform dependant
#undef min
#undef max
#include <game.h>
#include <Controller/DebugCameraController.h>
#include <Core/Common/EnumUtils.h>
#include <Core/Common/serialization.h>
#include <Core/HAL/Filesystem/Filesystem.h>
#include <Core/HAL/Keyboard/Keyboard.h>
#include <Core/JulietInit.h>
#include <Core/Logging/LogManager.h>
#include <Core/Memory/MemoryArena.h>
#include <Data/World.h>
#include <Entity/Entity.h>
#include <Entity/entity_types.h>
#include <Entity/EntityManager.h>
#include <Graphics/Camera.h>
#include <Graphics/MeshRenderer.h>
#include <imgui.h>
GameState* gGameState = nullptr;
#if JULIET_DEBUG
#include <Debug/DebugTopBar.h>
#include <UnitTest/WorldUnitTest.h>
#endif
// namespace
// {
// void serialize_test(Archive* ar, void* payload)
// {
// SerializedEntityTest* test = reinterpret_cast<SerializedEntityTest*>(payload);
// serialize_elem(ar, test->A);
// }
// } // namespace
//
// DEFINE_ENTITY_SERIALIZED(SerializedEntityTest, serialize_test)
namespace
{
GameState* gGameState = nullptr;
}
GameState* GetGameState()
{
return gGameState;
}
// Test code
namespace Game
{
struct Door
{
DECLARE_ENTITY()
bool IsOpened;
};
DEFINE_ENTITY(Door);
struct Rock
{
DECLARE_ENTITY()
int Health;
};
DEFINE_ENTITY(Rock);
} // namespace Game
extern "C" JULIET_API void __cdecl GameShutdown()
{
printf("Shutting down game...\n");
using namespace Juliet;
using namespace Game;
if (gGameState && gGameState->World)
{
ShutdownWorld(gGameState->World);
}
ShutdownEntityManager();
}
extern "C" JULIET_API void __cdecl GameUpdate(Juliet::GameData* params, [[maybe_unused]] float deltaTime)
extern "C" JULIET_API void __cdecl GameUpdate(GameData* params, [[maybe_unused]] float deltaTime)
{
using namespace Juliet;
using namespace Game;
gGameState = params->GameState;
if (!gGameState)
{
Arena* gameStateArena = ArenaAllocate({ .ReserveSize = Megabytes(1) } JULIET_DEBUG_PARAM("Game Total Arena"));
Arena* gameStateArena = ArenaAllocate({ .ReserveSize = Megabytes(1), .Name = "Game Total Arena" });
auto* gameState = ArenaPushStruct<GameState>(gameStateArena);
gGameState = params->GameState = gameState;
gameState->TotalArena = gameStateArena;
@@ -74,67 +72,91 @@ extern "C" JULIET_API void __cdecl GameUpdate(Juliet::GameData* params, [[maybe_
ReserveCamera(4);
// Bootstrap world
auto* worldArena = ArenaAllocate({ .ReserveSize = Megabytes(1) } JULIET_DEBUG_PARAM("World Arena"));
World* world = ArenaPushStruct<World>(worldArena JULIET_DEBUG_PARAM("World"));
gameState->World = world;
gameState->World->WorldArena = worldArena;
auto* worldArena = ArenaAllocate({ .ReserveSize = Megabytes(1), .Name = "World Arena" });
World* world = ArenaPushStruct<World>(worldArena JULIET_DEBUG_PARAM("World"));
gameState->World = world;
InitWorld(gameState->World, worldArena);
#if JULIET_DEBUG
UnitTest::WorldUnitTest();
#endif
// Entity Use case
InitEntityManager(gameState->World);
auto& manager = GetEntityManager();
Door* door = MakeEntity<Door>(manager, 10.0f, 2.0f);
door->IsOpened = true;
auto& manager = GetEntityManager();
Entity* ent = door->Base;
[[maybe_unused]] Door* stillDoor = DownCast<Door>(ent);
Assert(door == stillDoor);
NonNullPtr mesh = MakeEntity<Inert>(manager, 1.f, 2.f, 0.f);
Rock* rock = MakeEntity<Rock>(manager, 1.f, 2.f);
rock->Health = 100;
Assert(door->Base != rock->Base);
MeshAssetID cubeAsset = GetCubePrimitiveMeshAssetID();
MeshInstanceID meshInst = CreateMeshInstance(cubeAsset, 0, MatrixIdentity());
mesh->MeshInstance = meshInst;
MeshAssetID cubeAsset = GetCubePrimitiveMeshAssetID();
MeshInstanceID meshInst = CreateMeshInstance(cubeAsset, 0, MatrixIdentity());
rock->Base->MeshInstance = meshInst;
NonNullPtr mesh2 = MakeEntity<Inert>(manager, 4.f, 0.f, 2.f);
printf("Door is %s\n", door->IsOpened ? "Opened" : "Closed");
printf("Rock has %d health points\n", rock->Health);
MeshInstanceID meshInst2 = CreateMeshInstance(cubeAsset, 0, MatrixIdentity());
mesh2->MeshInstance = meshInst2;
// Summer at 2pm lighting
Vector3 sunDirection = { -0.2f, -0.9f, -0.3f };
Vector3 sunColor = { 1.0f, 0.95f, 0.85f };
float ambientIntensity = 0.4f;
Vector3 sunDirection = { -0.2f, -0.9f, -0.3f };
Vector3 sunColor = { 1.0f, 0.95f, 0.85f };
float ambientIntensity = 0.4f;
SetGlobalLight(sunDirection, sunColor, ambientIntensity);
}
#if JULIET_DEBUG
DrawTopBar();
#endif
GameMode previousFrameMode = gGameState->Mode;
if (IsKeyPressed(ScanCode::F1))
{
gGameState->Mode = static_cast<GameMode>((ToUnderlying(gGameState->Mode) + 1) % 2);
if (previousFrameMode == GameMode::Play)
{
gGameState->Mode = GameMode::Debug;
}
else if (previousFrameMode == GameMode::Debug)
{
gGameState->Mode = GameMode::Play;
}
}
if (gGameState->Mode == GameMode::Editor)
{
if (IsKeyPressed(ScanCode::F5))
{
gGameState->Mode = GameMode::Play;
}
#if JULIET_EDITOR
RenderWorldEditorUI(*gGameState->World);
#endif
}
if (gGameState->Mode == GameMode::Play)
{
if (IsKeyPressed(ScanCode::Escape))
{
gGameState->Mode = GameMode::Editor;
}
// update game
}
// Sync entity transforms to meshes
auto& manager = GetEntityManager();
for (Entity& ent : manager.Entities)
{
if (ent.MeshInstance != static_cast<index_t>(-1))
{
SetMeshInstanceTransform(ent.MeshInstance, MatrixTranslation(ent.X, ent.Y, 0.0f));
}
}
UpdateEntityManager(manager);
if (gGameState->Mode == GameMode::Debug)
{
if (previousFrameMode != gGameState->Mode)
if (previousFrameMode == GameMode::Play)
{
ActivateDebugController();
}
if (IsKeyPressed(ScanCode::Escape))
{
gGameState->Mode = GameMode::Editor;
}
UpdateDebugController(deltaTime);
#if JULIET_DEBUG
@@ -143,11 +165,8 @@ extern "C" JULIET_API void __cdecl GameUpdate(Juliet::GameData* params, [[maybe_
ImGui::End();
#endif
}
else
if (gGameState->Mode != GameMode::Debug && previousFrameMode == GameMode::Debug)
{
if (previousFrameMode != gGameState->Mode)
{
DeactivateDebugController();
}
DeactivateDebugController();
}
}
+8 -12
View File
@@ -1,31 +1,27 @@
#pragma once
#include <Core/Memory/MemoryArena.h>
#include <Data/World.h>
struct EntityManager;
struct World
{
Juliet::Arena* WorldArena;
EntityManager* EntityManager;
};
enum class GameMode
{
Editor,
Play,
Debug
};
struct GameState
{
Juliet::Arena* TotalArena;
Arena* TotalArena = nullptr;
World* World;
World* World = nullptr;
GameMode Mode = GameMode::Play;
GameMode Mode = GameMode::Editor;
float TotalTime;
int Score;
float TotalTime = 0.0f;
int Score = 0;
};
extern GameState* GetGameState();
[[nodiscard]] extern GameState* GetGameState();
@@ -1,13 +1,9 @@
#pragma once
#pragma once
#include <Core/Application/IApplication.h>
#include <Core/Common/CoreTypes.h>
#include <Juliet.h>
namespace Juliet
{
enum class JulietInit_Flags : uint8;
enum class JulietInit_Flags : uint8;
struct Arena;
extern JULIET_API void StartApplication(IApplication& app, JulietInit_Flags flags);
} // namespace Juliet
struct Arena;
extern JULIET_API void StartApplication(IApplication& app, JulietInit_Flags flags);
+23 -26
View File
@@ -1,32 +1,29 @@
#pragma once
#pragma once
#include <Core/Common/NonNullPtr.h>
namespace Juliet
struct Camera;
struct RenderPass;
struct CommandList;
struct Texture;
struct ColorTargetInfo;
struct DepthStencilTargetInfo;
struct Arena;
class IApplication
{
struct Camera;
struct RenderPass;
struct CommandList;
struct Texture;
struct ColorTargetInfo;
struct DepthStencilTargetInfo;
struct Arena;
public:
virtual ~IApplication() = default;
virtual void Init(NonNullPtr<Arena> arena) = 0;
virtual void Shutdown() = 0;
virtual void Update(float deltaTime) = 0;
virtual bool IsRunning() = 0;
class IApplication
{
public:
virtual ~IApplication() = default;
virtual void Init(NonNullPtr<Arena> arena) = 0;
virtual void Shutdown() = 0;
virtual void Update(float deltaTime) = 0;
virtual bool IsRunning() = 0;
// Accessors for Engine Systems
virtual struct Window* GetPlatformWindow() = 0;
virtual struct GraphicsDevice* GetGraphicsDevice() = 0;
// Accessors for Engine Systems
virtual struct Window* GetPlatformWindow() = 0;
virtual struct GraphicsDevice* GetGraphicsDevice() = 0;
// Render Lifecycle (Engine-Managed Render Loop)
virtual ColorTargetInfo GetColorTargetInfo(Texture* swapchainTexture) = 0;
virtual DepthStencilTargetInfo* GetDepthTargetInfo() = 0;
};
} // namespace Juliet
// Render Lifecycle (Engine-Managed Render Loop)
virtual ColorTargetInfo GetColorTargetInfo(Texture* swapchainTexture) = 0;
virtual DepthStencilTargetInfo* GetDepthTargetInfo() = 0;
};
+53 -49
View File
@@ -1,58 +1,62 @@
#pragma once
#pragma once
#include <Core/Common/CoreUtils.h>
#include <Core/Common/String.h>
// From https://web.mit.edu/freebsd/head/sys/libkern/crc32.c
namespace Juliet
namespace details
{
namespace details
{
constexpr uint32_t crc32_tab[] = {
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832,
0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a,
0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3,
0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab,
0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4,
0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074,
0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525,
0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615,
0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76,
0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6,
0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7,
0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7,
0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278,
0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330,
0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
};
}
constexpr uint32_t crc32_tab[] = {
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832,
0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a,
0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3,
0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab,
0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4,
0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074,
0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525,
0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615,
0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76,
0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6,
0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7,
0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7,
0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278,
0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330,
0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
};
}
consteval uint32 crc32(const char* str, size_t length)
constexpr uint32 crc32(const char* str, size_t length)
{
Assert(str && length > 0);
const char* p = str;
uint32_t crc = ~0U;
while (length--)
{
const char* p = str;
uint32_t crc = ~0U;
while (length--)
{
crc = details::crc32_tab[(crc ^ static_cast<uint8>(*p++)) & 0xFF] ^ (crc >> 8);
}
return crc ^ ~0U;
crc = details::crc32_tab[(crc ^ static_cast<uint8>(*p++)) & 0xFF] ^ (crc >> 8);
}
return crc ^ ~0U;
}
consteval uint32 operator""_crc32(const char* str, size_t length)
{
return crc32(str, length);
}
constexpr uint32 crc32(String str)
{
return crc32(str.Str, str.Size);
}
} // namespace Juliet
consteval uint32 operator""_crc32(const char* str, size_t length)
{
return crc32(str, length);
}
+18
View File
@@ -43,5 +43,23 @@ constexpr int64 int64Max = MaxValueOf<int64>();
constexpr index_t indexMax = MaxValueOf<index_t>();
template <typename Type>
consteval Type MinValueOf()
{
return std::numeric_limits<Type>::lowest();
}
constexpr uint8 uint8Min = MinValueOf<uint8>();
constexpr uint16 uint16Min = MinValueOf<uint16>();
constexpr uint32 uint32Min = MinValueOf<uint32>();
constexpr uint64 uint64Min = MinValueOf<uint64>();
constexpr int8 int8Min = MinValueOf<int8>();
constexpr int16 int16Min = MinValueOf<int16>();
constexpr int32 int32Min = MinValueOf<int32>();
constexpr int64 int64Min = MinValueOf<int64>();
constexpr index_t indexMin = MinValueOf<index_t>();
#define Kilobytes(value) value * 1024
#define Megabytes(value) Kilobytes(value) * 1024
+99 -77
View File
@@ -1,18 +1,15 @@
#pragma once
#pragma once
#include <Core/Common/CoreTypes.h>
#include <Juliet.h>
namespace Juliet
{
#define global static
#define internal static
// 1. Stringify helpers
// 1. Stringify helpers
#define JULIET_STR(x) #x
#define JULIET_TOSTRING(x) JULIET_STR(x)
// 2. Define the pragma operator based on compiler
// 2. Define the pragma operator based on compiler
#if defined(__clang__) || defined(__GNUC__)
#define JULIET_PRAGMA(x) _Pragma(#x)
#define JULIET_SUPPRESS_MSVC(id)
@@ -27,7 +24,7 @@ namespace Juliet
#define JULIET_SUPPRESS_CLANG(str)
#endif
// 3. The Agnostic "Push/Pop"
// 3. The Agnostic "Push/Pop"
#if defined(__clang__)
#define JULIET_WARNING_PUSH JULIET_PRAGMA(clang diagnostic push)
#define JULIET_WARNING_POP JULIET_PRAGMA(clang diagnostic pop)
@@ -40,10 +37,10 @@ namespace Juliet
#endif
#if defined(_MSC_VER)
// MSVC specific intrinsic
// MSVC specific intrinsic
#define JULIET_PLATFORM_BREAK() (__nop(), __debugbreak())
#elif defined(__clang__) || defined(__GNUC__)
// Clang/GCC specific intrinsic
// Clang/GCC specific intrinsic
#define JULIET_PLATFORM_BREAK() __builtin_trap()
#else
#include <signal.h>
@@ -59,7 +56,7 @@ namespace Juliet
{ \
if (!(expression)) [[unlikely]] \
{ \
Juliet::JulietAssert(#expression, message); \
JulietAssert(#expression, message); \
} \
} \
JULIET_WARNING_POP \
@@ -71,7 +68,7 @@ namespace Juliet
long hr_val = (hr_expression); \
if (hr_val < 0) \
{ \
Juliet::JulietAssert(#hr_expression, message, std::source_location::current(), hr_val); \
JulietAssert(#hr_expression, message, std::source_location::current(), hr_val); \
} \
} \
while (0)
@@ -83,7 +80,7 @@ namespace Juliet
#define Unimplemented() \
do \
{ \
Juliet::JulietAssert("UNIMPLEMENTED", "This code path is not yet functional."); \
JulietAssert("UNIMPLEMENTED", "This code path is not yet functional."); \
} \
while (0)
@@ -93,85 +90,85 @@ namespace Juliet
#define Unimplemented() ((void)0)
#endif
JULIET_API extern void JulietAssert(const char* expression, const char* message,
std::source_location location = std::source_location::current(), long handleResult = 0);
JULIET_API extern void JulietAssert(const char* expression, const char* message,
std::source_location location = std::source_location::current(), long handleResult = 0);
#define ZeroStruct(structInstance) ZeroSize(sizeof(structInstance), &(structInstance))
#define ZeroArray(array) ZeroSize(sizeof((array)), (array))
#define ZeroDynArray(Count, Pointer) ZeroSize((Count) * sizeof((Pointer)[0]), Pointer)
inline void ZeroSize(size_t size, void* ptr)
inline void ZeroSize(size_t size, void* ptr)
{
auto Byte = (uint8*)ptr;
while (size--)
{
auto Byte = (uint8*)ptr;
while (size--)
{
*Byte++ = 0;
}
*Byte++ = 0;
}
}
#define Restrict __restrict
template <class Function>
class DeferredFunction
template <class Function>
class DeferredFunction
{
public:
explicit DeferredFunction(const Function& otherFct) noexcept
: Callback(otherFct)
{
public:
explicit DeferredFunction(const Function& otherFct) noexcept
: Callback(otherFct)
{
}
explicit DeferredFunction(Function&& otherFct) noexcept
: Callback(std::move(otherFct))
{
}
~DeferredFunction() noexcept { Callback(); }
DeferredFunction(const DeferredFunction&) = delete;
DeferredFunction(const DeferredFunction&&) = delete;
void operator=(const DeferredFunction&) = delete;
void operator=(DeferredFunction&&) = delete;
private:
Function Callback;
};
template <class Function>
auto Defer(Function&& fct) noexcept
}
explicit DeferredFunction(Function&& otherFct) noexcept
: Callback(std::move(otherFct))
{
return DeferredFunction<std::decay_t<Function>>{ std::forward<Function>(fct) };
}
inline bool IsValid(ByteBuffer buffer)
{
return buffer.Size > 0 && buffer.Data;
}
~DeferredFunction() noexcept { Callback(); }
extern JULIET_API void Free(ByteBuffer& buffer);
DeferredFunction(const DeferredFunction&) = delete;
DeferredFunction(const DeferredFunction&&) = delete;
void operator=(const DeferredFunction&) = delete;
void operator=(DeferredFunction&&) = delete;
template <std::integral T>
[[nodiscard]] constexpr T AlignPow2(T x, T alignment)
{
// Safety Check:
Assert(std::has_single_bit(static_cast<size_t>(alignment)));
private:
Function Callback;
};
return (x + alignment - 1) & ~(alignment - 1);
}
template <class Function>
auto Defer(Function&& fct) noexcept
{
return DeferredFunction<std::decay_t<Function>>{ std::forward<Function>(fct) };
}
template <typename T>
inline void Swap(T* Restrict a, T* Restrict b)
{
T temp = std::move(*a);
*a = std::move(*b);
*b = std::move(temp);
}
inline bool IsValid(ByteBuffer buffer)
{
return buffer.Size > 0 && buffer.Data;
}
// Move to another file dedicated to those
extern JULIET_API void Free(ByteBuffer& buffer);
template <std::integral T>
[[nodiscard]] constexpr T AlignPow2(T x, T alignment)
{
// Safety Check:
Assert(std::has_single_bit(static_cast<size_t>(alignment)));
return (x + alignment - 1) & ~(alignment - 1);
}
template <typename T>
inline void Swap(T* Restrict a, T* Restrict b)
{
T temp = std::move(*a);
*a = std::move(*b);
*b = std::move(temp);
}
// Move to another file dedicated to those
#if defined(__clang__)
#define COMPILER_CLANG 1
#elif defined(_MSC_VER)
#define COMPILER_MSVC 1
#endif
// Undef anything not defined
// Undef anything not defined
#if !defined(COMPILER_CLANG)
#define COMPILER_CLANG 0
#endif
@@ -189,17 +186,42 @@ namespace Juliet
#error AlignOf not defined for this compiler.
#endif
template <typename T>
[[nodiscard]] constexpr const char* GetTypeName()
{
template <typename T>
[[nodiscard]] constexpr const char* GetTypeName()
{
#if COMPILER_CLANG
return __PRETTY_FUNCTION__;
return __PRETTY_FUNCTION__;
#elif COMPILER_MSVC
return __FUNCSIG__;
return __FUNCSIG__;
#elif COMPILER_GCC
return __PRETTY_FUNCTION__;
return __PRETTY_FUNCTION__;
#else
return "UnknownType";
return "UnknownType";
#endif
}
} // namespace Juliet
}
inline uint16 safe_cast_uint16(uint32 value)
{
Assert(value <= uint16Max);
uint16 result = (uint16)value;
return result;
}
const uint32 bitmask1 = 0b0000'0001;
const uint32 bitmask2 = 0b0000'0011;
const uint32 bitmask3 = 0b0000'0111;
const uint32 bitmask4 = 0b0000'1111;
const uint32 bitmask5 = 0b0001'1111;
const uint32 bitmask6 = 0b0011'1111;
const uint32 bitmask7 = 0b0111'1111;
const uint32 bitmask8 = 0b1111'1111;
const uint32 bitmask9 = 0x0000'01ff;
const uint32 bitmask10 = 0x0000'03ff;
const uint32 bitmask11 = 0x0000'07ff;
const uint32 bitmask12 = 0x0000'0fff;
const uint32 bitmask13 = 0x0000'1fff;
const uint32 bitmask14 = 0x0000'3fff;
const uint32 bitmask15 = 0x0000'7fff;
const uint32 bitmask16 = 0x0000'ffff;
// ...
const uint32 bitmask32 = 0xffff'ffff;
+72 -75
View File
@@ -1,89 +1,86 @@
#pragma once
#pragma once
namespace Juliet
template <typename T>
concept IsEnum = std::is_enum_v<T>;
template <IsEnum E>
constexpr E operator~(E lhs) noexcept
{
template <typename T>
concept IsEnum = std::is_enum_v<T>;
return static_cast<E>(~static_cast<std::underlying_type_t<E>>(lhs));
}
template <IsEnum E>
constexpr E operator~(E lhs) noexcept
{
return static_cast<E>(~static_cast<std::underlying_type_t<E>>(lhs));
}
template <IsEnum E>
constexpr E operator|(E lhs, E rhs) noexcept
{
return static_cast<E>(static_cast<std::underlying_type_t<E>>(lhs) | static_cast<std::underlying_type_t<E>>(rhs));
}
template <IsEnum E>
constexpr E operator|(E lhs, E rhs) noexcept
{
return static_cast<E>(static_cast<std::underlying_type_t<E>>(lhs) | static_cast<std::underlying_type_t<E>>(rhs));
}
template <IsEnum E>
constexpr E& operator|=(E& lhs, E rhs) noexcept
{
return lhs = (lhs | rhs);
}
template <IsEnum E>
constexpr E& operator|=(E& lhs, E rhs) noexcept
{
return lhs = (lhs | rhs);
}
template <IsEnum E>
constexpr E operator&(E lhs, E rhs) noexcept
{
return static_cast<E>(static_cast<std::underlying_type_t<E>>(lhs) & static_cast<std::underlying_type_t<E>>(rhs));
}
template <IsEnum E>
constexpr E operator&(E lhs, E rhs) noexcept
{
return static_cast<E>(static_cast<std::underlying_type_t<E>>(lhs) & static_cast<std::underlying_type_t<E>>(rhs));
}
template <IsEnum E>
constexpr E& operator&=(E& lhs, E rhs) noexcept
{
return lhs = (lhs & rhs);
}
template <IsEnum E>
constexpr E& operator&=(E& lhs, E rhs) noexcept
{
return lhs = (lhs & rhs);
}
template <IsEnum E>
constexpr E operator^(E lhs, E rhs) noexcept
{
return static_cast<E>(static_cast<std::underlying_type_t<E>>(lhs) ^ static_cast<std::underlying_type_t<E>>(rhs));
}
template <IsEnum E>
constexpr E operator^(E lhs, E rhs) noexcept
{
return static_cast<E>(static_cast<std::underlying_type_t<E>>(lhs) ^ static_cast<std::underlying_type_t<E>>(rhs));
}
template <IsEnum E>
constexpr E& operator^=(E& lhs, E rhs) noexcept
{
return lhs = (lhs ^ rhs);
}
template <IsEnum E>
constexpr E& operator^=(E& lhs, E rhs) noexcept
{
return lhs = (lhs ^ rhs);
}
template <IsEnum E>
constexpr std::underlying_type_t<E> operator-(E lhs, E rhs) noexcept
{
using T = std::underlying_type_t<E>;
return static_cast<T>(static_cast<T>(lhs) - static_cast<T>(rhs));
}
template <IsEnum E>
constexpr std::underlying_type_t<E> operator-(E lhs, E rhs) noexcept
{
using T = std::underlying_type_t<E>;
return static_cast<T>(static_cast<T>(lhs) - static_cast<T>(rhs));
}
template <IsEnum E>
constexpr std::underlying_type_t<E> operator+(E lhs, E rhs) noexcept
{
using T = std::underlying_type_t<E>;
return static_cast<T>(static_cast<T>(lhs) + static_cast<T>(rhs));
}
template <IsEnum E>
constexpr std::underlying_type_t<E> operator+(E lhs, E rhs) noexcept
{
using T = std::underlying_type_t<E>;
return static_cast<T>(static_cast<T>(lhs) + static_cast<T>(rhs));
}
template <IsEnum E>
constexpr E operator-(E lhs, std::underlying_type_t<E> rhs) noexcept
{
using T = std::underlying_type_t<E>;
return static_cast<E>(static_cast<T>(static_cast<T>(lhs) - rhs));
}
template <IsEnum E>
constexpr E operator-(E lhs, std::underlying_type_t<E> rhs) noexcept
{
using T = std::underlying_type_t<E>;
return static_cast<E>(static_cast<T>(static_cast<T>(lhs) - rhs));
}
template <IsEnum E>
constexpr E operator+(E lhs, std::underlying_type_t<E> rhs) noexcept
{
using T = std::underlying_type_t<E>;
return static_cast<E>(static_cast<T>(static_cast<T>(lhs) + rhs));
}
template <IsEnum E>
constexpr E operator+(E lhs, std::underlying_type_t<E> rhs) noexcept
{
using T = std::underlying_type_t<E>;
return static_cast<E>(static_cast<T>(static_cast<T>(lhs) + rhs));
}
template <IsEnum E>
constexpr std::underlying_type_t<E> ToUnderlying(E enm) noexcept
{
return static_cast<std::underlying_type_t<E>>(enm);
}
template <IsEnum E>
constexpr std::underlying_type_t<E> ToUnderlying(E enm) noexcept
{
return static_cast<std::underlying_type_t<E>>(enm);
}
template <IsEnum E>
constexpr E ToEnum(std::underlying_type_t<E> value) noexcept
{
return static_cast<E>(value);
}
} // namespace Juliet
template <IsEnum E>
constexpr E ToEnum(std::underlying_type_t<E> value) noexcept
{
return static_cast<E>(value);
}
+90 -93
View File
@@ -1,113 +1,110 @@
#pragma once
#pragma once
#include <Core/Common/CoreUtils.h>
namespace Juliet
template <typename Type, typename OtherType>
concept NonNullPtr_Convertible = std::is_convertible_v<OtherType*, Type*>;
template <typename Type, typename OtherType>
concept NonNullPtr_SameType = std::is_same_v<OtherType*, Type*>;
template <typename Type>
class NonNullPtr
{
template <typename Type, typename OtherType>
concept NonNullPtr_Convertible = std::is_convertible_v<OtherType*, Type*>;
template <typename Type, typename OtherType>
concept NonNullPtr_SameType = std::is_same_v<OtherType*, Type*>;
template <typename Type>
class NonNullPtr
public:
constexpr NonNullPtr(Type* ptr)
: InternalPtr(ptr)
{
public:
constexpr NonNullPtr(Type* ptr)
: InternalPtr(ptr)
{
Assert(ptr, "Tried to initialize a NonNullPtr with a null pointer");
}
Assert(ptr, "Tried to initialize a NonNullPtr with a null pointer");
}
template <typename OtherType>
requires NonNullPtr_Convertible<OtherType*, Type*>
constexpr NonNullPtr(const NonNullPtr<OtherType>& otherPtr)
: InternalPtr(otherPtr.Get())
{
Assert(InternalPtr, "Fatal Error: Assigned a non null ptr using another NonNullPtr but its was null.");
}
template <typename OtherType>
requires NonNullPtr_Convertible<OtherType*, Type*>
constexpr NonNullPtr(const NonNullPtr<OtherType>& otherPtr)
: InternalPtr(otherPtr.Get())
{
Assert(InternalPtr, "Fatal Error: Assigned a non null ptr using another NonNullPtr but its was null.");
}
// Assignment
[[nodiscard]] constexpr NonNullPtr& operator=(Type* ptr)
{
Assert(ptr, "Tried to assign a null pointer to a NonNullPtr!");
InternalPtr = ptr;
return *this;
}
// Assignment
[[nodiscard]] constexpr NonNullPtr& operator=(Type* ptr)
{
Assert(ptr, "Tried to assign a null pointer to a NonNullPtr!");
InternalPtr = ptr;
return *this;
}
template <typename OtherType>
requires NonNullPtr_Convertible<OtherType*, Type*>
[[nodiscard]] constexpr NonNullPtr& operator=(const NonNullPtr<OtherType>& otherPtr)
{
InternalPtr = otherPtr.Get();
return *this;
}
template <typename OtherType>
requires NonNullPtr_Convertible<OtherType*, Type*>
[[nodiscard]] constexpr NonNullPtr& operator=(const NonNullPtr<OtherType>& otherPtr)
{
InternalPtr = otherPtr.Get();
return *this;
}
// Accessors
[[nodiscard]] constexpr operator Type*() const
{
Assert(InternalPtr, "NonNullPtr: Internal Pointer is Null");
return InternalPtr;
}
// Accessors
[[nodiscard]] constexpr operator Type*() const
{
Assert(InternalPtr, "NonNullPtr: Internal Pointer is Null");
return InternalPtr;
}
[[nodiscard]] constexpr Type* Get() const
{
Assert(InternalPtr, "NonNullPtr: Internal Pointer is Null");
return InternalPtr;
}
[[nodiscard]] constexpr Type* Get() const
{
Assert(InternalPtr, "NonNullPtr: Internal Pointer is Null");
return InternalPtr;
}
[[nodiscard]] constexpr Type& operator*() const
{
Assert(InternalPtr, "NonNullPtr: Internal Pointer is Null");
return *InternalPtr;
}
[[nodiscard]] constexpr Type& operator*() const
{
Assert(InternalPtr, "NonNullPtr: Internal Pointer is Null");
return *InternalPtr;
}
[[nodiscard]] constexpr Type* operator->() const
{
Assert(InternalPtr, "NonNullPtr: Internal Pointer is Null");
return InternalPtr;
}
[[nodiscard]] constexpr Type* operator->() const
{
Assert(InternalPtr, "NonNullPtr: Internal Pointer is Null");
return InternalPtr;
}
// Comparisons
[[nodiscard]] constexpr bool operator==(const NonNullPtr& otherPtr) const
{
return InternalPtr == otherPtr.InternalPtr;
}
// Comparisons
[[nodiscard]] constexpr bool operator==(const NonNullPtr& otherPtr) const
{
return InternalPtr == otherPtr.InternalPtr;
}
template <typename OtherType>
requires NonNullPtr_SameType<Type, OtherType>
[[nodiscard]] constexpr bool operator==(OtherType* otherRawPtr) const
{
return InternalPtr == otherRawPtr;
}
template <typename OtherType>
requires NonNullPtr_SameType<Type, OtherType>
[[nodiscard]] constexpr bool operator==(OtherType* otherRawPtr) const
{
return InternalPtr == otherRawPtr;
}
template <typename OtherType>
requires NonNullPtr_SameType<Type, OtherType>
[[nodiscard]] constexpr friend bool operator==(OtherType* otherRawPtr, const NonNullPtr& nonNullPtr)
{
return otherRawPtr == nonNullPtr.InternalPtr;
}
template <typename OtherType>
requires NonNullPtr_SameType<Type, OtherType>
[[nodiscard]] constexpr friend bool operator==(OtherType* otherRawPtr, const NonNullPtr& nonNullPtr)
{
return otherRawPtr == nonNullPtr.InternalPtr;
}
// Forbid assigning a nullptr at compile time
constexpr NonNullPtr(std::nullptr_t)
: InternalPtr(nullptr)
{
static_assert(sizeof(Type) == 0, "Trying to initialize a NonNullPtr with a nullptr value");
}
// Forbid assigning a nullptr at compile time
constexpr NonNullPtr(std::nullptr_t)
: InternalPtr(nullptr)
{
static_assert(sizeof(Type) == 0, "Trying to initialize a NonNullPtr with a nullptr value");
}
[[nodiscard]] constexpr NonNullPtr& operator=(std::nullptr_t)
{
static_assert(sizeof(Type) == 0, "Trying to assign a NonNullPtr with a nullptr value");
return *this;
}
[[nodiscard]] constexpr NonNullPtr& operator=(std::nullptr_t)
{
static_assert(sizeof(Type) == 0, "Trying to assign a NonNullPtr with a nullptr value");
return *this;
}
[[nodiscard]] constexpr explicit operator bool() const { return InternalPtr != nullptr; }
[[nodiscard]] constexpr explicit operator bool() const { return InternalPtr != nullptr; }
private:
Type* InternalPtr;
};
private:
Type* InternalPtr;
};
template <typename T>
NonNullPtr(T*) -> NonNullPtr<T>;
} // namespace Juliet
template <typename T>
NonNullPtr(T*) -> NonNullPtr<T>;
+140 -125
View File
@@ -1,4 +1,4 @@
#pragma once
#pragma once
#include <Core/Common/NonNullPtr.h>
#include <Core/Math/MathUtils.h>
@@ -14,154 +14,169 @@
#undef RESTORE_GLOBAL
#endif
namespace Juliet
{
struct Arena;
struct Arena;
#define ConstString(str) { const_cast<char*>((str)), sizeof(str) - 1 }
#define CStr(str) ((str).Data)
#define CStr(str) ((str).Str)
#define InplaceString(name, size) \
char name##_[size]; \
MemSet(name##_, 0, sizeof(uint32)); \
String name = { name##_, 0 }
// Everything is Little Endian
enum class StringEncoding : uint8
{
Unknown = 0,
ASCII,
LATIN1,
UTF8,
UTF16,
UTF32,
UCS2,
UCS4,
};
// Everything is Little Endian
enum class StringEncoding : uint8
{
Unknown = 0,
ASCII,
LATIN1,
UTF8,
UTF16,
UTF32,
UCS2,
UCS4,
};
// Represents a UTF-8 String.
// Not null terminated.
struct String
{
char* Data;
size_t Size;
};
// Represents a UTF-8 String.
// Not null terminated.
struct String8
{
char* Str;
size_t Size;
};
using String = String8;
struct StringBuffer : String
{
size_t Capacity;
};
struct String16
{
uint16* Str;
size_t Size;
};
constexpr uint32 kInvalidUTF8 = 0xFFFD;
struct StringBuffer : String
{
size_t Capacity;
};
inline size_t StringLength(String str)
{
return str.Size;
}
struct UnicodeDecode
{
uint32 Increment;
uint32 Codepoint;
};
inline size_t StringLength(const char* str)
constexpr uint32 kInvalidUTF8 = 0xFFFD;
inline size_t StringLength(String str)
{
return str.Size;
}
inline size_t StringLength(const char* str)
{
size_t length = 0;
if (str)
{
size_t length = 0;
if (str)
while (*str)
{
while (*str)
{
++length;
++str;
}
++length;
++str;
}
return length;
}
inline bool IsValid(String str)
{
return str.Size > 0 && str.Data != nullptr && *str.Data;
}
return length;
}
inline String WrapString(const char* str)
{
String result = {};
result.Data = const_cast<char*>(str);
result.Size = str ? strlen(str) : 0;
return result;
}
inline bool IsValid(String str)
{
return str.Size > 0 && str.Str != nullptr && *str.Str;
}
inline String FindChar(String str, char c)
inline String WrapString(const char* str)
{
String result = {};
result.Str = const_cast<char*>(str);
result.Size = str ? strlen(str) : 0;
return result;
}
inline String FindChar(String str, char c)
{
String result = str;
while (result.Size)
{
String result = str;
while (result.Size)
if (*result.Str != c)
{
if (*result.Data != c)
{
++result.Data;
--result.Size;
}
else
{
return result;
}
++result.Str;
--result.Size;
}
return {};
}
inline bool ContainsChar(String str, char c)
{
return IsValid(FindChar(str, c));
}
// Return:
// - < 0 if str1 < str2
// - = 0 : Both strings are equals
// - > 0 if str1 > str2
inline int32 StringCompare(String str1, String str2)
{
size_t len1 = StringLength(str1);
size_t len2 = StringLength(str2);
size_t minLen = Min(len1, len2);
int32 result = MemCompare(CStr(str1), CStr(str2), minLen);
if (result == 0)
else
{
if (len1 > len2)
{
return 1;
}
if (len1 < len2)
{
return -1;
}
return 0;
return result;
}
return result;
}
return {};
}
JULIET_API uint32 StepUTF8(String& inStr);
JULIET_API String FindString(String strLeft, String strRight);
inline bool ContainsChar(String str, char c)
{
return IsValid(FindChar(str, c));
}
// Case insensitive compare. Supports ASCII only
// TODO: Support UNICODE
extern JULIET_API int8 StringCompareCaseInsensitive(String str1, String str2);
// Do not allocate anything, you must allocate your out buffer yourself
// TODO: Version taking arena that can allocate
// Do not take String type because we dont know the string encoding we are going from/to
// src and dst will be casted based on the encoding.
// size will correspond to the number of characters
// Will convert \0 character if present.
extern JULIET_API bool ConvertString(StringEncoding from, StringEncoding to, String src, StringBuffer& dst, bool nullTerminate);
extern JULIET_API bool ConvertString(String from, String to, String src, StringBuffer& dst, bool nullTerminate);
JULIET_API String StringCopy(NonNullPtr<Arena> arena, String str);
template <typename... Args>
String Format(NonNullPtr<Arena> arena, const char* formatStr, Args&&... args)
// Return:
// - < 0 if str1 < str2
// - = 0 : Both strings are equals
// - > 0 if str1 > str2
inline int32 StringCompare(String str1, String str2)
{
size_t len1 = StringLength(str1);
size_t len2 = StringLength(str2);
size_t minLen = Min(len1, len2);
int32 result = MemCompare(CStr(str1), CStr(str2), minLen);
if (result == 0)
{
std::string result = std::vformat(formatStr, std::make_format_args(args...));
return StringCopy(arena, WrapString(result.c_str()));
if (len1 > len2)
{
return 1;
}
if (len1 < len2)
{
return -1;
}
return 0;
}
} // namespace Juliet
return result;
}
JULIET_API uint32 StepUTF8(String& inStr);
JULIET_API String FindString(String strLeft, String strRight);
// Case insensitive compare. Supports ASCII only
// TODO: Support UNICODE
extern JULIET_API int8 StringCompareCaseInsensitive(String str1, String str2);
// Do not allocate anything, you must allocate your out buffer yourself
// TODO: Version taking arena that can allocate
// Do not take String type because we dont know the string encoding we are going from/to
// src and dst will be casted based on the encoding.
// size will correspond to the number of characters
// Will convert \0 character if present.
extern JULIET_API bool ConvertString(StringEncoding from, StringEncoding to, String src, StringBuffer& dst, bool nullTerminate);
extern JULIET_API bool ConvertString(String from, String to, String src, StringBuffer& dst, bool nullTerminate);
JULIET_API String StringCopy(NonNullPtr<Arena> arena, String str);
JULIET_API String16 str16_from_8(NonNullPtr<Arena> arena, String8 str);
String trim_whitespace(String str);
template <typename... Args>
String Format(NonNullPtr<Arena> arena, const char* formatStr, Args&&... args)
{
std::string result = std::vformat(formatStr, std::make_format_args(args...));
return StringCopy(arena, WrapString(result.c_str()));
}
#define juliet_snprintf snprintf
#ifdef UNIT_TEST
namespace Juliet::UnitTest
namespace UnitTest
{
inline void TestFindChar()
{
@@ -169,12 +184,12 @@ namespace Juliet::UnitTest
String s2 = ConstString("abcdefabcdef");
String s3 = ConstString("11111111111111111111");
Assert(FindChar(s1, 'x').Data == nullptr);
Assert(FindChar(s2, 'y').Data == nullptr);
Assert(FindChar(s2, 'a').Data - s2.Data == 0);
Assert(FindChar(s2, 'd').Data - s2.Data == 3);
Assert(FindChar(s2, 'f').Data - s2.Data == 5);
Assert(FindChar(s3, '1').Data - s3.Data == 0);
Assert(FindChar(s1, 'x').Str == nullptr);
Assert(FindChar(s2, 'y').Str == nullptr);
Assert(FindChar(s2, 'a').Str - s2.Str == 0);
Assert(FindChar(s2, 'd').Str - s2.Str == 3);
Assert(FindChar(s2, 'f').Str - s2.Str == 5);
Assert(FindChar(s3, '1').Str - s3.Str == 0);
}
} // namespace Juliet::UnitTest
} // namespace UnitTest
#endif
+126
View File
@@ -0,0 +1,126 @@
#pragma once
#include <Juliet.h>
#include <Core/Common/String.h>
#include <Core/HAL/IO/IOStream.h>
// .jasset
// AssetFile ::= { CommentLine | EmptyLine | PropertyDeclaration } ;
// CommentLine ::= ( "#" | "//" ) { Character } LineEnding ;
// EmptyLine ::= { Whitespace } LineEnding ;
// PropertyDeclaration ::= KeyHeader LineEnding ValueBlock ;
// KeyHeader ::= ";" { Whitespace } Identifier ;
// ValueBlock ::= { ValueLine LineEnding } ;
// ValueLine ::= { Whitespace } ValueString { Whitespace } ;
// LineEnding ::= "\r\n" | "\n" ;
// Identifier ::= [a-zA-Z_][a-zA-Z0-9_]* ;
struct Vector4;
struct IOStream;
struct Arena;
struct ArchivePropertyNode
{
String key;
String value;
uint32 key_crc;
bool consumed;
};
struct ParsedArchive
{
ArchivePropertyNode* nodes = nullptr;
uint32 property_count = 0;
};
struct Archive
{
Arena* arena;
bool loading;
ParsedArchive base = {};
IOStream* stream = nullptr;
// to remove
void* base_ptr = nullptr;
index_t offset = 0;
};
JULIET_API void serialize(Archive& ar, void* data, size_t size);
#define serialize_elem(ar, val) serialize((ar), &(val), sizeof(val))
JULIET_API ParsedArchive tokenize_archive(NonNullPtr<Arena> arena, ByteBuffer file_buffer);
JULIET_API ArchivePropertyNode* find_property(NonNullPtr<ParsedArchive> archive, uint32 property_crc);
JULIET_API void write_property_header(Archive& archive, String property_name);
JULIET_API bool read_prop(Archive& ar, String value_raw, String& value);
JULIET_API void write(NonNullPtr<IOStream> stream, String value);
JULIET_API bool read(const char* buffer, float& value, const char** end = nullptr);
JULIET_API void write(NonNullPtr<IOStream> stream, float value);
JULIET_API bool read(const char* buffer, int8& value);
JULIET_API void write(NonNullPtr<IOStream> stream, int8 value);
JULIET_API bool read(const char* buffer, int16& value);
JULIET_API void write(NonNullPtr<IOStream> stream, int16 value);
JULIET_API bool read(const char* buffer, int32& value);
JULIET_API void write(NonNullPtr<IOStream> stream, int32 value);
JULIET_API bool read(const char* buffer, int64& value);
JULIET_API void write(NonNullPtr<IOStream> stream, int64 value);
JULIET_API bool read(const char* buffer, uint8& value);
JULIET_API void write(NonNullPtr<IOStream> stream, uint8 value);
JULIET_API bool read(const char* buffer, uint16& value);
JULIET_API void write(NonNullPtr<IOStream> stream, uint16 value);
JULIET_API bool read(const char* buffer, uint32& value);
JULIET_API void write(NonNullPtr<IOStream> stream, uint32 value);
JULIET_API bool read(const char* buffer, uint64& value);
JULIET_API void write(NonNullPtr<IOStream> stream, uint64 value);
JULIET_API bool read(const char* buffer, bool& value);
JULIET_API void write(NonNullPtr<IOStream> stream, bool value);
JULIET_API bool read(const char* buffer, Vector4& value);
JULIET_API void write(NonNullPtr<IOStream> stream, Vector4 value);
// For primitives not needing archive nor allocation
template <typename Type>
bool read_prop(Archive& /*ar*/, String value_raw, Type& value)
{
char buffer[64];
size_t cpy_size = Min(value_raw.Size, sizeof(buffer) - 1);
MemCopy(buffer, value_raw.Str, cpy_size);
buffer[cpy_size] = '\0';
return read(buffer, value);
}
#define SERIALIZE(ar, name, var) serialize((ar), ConstString(#name), #name##_crc32, (var))
#define SERIALIZE_SIMPLE(ar, var) SERIALIZE(ar, var, var)
template <typename Type>
bool serialize(Archive& ar, String property_name, uint32 property_crc, Type& value)
{
Assert(IsValid(property_name));
bool result = false;
if (ar.loading)
{
if (auto* prop = find_property(&ar.base, property_crc))
{
if (read_prop(ar, prop->value, value))
{
result = true;
}
}
}
else
{
// Save
write_property_header(ar, property_name);
write(ar.stream, value);
result = true;
}
return result;
}
#if JULIET_DEBUG
JULIET_API void audit_unconsumed_properties(NonNullPtr<ParsedArchive> archive, String context_name);
#endif
+182 -185
View File
@@ -1,214 +1,211 @@
#pragma once
#pragma once
#include <Core/Common/NonNullPtr.h>
#include <Core/Memory/MemoryArena.h>
namespace Juliet
template <typename Type, size_t ReserveSize = 16>
struct VectorArena
{
template <typename Type, size_t ReserveSize = 16>
struct VectorArena
void Create(NonNullPtr<Arena> arena JULIET_DEBUG_PARAM(const char* name = nullptr))
{
void Create(NonNullPtr<Arena> arena JULIET_DEBUG_PARAM(const char* name = nullptr))
Assert(!Arena);
JULIET_DEBUG_ONLY(Name = name ? name : Name;)
DataFirst = DataLast = Data = nullptr;
Count = 0;
Capacity = 0;
Arena = arena;
Reserve(ReserveSize);
}
void Destroy()
{
DataFirst = DataLast = Data = nullptr;
Count = 0;
Capacity = 0;
Arena = nullptr;
}
void Reserve(size_t newCapacity)
{
Assert(Arena);
Assert(newCapacity <= ReserveSize && "VectorArena capacity should be <= ReserveSize.");
if (Data == nullptr)
{
Assert(!Arena);
Data = ArenaPushArray<Type>(Arena, newCapacity JULIET_DEBUG_PARAM(Name));
Capacity = newCapacity;
}
else
{
Unimplemented();
}
}
JULIET_DEBUG_ONLY(Name = name ? name : Name;)
void Resize(size_t newCount)
{
Assert(Arena);
if (newCount == Count)
{
return;
}
DataFirst = DataLast = Data = nullptr;
Count = 0;
Capacity = 0;
Arena = arena;
if (Data == nullptr)
{
size_t initialCapacity = newCount > ReserveSize ? newCount : ReserveSize;
Reserve(initialCapacity);
}
Assert(newCount <= Capacity && "VectorArena capacity exceeded!");
Count = newCount;
if (Count > 0)
{
DataFirst = Data;
DataLast = Data + Count - 1;
}
else
{
DataFirst = DataLast = nullptr;
}
}
void PushBack(const Type* buffer, size_t amount)
{
Assert(Arena);
Assert(buffer || amount == 0);
if (amount == 0)
{
return;
}
if (Data == nullptr)
{
size_t initialCapacity = amount > ReserveSize ? amount : ReserveSize;
Reserve(initialCapacity);
}
Assert(Count + amount <= Capacity && "VectorArena capacity exceeded!");
Type* dst = Data + Count;
MemCopy(dst, buffer, amount * sizeof(Type));
if (Count == 0)
{
DataFirst = dst;
}
DataLast = dst + amount - 1;
Count += amount;
}
void PushBack(const Type& value)
{
Assert(Arena);
if (Data == nullptr)
{
Reserve(ReserveSize);
}
void Destroy()
Assert(Count + 1 <= Capacity && "VectorArena capacity exceeded!");
Type* entry = Data + Count;
*entry = value;
if (Count == 0)
{
DataFirst = DataLast = Data = nullptr;
Count = 0;
Capacity = 0;
Arena = nullptr;
DataFirst = entry;
}
DataLast = entry;
++Count;
}
void PushBack(Type&& value)
{
Assert(Arena);
if (Data == nullptr)
{
Reserve(ReserveSize);
}
void Reserve(size_t newCapacity)
{
Assert(Arena);
Assert(newCapacity <= ReserveSize && "VectorArena capacity should be <= ReserveSize.");
Assert(Count + 1 <= Capacity && "VectorArena capacity exceeded!");
if (Data == nullptr)
{
Data = ArenaPushArray<Type>(Arena, newCapacity JULIET_DEBUG_PARAM(Name));
Capacity = newCapacity;
}
else
{
Unimplemented();
}
Type* entry = Data + Count;
*entry = std::move(value);
if (Count == 0)
{
DataFirst = entry;
}
DataLast = entry;
++Count;
}
void RemoveAtFast(index_t index)
{
Assert(Arena);
Assert(index < Count);
Assert(Count > 0);
Type* elementAdr = DataFirst + index;
// Swap DataLast and element
if (DataLast != elementAdr)
{
Swap(DataLast, elementAdr);
}
void Resize(size_t newCount)
--DataLast;
--Count;
if (Count == 0)
{
Assert(Arena);
if (newCount == Count)
{
return;
}
if (Data == nullptr)
{
size_t initialCapacity = newCount > ReserveSize ? newCount : ReserveSize;
Reserve(initialCapacity);
}
Assert(newCount <= Capacity && "VectorArena capacity exceeded!");
Count = newCount;
if (Count > 0)
{
DataFirst = Data;
DataLast = Data + Count - 1;
}
else
{
DataFirst = DataLast = nullptr;
}
}
void PushBack(const Type* buffer, size_t amount)
{
Assert(Arena);
Assert(buffer || amount == 0);
if (amount == 0)
{
return;
}
if (Data == nullptr)
{
size_t initialCapacity = amount > ReserveSize ? amount : ReserveSize;
Reserve(initialCapacity);
}
Assert(Count + amount <= Capacity && "VectorArena capacity exceeded!");
Type* dst = Data + Count;
MemCopy(dst, buffer, amount * sizeof(Type));
if (Count == 0)
{
DataFirst = dst;
}
DataLast = dst + amount - 1;
Count += amount;
}
void PushBack(const Type& value)
{
Assert(Arena);
if (Data == nullptr)
{
Reserve(ReserveSize);
}
Assert(Count + 1 <= Capacity && "VectorArena capacity exceeded!");
Type* entry = Data + Count;
*entry = value;
if (Count == 0)
{
DataFirst = entry;
}
DataLast = entry;
++Count;
}
void PushBack(Type&& value)
{
Assert(Arena);
if (Data == nullptr)
{
Reserve(ReserveSize);
}
Assert(Count + 1 <= Capacity && "VectorArena capacity exceeded!");
Type* entry = Data + Count;
*entry = std::move(value);
if (Count == 0)
{
DataFirst = entry;
}
DataLast = entry;
++Count;
}
void RemoveAtFast(index_t index)
{
Assert(Arena);
Assert(index < Count);
Assert(Count > 0);
Type* elementAdr = DataFirst + index;
// Swap DataLast and element
if (DataLast != elementAdr)
{
Swap(DataLast, elementAdr);
}
--DataLast;
--Count;
if (Count == 0)
{
DataFirst = DataLast = nullptr;
}
}
void Clear()
{
Assert(Arena);
DataFirst = DataLast = nullptr;
Count = 0;
}
}
[[nodiscard]] bool IsEmpty() const { return Count == 0; }
void Clear()
{
Assert(Arena);
// C++ Accessors for loop supports and Index based access
[[nodiscard]] Type& operator[](size_t index) { return DataFirst[index]; }
[[nodiscard]] const Type& operator[](size_t index) const { return DataFirst[index]; }
DataFirst = DataLast = nullptr;
Count = 0;
}
[[nodiscard]] Type* begin() { return DataFirst; }
[[nodiscard]] Type* end() { return DataFirst + Count; }
[[nodiscard]] bool IsEmpty() const { return Count == 0; }
[[nodiscard]] const Type* begin() const { return DataFirst; }
[[nodiscard]] const Type* end() const { return DataFirst + Count; }
// C++ Accessors for loop supports and Index based access
[[nodiscard]] Type& operator[](size_t index) { return DataFirst[index]; }
[[nodiscard]] const Type& operator[](size_t index) const { return DataFirst[index]; }
[[nodiscard]] Type* First() { return DataFirst; }
[[nodiscard]] Type* Front() { return DataFirst; }
[[nodiscard]] Type* Last() { return DataLast; }
[[nodiscard]] Type* Back() { return DataLast; }
[[nodiscard]] Type* DataPtr() { return Data; }
[[nodiscard]] const Type* DataPtr() const { return Data; }
[[nodiscard]] Type* begin() { return DataFirst; }
[[nodiscard]] Type* end() { return DataFirst + Count; }
[[nodiscard]] size_t Size() const { return Count; }
[[nodiscard]] const Type* begin() const { return DataFirst; }
[[nodiscard]] const Type* end() const { return DataFirst + Count; }
Arena* Arena = nullptr;
Type* DataFirst = nullptr;
Type* DataLast = nullptr;
Type* Data = nullptr;
size_t Count = 0;
size_t Capacity = 0;
JULIET_DEBUG_ONLY(const char* Name = "VectorArena";)
};
static_assert(std::is_standard_layout_v<VectorArena<int>>,
"VectorArena must have a standard layout to remain POD-like.");
static_assert(std::is_trivially_copyable_v<VectorArena<int>>,
"VectorArena must be trivially copyable (no custom destructors/assignment).");
} // namespace Juliet
[[nodiscard]] Type* First() { return DataFirst; }
[[nodiscard]] Type* Front() { return DataFirst; }
[[nodiscard]] Type* Last() { return DataLast; }
[[nodiscard]] Type* Back() { return DataLast; }
[[nodiscard]] Type* DataPtr() { return Data; }
[[nodiscard]] const Type* DataPtr() const { return Data; }
[[nodiscard]] size_t Size() const { return Count; }
Arena* Arena = nullptr;
Type* DataFirst = nullptr;
Type* DataLast = nullptr;
Type* Data = nullptr;
size_t Count = 0;
size_t Capacity = 0;
JULIET_DEBUG_ONLY(const char* Name = "VectorArena";)
};
static_assert(std::is_standard_layout_v<VectorArena<int>>,
"VectorArena must have a standard layout to remain POD-like.");
static_assert(std::is_trivially_copyable_v<VectorArena<int>>,
"VectorArena must be trivially copyable (no custom destructors/assignment).");
+9 -14
View File
@@ -1,21 +1,16 @@
#pragma once
#pragma once
#include <Core/Common/CoreTypes.h>
#include <Core/Common/NonNullPtr.h>
#include <Core/Common/String.h>
#include <Juliet.h>
namespace Juliet
{
struct Window;
struct Window;
using WindowID = uint8;
extern JULIET_API Window* CreatePlatformWindow(const char* title, uint16 width, uint16 height, int flags = 0 /* unused */);
extern JULIET_API void DestroyPlatformWindow(NonNullPtr<Window> window);
using WindowID = uint8;
extern JULIET_API Window* CreatePlatformWindow(const char* title, uint16 width, uint16 height, int flags = 0 /* unused */);
extern JULIET_API void DestroyPlatformWindow(NonNullPtr<Window> window);
extern JULIET_API void ShowWindow(NonNullPtr<Window> window);
extern JULIET_API void HideWindow(NonNullPtr<Window> window);
extern JULIET_API void ShowWindow(NonNullPtr<Window> window);
extern JULIET_API void HideWindow(NonNullPtr<Window> window);
extern JULIET_API WindowID GetWindowID(NonNullPtr<Window> window);
extern JULIET_API void SetWindowTitle(NonNullPtr<Window> window, String title);
} // namespace Juliet
extern JULIET_API WindowID GetWindowID(NonNullPtr<Window> window);
extern JULIET_API void SetWindowTitle(NonNullPtr<Window> window, String title);
@@ -1,12 +1,9 @@
#pragma once
#pragma once
#include <Core/Common/NonNullPtr.h>
namespace Juliet
{
struct DynamicLibrary;
struct DynamicLibrary;
extern JULIET_API DynamicLibrary* LoadDynamicLibrary(const char* filename);
extern JULIET_API FunctionPtr LoadFunction(NonNullPtr<DynamicLibrary> lib, const char* functionName);
extern JULIET_API void UnloadDynamicLibrary(NonNullPtr<DynamicLibrary> lib);
} // namespace Juliet
extern JULIET_API DynamicLibrary* LoadDynamicLibrary(const char* filename);
extern JULIET_API FunctionPtr LoadFunction(NonNullPtr<DynamicLibrary> lib, const char* functionName);
extern JULIET_API void UnloadDynamicLibrary(NonNullPtr<DynamicLibrary> lib);
+93 -96
View File
@@ -1,4 +1,4 @@
#pragma once
#pragma once
#include <Core/HAL/Display/Display.h>
#include <Core/HAL/Keyboard/Keyboard.h>
@@ -9,115 +9,112 @@
// Handles all events from systems handling the Hardware
// Very inspired by SDL3
namespace Juliet
enum class EventType : uint32
{
enum class EventType : uint32
{
None = 0,
First = None,
None = 0,
First = None,
// Application Events
// User querying an exit
Application_Exit = 100,
// OS terminating the application
Application_OS_Terminate,
Application_Begin = Application_Exit,
Application_End = Application_OS_Terminate,
// Application Events
// User querying an exit
Application_Exit = 100,
// OS terminating the application
Application_OS_Terminate,
Application_Begin = Application_Exit,
Application_End = Application_OS_Terminate,
// Window Events
Window_Close_Request = 200,
Window_Begin = Window_Close_Request,
Window_End = Window_Close_Request,
// Window Events
Window_Close_Request = 200,
Window_Begin = Window_Close_Request,
Window_End = Window_Close_Request,
// Keyboard Event
Key_Down = 300,
Key_Up,
Keyboard_Begin = Key_Down,
Keyboard_End = Key_Up,
// Keyboard Event
Key_Down = 300,
Key_Up,
Keyboard_Begin = Key_Down,
Keyboard_End = Key_Up,
// Mouse Event
Mouse_Move = 400,
Mouse_ButtonPressed,
Mouse_ButtonReleased,
// Mouse Event
Mouse_Move = 400,
Mouse_ButtonPressed,
Mouse_ButtonReleased,
Mouse_Begin = Mouse_Move,
Mouse_End = Mouse_ButtonReleased,
Mouse_Begin = Mouse_Move,
Mouse_End = Mouse_ButtonReleased,
Last // Get value from the previous one
};
Last // Get value from the previous one
};
struct WindowEvent
{
WindowID AssociatedWindowID;
uint32 DataPadding[2]; // TODO : define how much data param we need
};
struct WindowEvent
{
WindowID AssociatedWindowID;
uint32 DataPadding[2]; // TODO : define how much data param we need
};
struct KeyboardEvent
{
KeyboardID AssociatedKeyboardID;
WindowID WindowID;
Key Key;
KeyState KeyState;
KeyMod KeyModeState;
};
struct KeyboardEvent
{
KeyboardID AssociatedKeyboardID;
WindowID WindowID;
Key Key;
KeyState KeyState;
KeyMod KeyModeState;
};
// =====================================================
// Mouse Events
// =====================================================
struct MouseMovementEvent
{
MouseID AssociatedMouseID;
WindowID WindowID;
float X;
float Y;
float X_Displacement;
float Y_Displacement;
MouseButton ButtonState;
};
// =====================================================
// Mouse Events
// =====================================================
struct MouseMovementEvent
{
MouseID AssociatedMouseID;
WindowID WindowID;
float X;
float Y;
float X_Displacement;
float Y_Displacement;
MouseButton ButtonState;
};
struct MouseButtonEvent
{
MouseID AssociatedMouseID;
WindowID WindowID;
float X;
float Y;
MouseButton ButtonState;
bool IsPressed : 1;
};
struct MouseButtonEvent
{
MouseID AssociatedMouseID;
WindowID WindowID;
float X;
float Y;
MouseButton ButtonState;
bool IsPressed : 1;
};
// Tagged union representing ALL possible system events + a bit of data for custom event if needed
union AllSystemEventUnion
{
WindowEvent Window;
KeyboardEvent Keyboard;
MouseMovementEvent MouseMovement;
MouseButtonEvent MouseButton;
uint8 Padding[128]; // Make sure that the union is fixed in size and big enough on all platforms.
};
// Make sure we do not bust the union size
static_assert(sizeof(AllSystemEventUnion) == sizeof(((AllSystemEventUnion*)nullptr)->Padding));
// Tagged union representing ALL possible system events + a bit of data for custom event if needed
union AllSystemEventUnion
{
WindowEvent Window;
KeyboardEvent Keyboard;
MouseMovementEvent MouseMovement;
MouseButtonEvent MouseButton;
uint8 Padding[128]; // Make sure that the union is fixed in size and big enough on all platforms.
};
// Make sure we do not bust the union size
static_assert(sizeof(AllSystemEventUnion) == sizeof(((AllSystemEventUnion*)nullptr)->Padding));
struct SystemEvent
{
EventType Type;
uint64 Timestamp;
AllSystemEventUnion Data;
};
struct SystemEvent
{
EventType Type;
uint64 Timestamp;
AllSystemEventUnion Data;
};
// Poll for any event, return false if no event is available.
// Equivalent to WaitEvent(event, 0);
// Will not block
extern JULIET_API bool GetEvent(SystemEvent& event);
// Poll for any event, return false if no event is available.
// Equivalent to WaitEvent(event, 0);
// Will not block
extern JULIET_API bool GetEvent(SystemEvent& event);
// TODO : use chrono to tag the timeout correctly with nanosec
// timeout == -1 means wait for any event before pursuing
// timeout == 0 means checking once for the frame and getting out
// timeout > 0 means wait until time is out
extern JULIET_API bool WaitEvent(SystemEvent& event, int32 timeoutInNS = -1);
// TODO : use chrono to tag the timeout correctly with nanosec
// timeout == -1 means wait for any event before pursuing
// timeout == 0 means checking once for the frame and getting out
// timeout > 0 means wait until time is out
extern JULIET_API bool WaitEvent(SystemEvent& event, int32 timeoutInNS = -1);
// Add an event onto the event queue.
// TODO : support array of events
extern JULIET_API bool AddEvent(SystemEvent& event);
// Add an event onto the event queue.
// TODO : support array of events
extern JULIET_API bool AddEvent(SystemEvent& event);
extern void Events_NewFrame(float deltaTime);
} // namespace Juliet
extern void Events_NewFrame(float deltaTime);
+11 -14
View File
@@ -1,20 +1,17 @@
#pragma once
#pragma once
#include <Core/Common/String.h>
namespace Juliet
{
// Returns the path to the application directory
[[nodiscard]] extern JULIET_API String GetBasePath();
// Returns the path to the application directory
[[nodiscard]] extern JULIET_API String GetBasePath();
// Returns the resolved base path to the compiled shaders directory.
// In dev, this resolves to ../../Assets/compiled/ relative to the exe.
// In shipping, this resolves to Assets/Shaders/ next to the exe.
[[nodiscard]] extern JULIET_API String GetAssetBasePath();
// Returns the resolved base path to the compiled shaders directory.
// In dev, this resolves to ../../Assets/compiled/ relative to the exe.
// In shipping, this resolves to Assets/Shaders/ next to the exe.
[[nodiscard]] extern JULIET_API String GetAssetBasePath();
// Builds a full path to an asset file given its filename (e.g. "Triangle.vert.dxil").
// The caller owns the returned buffer and must free it.
[[nodiscard]] extern JULIET_API String GetAssetPath(String filename);
// Builds a full path to an asset file given its filename (e.g. "Triangle.vert.dxil").
// The caller owns the returned buffer and must free it.
[[nodiscard]] extern JULIET_API String GetAssetPath(NonNullPtr<Arena> arena, String filename);
[[nodiscard]]extern JULIET_API bool IsAbsolutePath(String path);
} // namespace Juliet
[[nodiscard]] extern JULIET_API bool IsAbsolutePath(String path);
+47 -50
View File
@@ -1,69 +1,66 @@
#pragma once
#pragma once
#include <Core/Common/NonNullPtr.h>
#include <Core/Common/String.h>
#include <Juliet.h>
namespace Juliet
// Opaque type
struct IOStream;
struct IOStreamDataPayload
{
// Opaque type
struct IOStream;
};
struct IOStreamDataPayload
{
};
enum class IOStreamStatus : uint8
{
Ready,
Error,
EndOfFile,
NotReady,
ReadOnly,
WriteOnly
};
enum class IOStreamStatus : uint8
{
Ready,
Error,
EndOfFile,
NotReady,
ReadOnly,
WriteOnly
};
enum class IOStreamSeekPivot : uint8
{
Begin,
Current,
End,
Count
};
enum class IOStreamSeekPivot : uint8
{
Begin,
Current,
End,
Count
};
// IOStream can be opened on a file or memory, or anything else.
// Use the interface to make it transparent to the user.
struct IOStreamInterface
{
uint32 Version;
// IOStream can be opened on a file or memory, or anything else.
// Use the interface to make it transparent to the user.
struct IOStreamInterface
{
uint32 Version;
int64 (*Size)(NonNullPtr<IOStreamDataPayload> data);
int64 (*Size)(NonNullPtr<IOStreamDataPayload> data);
int64 (*Seek)(NonNullPtr<IOStreamDataPayload> data, int64 offset, IOStreamSeekPivot pivot);
size_t (*Read)(NonNullPtr<IOStreamDataPayload> data, void* outBuffer, size_t size, NonNullPtr<IOStreamStatus> status);
size_t (*Write)(NonNullPtr<IOStreamDataPayload> data, ByteBuffer inBuffer, NonNullPtr<IOStreamStatus> status);
bool (*Flush)(NonNullPtr<IOStreamDataPayload> data, NonNullPtr<IOStreamStatus> status);
int64 (*Seek)(NonNullPtr<IOStreamDataPayload> data, int64 offset, IOStreamSeekPivot pivot);
size_t (*Read)(NonNullPtr<IOStreamDataPayload> data, void* outBuffer, size_t size, NonNullPtr<IOStreamStatus> status);
size_t (*Write)(NonNullPtr<IOStreamDataPayload> data, ByteBuffer inBuffer, NonNullPtr<IOStreamStatus> status);
bool (*Flush)(NonNullPtr<IOStreamDataPayload> data, NonNullPtr<IOStreamStatus> status);
bool (*Close)(NonNullPtr<IOStreamDataPayload> data);
};
bool (*Close)(NonNullPtr<IOStreamDataPayload> data);
};
extern JULIET_API IOStream* IOFromFile(NonNullPtr<Arena> arena, String filename, String mode);
extern JULIET_API IOStream* IOFromFile(String filename, String mode);
// Let you use an interface to open any io. Is used internally by IOFromFile
extern JULIET_API IOStream* IOFromInterface(NonNullPtr<Arena> arena, NonNullPtr<const IOStreamInterface> streamInterface,
NonNullPtr<IOStreamDataPayload> payload);
// Let you use an interface to open any io. Is used internally by IOFromFile
extern JULIET_API IOStream* IOFromInterface(NonNullPtr<const IOStreamInterface> streamInterface, NonNullPtr<IOStreamDataPayload> payload);
// Write formatted string into the stream.
extern JULIET_API size_t IOPrintf(NonNullPtr<IOStream> stream, _Printf_format_string_ const char* format, ...);
extern JULIET_API size_t IOWrite(NonNullPtr<IOStream> stream, ByteBuffer inBuffer);
// Write formatted string into the stream.
extern JULIET_API size_t IOPrintf(NonNullPtr<IOStream> stream, _Printf_format_string_ const char* format, ...);
extern JULIET_API size_t IOWrite(NonNullPtr<IOStream> stream, ByteBuffer inBuffer);
extern JULIET_API size_t IORead(NonNullPtr<IOStream> stream, void* ptr, size_t size);
extern JULIET_API int64 IOSeek(NonNullPtr<IOStream> stream, int64 offset, IOStreamSeekPivot pivot);
extern JULIET_API size_t IORead(NonNullPtr<IOStream> stream, void* ptr, size_t size);
extern JULIET_API int64 IOSeek(NonNullPtr<IOStream> stream, int64 offset, IOStreamSeekPivot pivot);
extern JULIET_API int64 IOSize(NonNullPtr<IOStream> stream);
extern JULIET_API int64 IOSize(NonNullPtr<IOStream> stream);
extern JULIET_API ByteBuffer LoadFile(NonNullPtr<Arena> arena, String filename);
extern JULIET_API ByteBuffer LoadFile(NonNullPtr<Arena> arena, NonNullPtr<IOStream> stream, bool closeStreamWhenDone);
// TODO : Use memory arena because that Allocates
extern JULIET_API ByteBuffer LoadFile(String filename);
extern JULIET_API ByteBuffer LoadFile(NonNullPtr<IOStream> stream, bool closeStreamWhenDone);
extern JULIET_API bool IOClose(NonNullPtr<IOStream> stream);
} // namespace Juliet
extern JULIET_API bool IOClose(NonNullPtr<IOStream> stream);
+184 -189
View File
@@ -1,193 +1,188 @@
#pragma once
#pragma once
#include <Core/Common/CoreTypes.h>
namespace Juliet
// Represents a Virtual Key corresponding to the Physical key, but localized using the keyboard layout
// ScanCode reprensent US ASCII Keyboard
// WASD Scan codes are ZQSD in KeyCode for French keyboard
// We use the ASCII value of the generated character as value, when possible.
// Keys that do not produce a character are converted to an abritrary value high enough to not conflict
// Reference: https://www.asciitable.com/
// Reference: https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-6.0/aa299374(v=vs.60)
enum class KeyCode : uint32
{
// Represents a Virtual Key corresponding to the Physical key, but localized using the keyboard layout
// ScanCode reprensent US ASCII Keyboard
// WASD Scan codes are ZQSD in KeyCode for French keyboard
// We use the ASCII value of the generated character as value, when possible.
// Keys that do not produce a character are converted to an abritrary value high enough to not conflict
// Reference: https://www.asciitable.com/
// Reference: https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-6.0/aa299374(v=vs.60)
enum class KeyCode : uint32
{
Unknown = 0x0, // 0
Unsupported = 0x0, // 0
Return = 0X0Du, // '\r'
Escape = 0X1Bu, // '\X1B'
Backspace = 0X08u, // '\b'
Tab = 0X09u, // '\t'
Space = 0X20u, // ' '
ExclamationPoint = 0X21u, // '!'
DoubleApostrophe = 0X22u, // '"'
Hash = 0X23u, // '#'
Dollar = 0X24u, // '$'
Percent = 0X25u, // '%'
Ampersand = 0X26u, // '&'
Apostrophe = 0X27u, // '\''
LeftParenthesis = 0X28u, // '('
RightParenthesis = 0X29u, // ')'
Asterisk = 0X2Au, // '*'
Plus = 0X2Bu, // '+'
Comma = 0X2Cu, // ','
Minus = 0X2Du, // '-'
Period = 0X2Eu, // '.'
Slash = 0X2Fu, // '/'
Num0 = 0X30u, // '0'
Num1 = 0X31u, // '1'
Num2 = 0X32u, // '2'
Num3 = 0X33u, // '3'
Num4 = 0X34u, // '4'
Num5 = 0X35u, // '5'
Num6 = 0X36u, // '6'
Num7 = 0X37u, // '7'
Num8 = 0X38u, // '8'
Num9 = 0X39u, // '9'
Colon = 0X3Au, // ':'
Semicolon = 0X3Bu, // ';'
LessThan = 0X3Cu, // '<'
Equals = 0X3Du, // '='
GreaterThan = 0X3Eu, // '>'
QuestionMark = 0X3Fu, // '?'
CommercialAt = 0x40u, // '@'
LeftBracket = 0X5Bu, // '['
Backslash = 0X5Cu, // '\\'
RightBracket = 0X5DU, // ']'
Caret = 0X5Eu, // '^'
Underscore = 0X5Fu, // '_'
GraveAccent = 0X60u, // '`'
A = 0x61u, // 'a'
B = 0x62u, // 'b'
C = 0x63u, // 'c'
D = 0x64u, // 'd'
E = 0x65u, // 'e'
F = 0x66u, // 'f'
G = 0x67u, // 'g'
H = 0x68u, // 'h'
I = 0x69u, // 'i'
J = 0x6Au, // 'j'
K = 0x6Bu, // 'k'
L = 0x6CU, // 'l'
M = 0x6DU, // 'm'
N = 0x6Eu, // 'n'
O = 0x6Fu, // 'o'
P = 0x70u, // 'p'
Q = 0x71u, // 'q'
R = 0x72u, // 'r'
S = 0x73u, // 's'
T = 0x74u, // 't'
U = 0x75u, // 'y'
V = 0x76u, // 'v'
W = 0x77u, // 'w'
X = 0x78u, // 'x'
Y = 0x79u, // 'y'
Z = 0x7Au, // 'z'
LeftBrace = 0x7BU, // '{'
Pipe = 0x7CU, // '|'
RightBrace = 0x7DU, // '}'
Tilde = 0x7Eu, // '~'
Delete = 0x7Fu, // '\x7F'
PlusMinus = 0xb1u, // '\xB1'
Unknown = 0x0, // 0
Unsupported = 0x0, // 0
Return = 0X0Du, // '\r'
Escape = 0X1Bu, // '\X1B'
Backspace = 0X08u, // '\b'
Tab = 0X09u, // '\t'
Space = 0X20u, // ' '
ExclamationPoint = 0X21u, // '!'
DoubleApostrophe = 0X22u, // '"'
Hash = 0X23u, // '#'
Dollar = 0X24u, // '$'
Percent = 0X25u, // '%'
Ampersand = 0X26u, // '&'
Apostrophe = 0X27u, // '\''
LeftParenthesis = 0X28u, // '('
RightParenthesis = 0X29u, // ')'
Asterisk = 0X2Au, // '*'
Plus = 0X2Bu, // '+'
Comma = 0X2Cu, // ','
Minus = 0X2Du, // '-'
Period = 0X2Eu, // '.'
Slash = 0X2Fu, // '/'
Num0 = 0X30u, // '0'
Num1 = 0X31u, // '1'
Num2 = 0X32u, // '2'
Num3 = 0X33u, // '3'
Num4 = 0X34u, // '4'
Num5 = 0X35u, // '5'
Num6 = 0X36u, // '6'
Num7 = 0X37u, // '7'
Num8 = 0X38u, // '8'
Num9 = 0X39u, // '9'
Colon = 0X3Au, // ':'
Semicolon = 0X3Bu, // ';'
LessThan = 0X3Cu, // '<'
Equals = 0X3Du, // '='
GreaterThan = 0X3Eu, // '>'
QuestionMark = 0X3Fu, // '?'
CommercialAt = 0x40u, // '@'
LeftBracket = 0X5Bu, // '['
Backslash = 0X5Cu, // '\\'
RightBracket = 0X5DU, // ']'
Caret = 0X5Eu, // '^'
Underscore = 0X5Fu, // '_'
GraveAccent = 0X60u, // '`'
A = 0x61u, // 'a'
B = 0x62u, // 'b'
C = 0x63u, // 'c'
D = 0x64u, // 'd'
E = 0x65u, // 'e'
F = 0x66u, // 'f'
G = 0x67u, // 'g'
H = 0x68u, // 'h'
I = 0x69u, // 'i'
J = 0x6Au, // 'j'
K = 0x6Bu, // 'k'
L = 0x6CU, // 'l'
M = 0x6DU, // 'm'
N = 0x6Eu, // 'n'
O = 0x6Fu, // 'o'
P = 0x70u, // 'p'
Q = 0x71u, // 'q'
R = 0x72u, // 'r'
S = 0x73u, // 's'
T = 0x74u, // 't'
U = 0x75u, // 'y'
V = 0x76u, // 'v'
W = 0x77u, // 'w'
X = 0x78u, // 'x'
Y = 0x79u, // 'y'
Z = 0x7Au, // 'z'
LeftBrace = 0x7BU, // '{'
Pipe = 0x7CU, // '|'
RightBrace = 0x7DU, // '}'
Tilde = 0x7Eu, // '~'
Delete = 0x7Fu, // '\x7F'
PlusMinus = 0xb1u, // '\xB1'
// Keys not producing a character
// Based on SDL Algo: ScanCode | 0x40000000
CapsLock = 0x40000039u,
F1 = 0x4000003Au,
F2 = 0x4000003Bu,
F3 = 0x4000003CU,
F4 = 0x4000003DU,
F5 = 0x4000003Eu,
F6 = 0x4000003Fu,
F7 = 0x40000040u,
F8 = 0x40000041u,
F9 = 0x40000042u,
F10 = 0x40000043u,
F11 = 0x40000044u,
F12 = 0x40000045u,
PrintScreen = 0x40000046u,
ScrollLock = 0x40000047u,
Pause = 0x40000048u,
Insert = 0x40000049u,
Home = 0x4000004Au,
PageUp = 0x4000004Bu,
End = 0x4000004DU,
PageDown = 0x4000004Eu,
RightArrow = 0x4000004Fu,
LeftArrow = 0x40000050u,
DownArrow = 0x40000051u,
UpArrow = 0x40000052u,
NumlockClear = 0x40000053u,
KeyPad_Divide = 0x40000054u,
KeyPad_Multiply = 0x40000055u,
KeyPad_Minus = 0x40000056u,
KeyPad_Plus = 0x40000057u,
KeyPad_Enter = 0x40000058u,
KeyPad_Num1 = 0x40000059u,
KeyPad_Num2 = 0x4000005Au,
KeyPad_Num3 = 0x4000005Bu,
KeyPad_Num4 = 0x4000005Cu,
KeyPad_Num5 = 0x4000005Du,
KeyPad_Num6 = 0x4000005Eu,
KeyPad_Num7 = 0x4000005Fu,
KeyPad_Num8 = 0x40000060u,
KeyPad_Num9 = 0x40000061u,
KeyPad_Num0 = 0x40000062u,
KeyPad_Period = 0x40000063u,
Power = 0x40000066u,
KeyPad_Equals = 0x40000067u,
F13 = 0x40000068u,
F14 = 0x40000069u,
F15 = 0x4000006Au,
F16 = 0x4000006Bu,
F17 = 0x4000006Cu,
F18 = 0x4000006Du,
F19 = 0x4000006Eu,
F20 = 0x4000006Fu,
F21 = 0x40000070u,
F22 = 0x40000071u,
F23 = 0x40000072u,
F24 = 0x40000073u,
Mute = 0x4000007Fu,
VolumeUp = 0x40000080u,
VolumeDown = 0x40000081u,
KeyPad_Comma = 0x40000085u,
LeftControl = 0x400000E0u,
LeftShift = 0x400000E1u,
LeftAlt = 0x400000E2u,
LeftOSCommand = 0x400000E3u,
RightControl = 0x400000E4u,
RightShift = 0x400000E5u,
RightAlt = 0x400000E6u,
RightOSCommand = 0x400000E7u,
Sleep = 0x40000102u,
WakeUp = 0x40000103u,
Media_NextTrack = 0x4000010Bu,
Media_PreviousTrack = 0x4000010Cu,
Media_Stop = 0x4000010Du,
Media_Eject = 0x4000010Eu,
Media_PlayPause = 0x4000010Fu,
Media_Select = 0x40000110u,
};
// Keys not producing a character
// Based on SDL Algo: ScanCode | 0x40000000
CapsLock = 0x40000039u,
F1 = 0x4000003Au,
F2 = 0x4000003Bu,
F3 = 0x4000003CU,
F4 = 0x4000003DU,
F5 = 0x4000003Eu,
F6 = 0x4000003Fu,
F7 = 0x40000040u,
F8 = 0x40000041u,
F9 = 0x40000042u,
F10 = 0x40000043u,
F11 = 0x40000044u,
F12 = 0x40000045u,
PrintScreen = 0x40000046u,
ScrollLock = 0x40000047u,
Pause = 0x40000048u,
Insert = 0x40000049u,
Home = 0x4000004Au,
PageUp = 0x4000004Bu,
End = 0x4000004DU,
PageDown = 0x4000004Eu,
RightArrow = 0x4000004Fu,
LeftArrow = 0x40000050u,
DownArrow = 0x40000051u,
UpArrow = 0x40000052u,
NumlockClear = 0x40000053u,
KeyPad_Divide = 0x40000054u,
KeyPad_Multiply = 0x40000055u,
KeyPad_Minus = 0x40000056u,
KeyPad_Plus = 0x40000057u,
KeyPad_Enter = 0x40000058u,
KeyPad_Num1 = 0x40000059u,
KeyPad_Num2 = 0x4000005Au,
KeyPad_Num3 = 0x4000005Bu,
KeyPad_Num4 = 0x4000005Cu,
KeyPad_Num5 = 0x4000005Du,
KeyPad_Num6 = 0x4000005Eu,
KeyPad_Num7 = 0x4000005Fu,
KeyPad_Num8 = 0x40000060u,
KeyPad_Num9 = 0x40000061u,
KeyPad_Num0 = 0x40000062u,
KeyPad_Period = 0x40000063u,
Power = 0x40000066u,
KeyPad_Equals = 0x40000067u,
F13 = 0x40000068u,
F14 = 0x40000069u,
F15 = 0x4000006Au,
F16 = 0x4000006Bu,
F17 = 0x4000006Cu,
F18 = 0x4000006Du,
F19 = 0x4000006Eu,
F20 = 0x4000006Fu,
F21 = 0x40000070u,
F22 = 0x40000071u,
F23 = 0x40000072u,
F24 = 0x40000073u,
Mute = 0x4000007Fu,
VolumeUp = 0x40000080u,
VolumeDown = 0x40000081u,
KeyPad_Comma = 0x40000085u,
LeftControl = 0x400000E0u,
LeftShift = 0x400000E1u,
LeftAlt = 0x400000E2u,
LeftOSCommand = 0x400000E3u,
RightControl = 0x400000E4u,
RightShift = 0x400000E5u,
RightAlt = 0x400000E6u,
RightOSCommand = 0x400000E7u,
Sleep = 0x40000102u,
WakeUp = 0x40000103u,
Media_NextTrack = 0x4000010Bu,
Media_PreviousTrack = 0x4000010Cu,
Media_Stop = 0x4000010Du,
Media_Eject = 0x4000010Eu,
Media_PlayPause = 0x4000010Fu,
Media_Select = 0x40000110u,
};
enum class KeyMod : uint16
{
None = 0b0,
LeftShift = 0b0000'0000'0001u,
RightShift = 0b0000'0000'0010u,
LeftControl = 0b0000'0000'0100u,
RightControl = 0b0000'0000'1000u,
LeftAlt = 0b0000'0001'000u,
RightAlt = 0b0000'0010'0000u,
LeftOSCommand = 0b0000'0100'0000u,
RightOSCommand = 0b0000'1000'0000u,
NumLock = 0b0001'0000'0000u,
CapsLock = 0b0010'0000'0000u,
ScrollLock = 0b0100'0000'0000u,
Control = LeftControl | RightControl,
Shift = LeftShift | RightShift,
Alt = LeftAlt | RightAlt,
OSCommand = LeftOSCommand | RightOSCommand,
};
} // namespace Juliet
enum class KeyMod : uint16
{
None = 0b0,
LeftShift = 0b0000'0000'0001u,
RightShift = 0b0000'0000'0010u,
LeftControl = 0b0000'0000'0100u,
RightControl = 0b0000'0000'1000u,
LeftAlt = 0b0000'0001'000u,
RightAlt = 0b0000'0010'0000u,
LeftOSCommand = 0b0000'0100'0000u,
RightOSCommand = 0b0000'1000'0000u,
NumLock = 0b0001'0000'0000u,
CapsLock = 0b0010'0000'0000u,
ScrollLock = 0b0100'0000'0000u,
Control = LeftControl | RightControl,
Shift = LeftShift | RightShift,
Alt = LeftAlt | RightAlt,
OSCommand = LeftOSCommand | RightOSCommand,
};
+22 -25
View File
@@ -1,35 +1,32 @@
#pragma once
#pragma once
#include <Core/HAL/Keyboard/KeyCode.h>
#include <Core/HAL/Keyboard/ScanCode.h>
#include <Juliet.h>
namespace Juliet
using KeyboardID = uint8;
enum class KeyPosition : bool
{
using KeyboardID = uint8;
Up = false,
Down = true
};
enum class KeyPosition : bool
{
Up = false,
Down = true
};
struct KeyState
{
KeyPosition Position;
float Time;
};
struct KeyState
{
KeyPosition Position;
float Time;
};
struct Key
{
ScanCode ScanCode;
KeyCode KeyCode;
uint16 Raw;
};
struct Key
{
ScanCode ScanCode;
KeyCode KeyCode;
uint16 Raw;
};
extern JULIET_API bool IsKeyDown(ScanCode scanCode);
extern JULIET_API bool IsKeyPressed(ScanCode scanCode);
extern JULIET_API bool IsKeyDown(ScanCode scanCode);
extern JULIET_API bool IsKeyPressed(ScanCode scanCode);
extern JULIET_API KeyMod GetKeyModState();
extern JULIET_API KeyCode GetKeyCodeFromScanCode(ScanCode scanCode, KeyMod keyModState);
} // namespace Juliet
extern JULIET_API KeyMod GetKeyModState();
extern JULIET_API KeyCode GetKeyCodeFromScanCode(ScanCode scanCode, KeyMod keyModState);
+162 -165
View File
@@ -1,186 +1,183 @@
#pragma once
#pragma once
namespace Juliet
// Follow the HID Usage page for USB
// https://usb.org/sites/default/files/hut1_5.pdf
// 0 to 256 Is dedicated to Keyboard Usage Page (0x07)
// 257 to 286 Is dedicated to Consumer Usage Page (0xC)
// 287 to 511 Is not implemented. Could be used for Mobile or Consoles
// ScanCode reprensent Physical Keys and Buttons
enum class ScanCode : uint16
{
// Follow the HID Usage page for USB
// https://usb.org/sites/default/files/hut1_5.pdf
// 0 to 256 Is dedicated to Keyboard Usage Page (0x07)
// 257 to 286 Is dedicated to Consumer Usage Page (0xC)
// 287 to 511 Is not implemented. Could be used for Mobile or Consoles
// ScanCode reprensent Physical Keys and Buttons
enum class ScanCode : uint16
{
Unknown = 0,
Unsupported = 0,
Unknown = 0,
Unsupported = 0,
A = 4,
B = 5,
C = 6,
D = 7,
E = 8,
F = 9,
G = 10,
H = 11,
I = 12,
J = 13,
K = 14,
L = 15,
M = 16,
N = 17,
O = 18,
P = 19,
Q = 20,
R = 21,
S = 22,
T = 23,
U = 24,
V = 25,
W = 26,
X = 27,
Y = 28,
Z = 29,
A = 4,
B = 5,
C = 6,
D = 7,
E = 8,
F = 9,
G = 10,
H = 11,
I = 12,
J = 13,
K = 14,
L = 15,
M = 16,
N = 17,
O = 18,
P = 19,
Q = 20,
R = 21,
S = 22,
T = 23,
U = 24,
V = 25,
W = 26,
X = 27,
Y = 28,
Z = 29,
Num1 = 30,
Num2 = 31,
Num3 = 32,
Num4 = 33,
Num5 = 34,
Num6 = 35,
Num7 = 36,
Num8 = 37,
Num9 = 38,
Num0 = 39,
Num1 = 30,
Num2 = 31,
Num3 = 32,
Num4 = 33,
Num5 = 34,
Num6 = 35,
Num7 = 36,
Num8 = 37,
Num9 = 38,
Num0 = 39,
Return = 40,
Escape = 41,
Backspace = 42,
Tab = 43,
Space = 44,
Return = 40,
Escape = 41,
Backspace = 42,
Tab = 43,
Space = 44,
Minus = 45,
Equals = 46,
LeftBracket = 47,
RightBracket = 48,
Backslash = 49,
NonUSHash = 50, // Same as 49 but for ISO keyboards
Semicolon = 51,
Apostrophe = 52,
GraveAccent = 53,
Comma = 54,
Period = 55,
Slash = 56,
CapsLock = 57,
Minus = 45,
Equals = 46,
LeftBracket = 47,
RightBracket = 48,
Backslash = 49,
NonUSHash = 50, // Same as 49 but for ISO keyboards
Semicolon = 51,
Apostrophe = 52,
GraveAccent = 53,
Comma = 54,
Period = 55,
Slash = 56,
CapsLock = 57,
F1 = 58,
F2 = 59,
F3 = 60,
F4 = 61,
F5 = 62,
F6 = 63,
F7 = 64,
F8 = 65,
F9 = 66,
F10 = 67,
F11 = 68,
F12 = 69,
F1 = 58,
F2 = 59,
F3 = 60,
F4 = 61,
F5 = 62,
F6 = 63,
F7 = 64,
F8 = 65,
F9 = 66,
F10 = 67,
F11 = 68,
F12 = 69,
PrintScreen = 70,
ScrollLock = 71,
Pause = 72,
Insert = 73,
PrintScreen = 70,
ScrollLock = 71,
Pause = 72,
Insert = 73,
Home = 74,
PageUp = 75,
Delete = 76,
End = 77,
PageDown = 78,
RightArrow = 79,
LeftArrow = 80,
DownArrow = 81,
UpArrow = 82,
Home = 74,
PageUp = 75,
Delete = 76,
End = 77,
PageDown = 78,
RightArrow = 79,
LeftArrow = 80,
DownArrow = 81,
UpArrow = 82,
NumlockClear = 83, // Pc = Numlock / Mac = Clear
NumlockClear = 83, // Pc = Numlock / Mac = Clear
KeyPad_Divide = 84,
KeyPad_Multiply = 85,
KeyPad_Minus = 86,
KeyPad_Plus = 87,
KeyPad_Enter = 88,
KeyPad_Num1 = 89,
KeyPad_Num2 = 90,
KeyPad_Num3 = 91,
KeyPad_Num4 = 92,
KeyPad_Num5 = 93,
KeyPad_Num6 = 94,
KeyPad_Num7 = 95,
KeyPad_Num8 = 96,
KeyPad_Num9 = 97,
KeyPad_Num0 = 98,
KeyPad_Period = 99,
KeyPad_Divide = 84,
KeyPad_Multiply = 85,
KeyPad_Minus = 86,
KeyPad_Plus = 87,
KeyPad_Enter = 88,
KeyPad_Num1 = 89,
KeyPad_Num2 = 90,
KeyPad_Num3 = 91,
KeyPad_Num4 = 92,
KeyPad_Num5 = 93,
KeyPad_Num6 = 94,
KeyPad_Num7 = 95,
KeyPad_Num8 = 96,
KeyPad_Num9 = 97,
KeyPad_Num0 = 98,
KeyPad_Period = 99,
NonUSBackslash = 100, // ISO keyboards only
Power = 102, // Some mac have a Power key
NonUSBackslash = 100, // ISO keyboards only
Power = 102, // Some mac have a Power key
KeyPad_Equals = 103,
F13 = 104,
F14 = 105,
F15 = 106,
F16 = 107,
F17 = 108,
F18 = 109,
F19 = 110,
F20 = 111,
F21 = 112,
F22 = 113,
F23 = 114,
F24 = 115,
KeyPad_Equals = 103,
F13 = 104,
F14 = 105,
F15 = 106,
F16 = 107,
F17 = 108,
F18 = 109,
F19 = 110,
F20 = 111,
F21 = 112,
F22 = 113,
F23 = 114,
F24 = 115,
Mute = 127,
VolumeUp = 128,
VolumeDown = 129,
Mute = 127,
VolumeUp = 128,
VolumeDown = 129,
KeyPad_Comma = 133,
KeyPad_Comma = 133,
International1 = 135, // Mostly used on Asian keyboards
International2 = 136,
International3 = 137, // Yen Symbol
International4 = 138,
International5 = 139,
International6 = 140,
International7 = 141,
International8 = 142,
International9 = 143,
Lang1 = 144, // Hangul (Korean)
Lang2 = 145, // Hanja (Korean)
Lang3 = 146, // Katakana (Japanese)
Lang4 = 147, // Hiragana (Japanese)
Lang5 = 148, // Zenkaku/Hankaku (Japanese)
Lang6 = 149, // Unused
Lang7 = 150, // Unused
Lang8 = 151, // Unused
Lang9 = 152, // Unused
International1 = 135, // Mostly used on Asian keyboards
International2 = 136,
International3 = 137, // Yen Symbol
International4 = 138,
International5 = 139,
International6 = 140,
International7 = 141,
International8 = 142,
International9 = 143,
Lang1 = 144, // Hangul (Korean)
Lang2 = 145, // Hanja (Korean)
Lang3 = 146, // Katakana (Japanese)
Lang4 = 147, // Hiragana (Japanese)
Lang5 = 148, // Zenkaku/Hankaku (Japanese)
Lang6 = 149, // Unused
Lang7 = 150, // Unused
Lang8 = 151, // Unused
Lang9 = 152, // Unused
LeftControl = 224,
LeftShift = 225,
LeftAlt = 226, // Alt for PC, Option for Mac
LeftOSCommand = 227, // Window key for PC, Command for Mac
RightControl = 228,
RightShift = 229,
RightAlt = 230, // Alt Gr for PC, Option for Mac
RightOSCommand = 231, // Window key for PC, Command for Mac
LeftControl = 224,
LeftShift = 225,
LeftAlt = 226, // Alt for PC, Option for Mac
LeftOSCommand = 227, // Window key for PC, Command for Mac
RightControl = 228,
RightShift = 229,
RightAlt = 230, // Alt Gr for PC, Option for Mac
RightOSCommand = 231, // Window key for PC, Command for Mac
Sleep = 258,
WakeUp = 259,
Sleep = 258,
WakeUp = 259,
Media_NextTrack = 267,
Media_PreviousTrack = 268,
Media_Stop = 269,
Media_Eject = 270,
Media_PlayPause = 271,
Media_Select = 272,
Media_NextTrack = 267,
Media_PreviousTrack = 268,
Media_Stop = 269,
Media_Eject = 270,
Media_PlayPause = 271,
Media_Select = 272,
Reserved = 287,
Reserved = 287,
Count = 512
};
} // namespace Juliet
Count = 512
};
+21 -24
View File
@@ -1,28 +1,25 @@
#pragma once
#pragma once
namespace Juliet
using MouseID = uint8;
enum class MouseButton : uint8
{
using MouseID = uint8;
None = 0,
Left = 1 << 0,
Right = 1 << 1,
Middle = 1 << 2,
Button1 = 1 << 3,
Button2 = 1 << 4,
};
enum class MouseButton : uint8
{
None = 0,
Left = 1 << 0,
Right = 1 << 1,
Middle = 1 << 2,
Button1 = 1 << 3,
Button2 = 1 << 4,
};
// TODO : Replace by Vector2f
struct MousePosition
{
float X;
float Y;
};
// TODO : Replace by Vector2f
struct MousePosition
{
float X;
float Y;
};
JULIET_API extern bool IsMouseButtonDown(MouseButton button);
JULIET_API extern MousePosition GetMousePosition();
JULIET_API extern MousePosition GetMouseDelta();
JULIET_API extern MouseButton GetMouseButtonState();
} // namespace Juliet
JULIET_API extern bool IsMouseButtonDown(MouseButton button);
JULIET_API extern MousePosition GetMousePosition();
JULIET_API extern MousePosition GetMouseDelta();
JULIET_API extern MouseButton GetMouseButtonState();
+35 -41
View File
@@ -1,49 +1,43 @@
#pragma once
#pragma once
#include <Core/Common/CoreTypes.h>
#include <Juliet.h>
namespace Juliet
namespace Memory
{
namespace Memory
Byte* OS_Reserve(size_t size);
bool OS_Commit(Byte* ptr, size_t size);
void OS_Release(Byte* ptr, size_t size);
template <typename Type>
Type* OS_Reserve(size_t size)
{
Byte* OS_Reserve(size_t size);
bool OS_Commit(Byte* ptr, size_t size);
void OS_Release(Byte* ptr, size_t size);
return reinterpret_cast<Type*>(OS_Reserve(size));
}
template <typename Type>
Type* OS_Reserve(size_t size)
{
return reinterpret_cast<Type*>(OS_Reserve(size));
}
template <typename Type>
bool OS_Commit(Type* ptr, size_t size)
{
return OS_Commit(reinterpret_cast<Byte*>(ptr), size);
}
template <typename Type>
void OS_Release(Type* ptr, size_t size)
{
OS_Release(reinterpret_cast<Byte*>(ptr), size);
}
} // namespace Memory
namespace Time
template <typename Type>
bool OS_Commit(Type* ptr, size_t size)
{
uint64 Timestamp();
void ComputeDeltaTime();
float GetDeltaTime();
uint64 GetFrameNumber();
} // namespace Time
return OS_Commit(reinterpret_cast<Byte*>(ptr), size);
}
namespace Debug
template <typename Type>
void OS_Release(Type* ptr, size_t size)
{
JULIET_API bool IsDebuggerPresent();
} // namespace Debug
OS_Release(reinterpret_cast<Byte*>(ptr), size);
}
using EntryPointFunc = int (*)(int, wchar_t**);
JULIET_API int Bootstrap(EntryPointFunc entryPointFunc, int argc, wchar_t** argv);
} // namespace Juliet
} // namespace Memory
namespace Time
{
uint64 Timestamp();
void ComputeDeltaTime();
float GetDeltaTime();
uint64 GetFrameNumber();
} // namespace Time
namespace Debug
{
JULIET_API bool IsDebuggerPresent();
} // namespace Debug
using EntryPointFunc = int (*)(int, wchar_t**);
JULIET_API int Bootstrap(EntryPointFunc entryPointFunc, int argc, wchar_t** argv);
+23 -27
View File
@@ -1,39 +1,35 @@
#pragma once
#pragma once
#include <Core/Common/String.h>
namespace Juliet
// Fwd Declare
struct DynamicLibrary;
struct HotReloadCode
{
// Fwd Declare
struct DynamicLibrary;
String DLLFullPath;
String LockFullPath;
String TransientDLLName;
struct HotReloadCode
{
Arena* Arena;
uint64 LastWriteTime;
String DLLFullPath;
String LockFullPath;
String TransientDLLName;
DynamicLibrary* Dll;
uint64 LastWriteTime;
void** Functions;
const char** FunctionNames;
uint32 FunctionCount;
DynamicLibrary* Dll;
uint32 UniqueID;
void** Functions;
const char** FunctionNames;
uint32 FunctionCount;
bool IsValid : 1;
};
uint32 UniqueID;
extern JULIET_API void InitHotReloadCode(NonNullPtr<Arena> arena, HotReloadCode& code, String dllName,
String transientDllName, String lockFilename);
extern JULIET_API void ShutdownHotReloadCode(HotReloadCode& code);
bool IsValid : 1;
};
extern JULIET_API void LoadCode(HotReloadCode& code);
extern JULIET_API void UnloadCode(HotReloadCode& code);
extern JULIET_API void InitHotReloadCode(HotReloadCode& code, String dllName, String transientDllName, String lockFilename);
extern JULIET_API void ShutdownHotReloadCode(HotReloadCode& code);
extern JULIET_API void LoadCode(HotReloadCode& code);
extern JULIET_API void UnloadCode(HotReloadCode& code);
extern JULIET_API void ReloadCode(HotReloadCode& code);
extern JULIET_API bool ShouldReloadCode(const HotReloadCode& code);
} // namespace Juliet
extern JULIET_API void ReloadCode(HotReloadCode& code);
extern JULIET_API bool ShouldReloadCode(const HotReloadCode& code);
+14 -18
View File
@@ -1,31 +1,27 @@
#pragma once
#pragma once
#include <Core/Common/CoreTypes.h>
#include <Core/Common/NonNullPtr.h>
#ifdef JULIET_ENABLE_IMGUI
struct ImGuiContext;
namespace Juliet
struct Window;
struct GraphicsDevice;
namespace ImGuiService
{
struct Window;
struct GraphicsDevice;
JULIET_API void Initialize(NonNullPtr<Window> window);
JULIET_API void Shutdown();
namespace ImGuiService
{
JULIET_API void Initialize(NonNullPtr<Window> window);
JULIET_API void Shutdown();
JULIET_API void NewFrame();
JULIET_API void Render();
JULIET_API void NewFrame();
JULIET_API void Render();
JULIET_API bool IsInitialized();
JULIET_API ImGuiContext* GetContext();
JULIET_API bool IsInitialized();
JULIET_API ImGuiContext* GetContext();
// Run internal unit tests
JULIET_API void RunTests();
} // namespace ImGuiService
} // namespace Juliet
// Run internal unit tests
JULIET_API void RunTests();
} // namespace ImGuiService
#endif // JULIET_ENABLE_IMGUI
+2 -2
View File
@@ -1,4 +1,4 @@
#pragma once
#pragma once
#include <Core/Common/NonNullPtr.h>
#include <Core/HAL/Display/Window.h>
@@ -6,7 +6,7 @@
#include <Juliet.h>
namespace Juliet::UnitTest
namespace UnitTest
{
void TestImGui();
}
+16 -21
View File
@@ -1,26 +1,21 @@
#pragma once
#pragma once
#include <Core/Common/CoreTypes.h>
namespace Juliet
enum class JulietInit_Flags : uint8
{
enum class JulietInit_Flags : uint8
{
None = 0,
Display = 1 << 0,
Audio = 1 << 1,
Count = Audio,
All = 0xFb
};
None = 0,
Display = 1 << 0,
Audio = 1 << 1,
Count = Audio,
All = 0xFb
};
struct Arena;
struct Arena;
struct GameData
{
struct GameState* GameState;
Arena* ScratchArena;
};
struct GameData
{
struct GameState* GameState;
Arena* ScratchArena;
};
void JulietInit(JulietInit_Flags flags);
void JulietShutdown();
} // namespace Juliet
void JulietInit(JulietInit_Flags flags);
void JulietShutdown();
+13 -18
View File
@@ -1,29 +1,24 @@
#pragma once
#pragma once
#include <Core/Common/CoreTypes.h>
#include <Core/Common/NonNullPtr.h>
#include <Core/Memory/MemoryArena.h>
#include <Juliet.h>
// TODO : Juliet strings
// TODO Juliet Containers + Allocators...
// TODO: Juliet chrono, because it prevents me from doing #define global static
namespace Juliet
{
enum class LogLevel : uint8;
enum class LogCategory : uint8;
enum class LogLevel : uint8;
enum class LogCategory : uint8;
extern void JULIET_API InitializeLogManager();
extern void JULIET_API ShutdownLogManager();
extern void JULIET_API InitializeLogManager();
extern void JULIET_API ShutdownLogManager();
extern void JULIET_API LogScopeBegin();
// TODO everything that happened in there to export them to file or something
extern void JULIET_API LogScopeEnd();
extern void JULIET_API LogScopeBegin();
// TODO everything that happened in there to export them to file or something
extern void JULIET_API LogScopeEnd();
extern void JULIET_API Log(LogLevel level, LogCategory category, const char* fmt, ...);
extern void JULIET_API LogDebug(LogCategory category, const char* fmt, ...);
extern void JULIET_API LogMessage(LogCategory category, const char* fmt, ...);
extern void JULIET_API LogWarning(LogCategory category, const char* fmt, ...);
extern void JULIET_API LogError(LogCategory category, const char* fmt, ...);
} // namespace Juliet
extern void JULIET_API Log(LogLevel level, LogCategory category, const char* fmt, ...);
extern void JULIET_API LogDebug(LogCategory category, const char* fmt, ...);
extern void JULIET_API LogMessage(LogCategory category, const char* fmt, ...);
extern void JULIET_API LogWarning(LogCategory category, const char* fmt, ...);
extern void JULIET_API LogError(LogCategory category, const char* fmt, ...);
+16 -19
View File
@@ -1,22 +1,19 @@
#pragma once
#pragma once
namespace Juliet
enum class LogLevel : uint8
{
enum class LogLevel : uint8
{
Debug = 0,
Message = 1,
Warning = 2,
Error = 3,
};
Debug = 0,
Message = 1,
Warning = 2,
Error = 3,
};
enum class LogCategory : uint8
{
Core = 0,
Graphics = 1,
Networking = 2,
Engine = 3,
Tool = 4,
Game = 5,
};
} // namespace Juliet
enum class LogCategory : uint8
{
Core = 0,
Graphics = 1,
Networking = 2,
Engine = 3,
Tool = 4,
Game = 5,
};
+4 -4
View File
@@ -1,4 +1,4 @@
#pragma once
#pragma once
#include <Core/HAL/OS/OS.h>
@@ -12,12 +12,12 @@ extern int JulietMain(int, wchar_t**);
#if UNICODE
int wmain(int argc, wchar_t** argv)
{
return Juliet::Bootstrap(JulietMain, argc, argv);
return Bootstrap(JulietMain, argc, argv);
}
#else
int main(int argc, char** argv)
{
return Juliet::Bootstrap(JulietMain, argc, argv);
return Bootstrap(JulietMain, argc, argv);
}
#endif
@@ -38,7 +38,7 @@ int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int sw)
(void)szCmdLine;
(void)sw;
return Juliet::Bootstrap(JulietMain, __argc, __wargv);
return Bootstrap(JulietMain, __argc, __wargv);
}
}
#else
+34 -40
View File
@@ -1,52 +1,46 @@
#pragma once
#pragma once
#include <Core/Common/CoreTypes.h>
#include <Juliet.h>
extern JULIET_API float RoundF(float value);
namespace Juliet
inline int32 LRoundF(float value)
{
extern JULIET_API float RoundF(float value);
return static_cast<int32>(RoundF(value));
}
inline int32 LRoundF(float value)
{
return static_cast<int32>(RoundF(value));
}
template <typename Type>
constexpr Type Min(Type lhs, Type rhs)
{
return rhs < lhs ? rhs : lhs;
}
template <typename Type>
constexpr Type Min(Type lhs, Type rhs)
{
return rhs < lhs ? rhs : lhs;
}
template <typename Type>
constexpr Type Max(Type lhs, Type rhs)
{
return lhs < rhs ? rhs : lhs;
}
template <typename Type>
constexpr Type Max(Type lhs, Type rhs)
{
return lhs < rhs ? rhs : lhs;
}
template <typename Type>
constexpr Type ClampTop(Type value, Type X)
{
return Min(value, X);
}
template <typename Type>
constexpr Type ClampTop(Type value, Type X)
{
return Min(value, X);
}
template <typename Type>
constexpr Type ClampBottom(Type value, Type X)
{
return Max(value, X);
}
template <typename Type>
constexpr Type ClampBottom(Type value, Type X)
template <typename Type>
constexpr Type Clamp(Type val, Type min, Type max)
{
if (val < min)
{
return Max(value, X);
return min;
}
template <typename Type>
constexpr Type Clamp(Type val, Type min, Type max)
if (val > max)
{
if (val < min)
{
return min;
}
if (val > max)
{
return max;
}
return val;
return max;
}
} // namespace Juliet
return val;
}
+174 -177
View File
@@ -1,194 +1,191 @@
#pragma once
#pragma once
#include <Core/Math/Vector.h>
#include <math.h>
namespace Juliet
struct Matrix
{
struct Matrix
{
float m[4][4];
};
float m[4][4];
};
[[nodiscard]] inline Matrix MatrixIdentity()
{
Matrix result = {};
result.m[0][0] = 1.0f;
result.m[1][1] = 1.0f;
result.m[2][2] = 1.0f;
result.m[3][3] = 1.0f;
return result;
}
[[nodiscard]] inline Matrix MatrixIdentity()
{
Matrix result = {};
result.m[0][0] = 1.0f;
result.m[1][1] = 1.0f;
result.m[2][2] = 1.0f;
result.m[3][3] = 1.0f;
return result;
}
[[nodiscard]] inline Matrix operator*(const Matrix& lhs, const Matrix& rhs)
[[nodiscard]] inline Matrix operator*(const Matrix& lhs, const Matrix& rhs)
{
Matrix result = {};
for (int i = 0; i < 4; ++i)
{
Matrix result = {};
for (int i = 0; i < 4; ++i)
for (int j = 0; j < 4; ++j)
{
for (int j = 0; j < 4; ++j)
for (int k = 0; k < 4; ++k)
{
for (int k = 0; k < 4; ++k)
{
result.m[i][j] += lhs.m[i][k] * rhs.m[k][j];
}
result.m[i][j] += lhs.m[i][k] * rhs.m[k][j];
}
}
return result;
}
return result;
}
[[nodiscard]] inline Matrix MatrixTranslation(float x, float y, float z)
[[nodiscard]] inline Matrix MatrixTranslation(float x, float y, float z)
{
Matrix result = MatrixIdentity();
result.m[0][3] = x;
result.m[1][3] = y;
result.m[2][3] = z;
return result;
}
[[nodiscard]] inline Matrix MatrixScale(float x, float y, float z)
{
Matrix result = MatrixIdentity();
result.m[0][0] = x;
result.m[1][1] = y;
result.m[2][2] = z;
return result;
}
[[nodiscard]] inline Matrix MatrixRotationX(float radians)
{
float c = cosf(radians);
float s = sinf(radians);
Matrix result = MatrixIdentity();
result.m[1][1] = c;
result.m[1][2] = -s;
result.m[2][1] = s;
result.m[2][2] = c;
return result;
}
[[nodiscard]] inline Matrix MatrixRotationY(float radians)
{
float c = cosf(radians);
float s = sinf(radians);
Matrix result = MatrixIdentity();
result.m[0][0] = c;
result.m[0][2] = s;
result.m[2][0] = -s;
result.m[2][2] = c;
return result;
}
[[nodiscard]] inline Matrix MatrixRotationZ(float radians)
{
float c = cosf(radians);
float s = sinf(radians);
Matrix result = MatrixIdentity();
result.m[0][0] = c;
result.m[0][1] = -s;
result.m[1][0] = s;
result.m[1][1] = c;
return result;
}
inline void MatrixTranslate(Matrix& m, const Vector3& v)
{
m.m[0][3] += v.x;
m.m[1][3] += v.y;
m.m[2][3] += v.z;
}
[[nodiscard]] inline Matrix MatrixRotation(float x, float y, float z)
{
return MatrixRotationX(x) * MatrixRotationY(y) * MatrixRotationZ(z);
}
inline Matrix LookAt(const Vector3& eye, const Vector3& target, const Vector3& up)
{
// Left-Handed convention
Vector3 zaxis = Normalize(target - eye); // Forward is +z
Vector3 xaxis = Normalize(Cross(up, zaxis));
Vector3 yaxis = Cross(zaxis, xaxis);
Matrix result = {};
// Row 0
result.m[0][0] = xaxis.x;
result.m[0][1] = xaxis.y;
result.m[0][2] = xaxis.z;
result.m[0][3] = -Dot(xaxis, eye);
// Row 1
result.m[1][0] = yaxis.x;
result.m[1][1] = yaxis.y;
result.m[1][2] = yaxis.z;
result.m[1][3] = -Dot(yaxis, eye);
// Row 2
result.m[2][0] = zaxis.x;
result.m[2][1] = zaxis.y;
result.m[2][2] = zaxis.z;
result.m[2][3] = -Dot(zaxis, eye);
// Row 3
result.m[3][3] = 1.0f;
return result;
}
inline Matrix PerspectiveFov(float fovY, float aspectRatio, float nearZ, float farZ)
{
// Left-Handed Perspective
float yScale = 1.0f / tanf(fovY * 0.5f);
float xScale = yScale / aspectRatio;
Matrix result = {};
result.m[0][0] = xScale;
result.m[1][1] = yScale;
result.m[2][2] = farZ / (farZ - nearZ);
result.m[2][3] = (-nearZ * farZ) / (farZ - nearZ);
result.m[3][2] = 1.0f;
result.m[3][3] = 0.0f;
return result;
}
[[nodiscard]] inline Matrix MatrixInverse(const Matrix& m)
{
Matrix out = {};
float m00 = m.m[0][0], m01 = m.m[0][1], m02 = m.m[0][2], m03 = m.m[0][3];
float m10 = m.m[1][0], m11 = m.m[1][1], m12 = m.m[1][2], m13 = m.m[1][3];
float m20 = m.m[2][0], m21 = m.m[2][1], m22 = m.m[2][2], m23 = m.m[2][3];
float m30 = m.m[3][0], m31 = m.m[3][1], m32 = m.m[3][2], m33 = m.m[3][3];
out.m[0][0] = m11 * m22 * m33 - m11 * m23 * m32 - m21 * m12 * m33 + m21 * m13 * m32 + m31 * m12 * m23 - m31 * m13 * m22;
out.m[1][0] = -m10 * m22 * m33 + m10 * m23 * m32 + m20 * m12 * m33 - m20 * m13 * m32 - m30 * m12 * m23 + m30 * m13 * m22;
out.m[2][0] = m10 * m21 * m33 - m10 * m23 * m31 - m20 * m11 * m33 + m20 * m13 * m31 + m30 * m11 * m23 - m30 * m13 * m21;
out.m[3][0] = -m10 * m21 * m32 + m10 * m22 * m31 + m20 * m11 * m32 - m20 * m12 * m31 - m30 * m11 * m22 + m30 * m12 * m21;
out.m[0][1] = -m01 * m22 * m33 + m01 * m23 * m32 + m21 * m02 * m33 - m21 * m03 * m32 - m31 * m02 * m23 + m31 * m03 * m22;
out.m[1][1] = m00 * m22 * m33 - m00 * m23 * m32 - m20 * m02 * m33 + m20 * m03 * m32 + m30 * m02 * m23 - m30 * m03 * m22;
out.m[2][1] = -m00 * m21 * m33 + m00 * m23 * m31 + m20 * m01 * m33 - m20 * m03 * m31 - m30 * m01 * m23 + m30 * m03 * m21;
out.m[3][1] = m00 * m21 * m32 - m00 * m22 * m31 - m20 * m01 * m32 + m20 * m02 * m31 + m30 * m01 * m22 - m30 * m02 * m21;
out.m[0][2] = m01 * m12 * m33 - m01 * m13 * m32 - m11 * m02 * m33 + m11 * m03 * m32 + m31 * m02 * m13 - m31 * m03 * m12;
out.m[1][2] = -m00 * m12 * m33 + m00 * m13 * m32 + m10 * m02 * m33 - m10 * m03 * m32 - m30 * m02 * m13 + m30 * m03 * m12;
out.m[2][2] = m00 * m11 * m33 - m00 * m13 * m31 - m10 * m01 * m33 + m10 * m03 * m31 + m30 * m01 * m13 - m30 * m03 * m11;
out.m[3][2] = -m00 * m11 * m32 + m00 * m12 * m31 + m10 * m01 * m32 - m10 * m02 * m31 - m30 * m01 * m12 + m30 * m02 * m11;
out.m[0][3] = -m01 * m12 * m23 + m01 * m13 * m22 + m11 * m02 * m23 - m11 * m03 * m22 - m21 * m02 * m13 + m21 * m03 * m12;
out.m[1][3] = m00 * m12 * m23 - m00 * m13 * m22 - m10 * m02 * m23 + m10 * m03 * m22 + m20 * m02 * m13 - m20 * m03 * m12;
out.m[2][3] = -m00 * m11 * m23 + m00 * m13 * m21 + m10 * m01 * m23 - m10 * m03 * m21 - m20 * m01 * m13 + m20 * m03 * m11;
out.m[3][3] = m00 * m11 * m22 - m00 * m12 * m21 - m10 * m01 * m22 + m10 * m02 * m21 + m20 * m01 * m12 - m20 * m02 * m11;
float det = m00 * out.m[0][0] + m01 * out.m[1][0] + m02 * out.m[2][0] + m03 * out.m[3][0];
if (det != 0.0f)
{
Matrix result = MatrixIdentity();
result.m[0][3] = x;
result.m[1][3] = y;
result.m[2][3] = z;
return result;
float invDet = 1.0f / det;
for (int r = 0; r < 4; ++r)
for (int c = 0; c < 4; ++c)
out.m[r][c] *= invDet;
}
[[nodiscard]] inline Matrix MatrixScale(float x, float y, float z)
{
Matrix result = MatrixIdentity();
result.m[0][0] = x;
result.m[1][1] = y;
result.m[2][2] = z;
return result;
}
[[nodiscard]] inline Matrix MatrixRotationX(float radians)
{
float c = cosf(radians);
float s = sinf(radians);
Matrix result = MatrixIdentity();
result.m[1][1] = c;
result.m[1][2] = -s;
result.m[2][1] = s;
result.m[2][2] = c;
return result;
}
[[nodiscard]] inline Matrix MatrixRotationY(float radians)
{
float c = cosf(radians);
float s = sinf(radians);
Matrix result = MatrixIdentity();
result.m[0][0] = c;
result.m[0][2] = s;
result.m[2][0] = -s;
result.m[2][2] = c;
return result;
}
[[nodiscard]] inline Matrix MatrixRotationZ(float radians)
{
float c = cosf(radians);
float s = sinf(radians);
Matrix result = MatrixIdentity();
result.m[0][0] = c;
result.m[0][1] = -s;
result.m[1][0] = s;
result.m[1][1] = c;
return result;
}
inline void MatrixTranslate(Matrix& m, const Vector3& v)
{
m.m[0][3] += v.x;
m.m[1][3] += v.y;
m.m[2][3] += v.z;
}
[[nodiscard]] inline Matrix MatrixRotation(float x, float y, float z)
{
return MatrixRotationX(x) * MatrixRotationY(y) * MatrixRotationZ(z);
}
inline Matrix LookAt(const Vector3& eye, const Vector3& target, const Vector3& up)
{
// Left-Handed convention
Vector3 zaxis = Normalize(target - eye); // Forward is +z
Vector3 xaxis = Normalize(Cross(up, zaxis));
Vector3 yaxis = Cross(zaxis, xaxis);
Matrix result = {};
// Row 0
result.m[0][0] = xaxis.x;
result.m[0][1] = xaxis.y;
result.m[0][2] = xaxis.z;
result.m[0][3] = -Dot(xaxis, eye);
// Row 1
result.m[1][0] = yaxis.x;
result.m[1][1] = yaxis.y;
result.m[1][2] = yaxis.z;
result.m[1][3] = -Dot(yaxis, eye);
// Row 2
result.m[2][0] = zaxis.x;
result.m[2][1] = zaxis.y;
result.m[2][2] = zaxis.z;
result.m[2][3] = -Dot(zaxis, eye);
// Row 3
result.m[3][3] = 1.0f;
return result;
}
inline Matrix PerspectiveFov(float fovY, float aspectRatio, float nearZ, float farZ)
{
// Left-Handed Perspective
float yScale = 1.0f / tanf(fovY * 0.5f);
float xScale = yScale / aspectRatio;
Matrix result = {};
result.m[0][0] = xScale;
result.m[1][1] = yScale;
result.m[2][2] = farZ / (farZ - nearZ);
result.m[2][3] = (-nearZ * farZ) / (farZ - nearZ);
result.m[3][2] = 1.0f;
result.m[3][3] = 0.0f;
return result;
}
[[nodiscard]] inline Matrix MatrixInverse(const Matrix& m)
{
Matrix out = {};
float m00 = m.m[0][0], m01 = m.m[0][1], m02 = m.m[0][2], m03 = m.m[0][3];
float m10 = m.m[1][0], m11 = m.m[1][1], m12 = m.m[1][2], m13 = m.m[1][3];
float m20 = m.m[2][0], m21 = m.m[2][1], m22 = m.m[2][2], m23 = m.m[2][3];
float m30 = m.m[3][0], m31 = m.m[3][1], m32 = m.m[3][2], m33 = m.m[3][3];
out.m[0][0] = m11 * m22 * m33 - m11 * m23 * m32 - m21 * m12 * m33 + m21 * m13 * m32 + m31 * m12 * m23 - m31 * m13 * m22;
out.m[1][0] = -m10 * m22 * m33 + m10 * m23 * m32 + m20 * m12 * m33 - m20 * m13 * m32 - m30 * m12 * m23 + m30 * m13 * m22;
out.m[2][0] = m10 * m21 * m33 - m10 * m23 * m31 - m20 * m11 * m33 + m20 * m13 * m31 + m30 * m11 * m23 - m30 * m13 * m21;
out.m[3][0] = -m10 * m21 * m32 + m10 * m22 * m31 + m20 * m11 * m32 - m20 * m12 * m31 - m30 * m11 * m22 + m30 * m12 * m21;
out.m[0][1] = -m01 * m22 * m33 + m01 * m23 * m32 + m21 * m02 * m33 - m21 * m03 * m32 - m31 * m02 * m23 + m31 * m03 * m22;
out.m[1][1] = m00 * m22 * m33 - m00 * m23 * m32 - m20 * m02 * m33 + m20 * m03 * m32 + m30 * m02 * m23 - m30 * m03 * m22;
out.m[2][1] = -m00 * m21 * m33 + m00 * m23 * m31 + m20 * m01 * m33 - m20 * m03 * m31 - m30 * m01 * m23 + m30 * m03 * m21;
out.m[3][1] = m00 * m21 * m32 - m00 * m22 * m31 - m20 * m01 * m32 + m20 * m02 * m31 + m30 * m01 * m22 - m30 * m02 * m21;
out.m[0][2] = m01 * m12 * m33 - m01 * m13 * m32 - m11 * m02 * m33 + m11 * m03 * m32 + m31 * m02 * m13 - m31 * m03 * m12;
out.m[1][2] = -m00 * m12 * m33 + m00 * m13 * m32 + m10 * m02 * m33 - m10 * m03 * m32 - m30 * m02 * m13 + m30 * m03 * m12;
out.m[2][2] = m00 * m11 * m33 - m00 * m13 * m31 - m10 * m01 * m33 + m10 * m03 * m31 + m30 * m01 * m13 - m30 * m03 * m11;
out.m[3][2] = -m00 * m11 * m32 + m00 * m12 * m31 + m10 * m01 * m32 - m10 * m02 * m31 - m30 * m01 * m12 + m30 * m02 * m11;
out.m[0][3] = -m01 * m12 * m23 + m01 * m13 * m22 + m11 * m02 * m23 - m11 * m03 * m22 - m21 * m02 * m13 + m21 * m03 * m12;
out.m[1][3] = m00 * m12 * m23 - m00 * m13 * m22 - m10 * m02 * m23 + m10 * m03 * m22 + m20 * m02 * m13 - m20 * m03 * m12;
out.m[2][3] = -m00 * m11 * m23 + m00 * m13 * m21 + m10 * m01 * m23 - m10 * m03 * m21 - m20 * m01 * m13 + m20 * m03 * m11;
out.m[3][3] = m00 * m11 * m22 - m00 * m12 * m21 - m10 * m01 * m22 + m10 * m02 * m21 + m20 * m01 * m12 - m20 * m02 * m11;
float det = m00 * out.m[0][0] + m01 * out.m[1][0] + m02 * out.m[2][0] + m03 * out.m[3][0];
if (det != 0.0f)
{
float invDet = 1.0f / det;
for (int r = 0; r < 4; ++r)
for (int c = 0; c < 4; ++c)
out.m[r][c] *= invDet;
}
return out;
}
} // namespace Juliet
return out;
}
+7 -10
View File
@@ -1,12 +1,9 @@
#pragma once
#pragma once
namespace Juliet
struct Rectangle
{
struct Rectangle
{
int32 X;
int32 Y;
int32 Width;
int32 Height;
};
} // namespace Juliet
int32 X;
int32 Y;
int32 Width;
int32 Height;
};
+29 -30
View File
@@ -1,39 +1,38 @@
#pragma once
#pragma once
#include <Core/Common/CoreTypes.h>
#include <Juliet.h>
#include <math.h>
namespace Juliet
struct Vector3
{
struct Vector3
{
float x, y, z;
float x, y, z;
Vector3 operator+(const Vector3& rhs) const { return { x + rhs.x, y + rhs.y, z + rhs.z }; }
Vector3 operator-(const Vector3& rhs) const { return { x - rhs.x, y - rhs.y, z - rhs.z }; }
Vector3 operator*(float s) const { return { x * s, y * s, z * s }; }
};
Vector3 operator+(const Vector3& rhs) const { return { x + rhs.x, y + rhs.y, z + rhs.z }; }
Vector3 operator-(const Vector3& rhs) const { return { x - rhs.x, y - rhs.y, z - rhs.z }; }
Vector3 operator*(float s) const { return { x * s, y * s, z * s }; }
};
struct Vector4
{
float x, y, z, w;
};
struct Vector4
{
float x = 0.f;
float y = 0.f;
float z = 0.f;
float w = 0.f;
};
inline Vector3 Normalize(const Vector3& v)
inline Vector3 Normalize(const Vector3& v)
{
float len = sqrtf(v.x * v.x + v.y * v.y + v.z * v.z);
if (len > 0.0001f)
{
float len = sqrtf(v.x * v.x + v.y * v.y + v.z * v.z);
if (len > 0.0001f)
{
return { v.x / len, v.y / len, v.z / len };
}
return v;
return { v.x / len, v.y / len, v.z / len };
}
return v;
}
inline Vector3 Cross(const Vector3& a, const Vector3& b)
{
return { a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x };
}
inline Vector3 Cross(const Vector3& a, const Vector3& b)
{
return { a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x };
}
inline float Dot(const Vector3& a, const Vector3& b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
} // namespace Juliet
inline float Dot(const Vector3& a, const Vector3& b)
{
return a.x * b.x + a.y * b.y + a.z * b.z;
}
+20 -23
View File
@@ -1,31 +1,28 @@
#pragma once
#pragma once
#include <Juliet.h>
#include <Core/Common/CoreUtils.h>
namespace Juliet
{
// Uninitialized allocation
JULIET_API void* Malloc(size_t elem_size);
// Initialized to 0 allocation
JULIET_API void* Calloc(size_t nb_elem, size_t elem_size);
JULIET_API void* Realloc(void* memory, size_t newSize);
// Uninitialized allocation
JULIET_API void* Malloc(size_t elem_size);
// Initialized to 0 allocation
JULIET_API void* Calloc(size_t nb_elem, size_t elem_size);
JULIET_API void* Realloc(void* memory, size_t newSize);
// Free
template <typename Type>
void Free(Type* memory)
// Free
template <typename Type>
void Free(Type* memory)
{
Assert(memory);
::free(memory);
}
// Free and Set the ptr to nullptr
template <typename Type>
void SafeFree(Type*& memory)
{
if (memory)
{
Assert(memory);
::free(memory);
memory = nullptr;
}
// Free and Set the ptr to nullptr
template <typename Type>
void SafeFree(Type*& memory)
{
if (memory)
{
::free(memory);
memory = nullptr;
}
}
} // namespace Juliet
}
+107 -111
View File
@@ -1,129 +1,125 @@
#pragma once
#pragma once
#include <Core/Common/CoreTypes.h>
#include <Core/Common/CoreUtils.h>
#include <Core/Common/NonNullPtr.h>
#include <Core/Common/String.h>
#include <Juliet.h>
#if JULIET_DEBUG
#include <Core/Memory/MemoryArenaDebug.h>
#endif
namespace Juliet
constexpr global uint64 g_Arena_Default_Reserve_Size = Megabytes(64);
constexpr global uint64 g_Arena_Default_Commit_Size = Kilobytes(64);
constexpr global uint64 k_ArenaHeaderSize = 128;
#if JULIET_DEBUG
struct ArenaDebugInfo;
JULIET_API String FormatDebugTagV(const char* formatStr, std::format_args args);
#endif
struct Arena
{
constexpr global uint64 g_Arena_Default_Reserve_Size = Megabytes(64);
constexpr global uint64 g_Arena_Default_Commit_Size = Kilobytes(64);
constexpr global uint64 k_ArenaHeaderSize = 128;
Arena* Previous;
Arena* Current;
uint64 BasePosition;
uint64 Position;
uint64 Alignment;
uint64 CommitSize;
uint64 ReserveSize;
uint64 Committed;
uint64 Reserved;
Arena* FreeBlockLast;
JULIET_DEBUG_ONLY(uint16 LostNodeCount;)
JULIET_DEBUG_ONLY(bool CanReserveMore : 1;)
JULIET_DEBUG_ONLY(Arena* GlobalNext;)
JULIET_DEBUG_ONLY(Arena* GlobalPrev;)
JULIET_DEBUG_ONLY(ArenaDebugInfo* FirstDebugInfo;)
const char* Name;
};
static_assert(sizeof(Arena) <= k_ArenaHeaderSize);
struct TempArena
{
Arena* Arena;
index_t Position;
};
struct ArenaParams
{
uint64 ReserveSize = g_Arena_Default_Reserve_Size;
uint64 CommitSize = g_Arena_Default_Commit_Size;
const char* Name;
// When false, will assert if a new block is reserved.
JULIET_DEBUG_ONLY(bool CanReserveMore : 1 = true;)
};
[[nodiscard]] JULIET_API Arena* ArenaAllocate(const ArenaParams& params,
const std::source_location& loc = std::source_location::current());
JULIET_API void ArenaRelease(NonNullPtr<Arena> arena);
// Raw Push, can be used but templated helpers exists below
[[nodiscard]] JULIET_API void* ArenaPush(NonNullPtr<Arena> arena, size_t size, size_t align,
bool shouldBeZeroed JULIET_DEBUG_PARAM(const char* tag));
JULIET_API void ArenaPopTo(NonNullPtr<Arena> arena, size_t position);
JULIET_API void ArenaPop(NonNullPtr<Arena> arena, size_t amount);
JULIET_API void ArenaClear(NonNullPtr<Arena> arena);
[[nodiscard]] JULIET_API size_t ArenaPos(NonNullPtr<Arena> arena);
#if JULIET_DEBUG
struct ArenaDebugInfo;
JULIET_API String FormatDebugTagV(const char* formatStr, std::format_args args);
template <typename FirstDebugArg, typename... DebugArgs>
#endif
[[nodiscard]] inline void* ArenaPushSize(NonNullPtr<Arena> arena, size_t size, size_t align,
bool shouldBeZeroed JULIET_DEBUG_PARAM(FirstDebugArg&& firstDebugArg, DebugArgs&&... debugArgs))
{
return ArenaPush(arena, size, align,
shouldBeZeroed JULIET_DEBUG_PARAM(
[&]() -> const char*
{
return Format(GetDebugInfoArena(), std::forward<FirstDebugArg>(firstDebugArg),
std::forward<DebugArgs>(debugArgs)...)
.Str;
}()));
}
struct Arena
{
Arena* Previous;
Arena* Current;
uint64 BasePosition;
uint64 Position;
uint64 Alignment;
uint64 CommitSize;
uint64 ReserveSize;
uint64 Committed;
uint64 Reserved;
Arena* FreeBlockLast;
JULIET_DEBUG_ONLY(uint16 LostNodeCount;)
JULIET_DEBUG_ONLY(bool CanReserveMore : 1;)
JULIET_DEBUG_ONLY(Arena* GlobalNext;)
JULIET_DEBUG_ONLY(Arena* GlobalPrev;)
JULIET_DEBUG_ONLY(ArenaDebugInfo* FirstDebugInfo;)
JULIET_DEBUG_ONLY(const char* Name;)
};
static_assert(sizeof(Arena) <= k_ArenaHeaderSize);
struct TempArena
{
Arena* Arena;
index_t Position;
};
struct ArenaParams
{
uint64 ReserveSize = g_Arena_Default_Reserve_Size;
uint64 CommitSize = g_Arena_Default_Commit_Size;
// When false, will assert if a new block is reserved.
JULIET_DEBUG_ONLY(bool CanReserveMore : 1 = true;)
};
[[nodiscard]] JULIET_API Arena* ArenaAllocate(const ArenaParams& params JULIET_DEBUG_PARAM(const char* name),
const std::source_location& loc = std::source_location::current());
JULIET_API void ArenaRelease(NonNullPtr<Arena> arena);
// Raw Push, can be used but templated helpers exists below
[[nodiscard]] JULIET_API void* ArenaPush(NonNullPtr<Arena> arena, size_t size, size_t align,
bool shouldBeZeroed JULIET_DEBUG_PARAM(const char* tag));
JULIET_API void ArenaPopTo(NonNullPtr<Arena> arena, size_t position);
JULIET_API void ArenaPop(NonNullPtr<Arena> arena, size_t amount);
JULIET_API void ArenaClear(NonNullPtr<Arena> arena);
[[nodiscard]] JULIET_API size_t ArenaPos(NonNullPtr<Arena> arena);
#if JULIET_DEBUG
template <typename FirstDebugArg, typename... DebugArgs>
#endif
[[nodiscard]] inline void* ArenaPushSize(NonNullPtr<Arena> arena, size_t size, size_t align,
bool shouldBeZeroed JULIET_DEBUG_PARAM(FirstDebugArg&& firstDebugArg,
DebugArgs&&... debugArgs))
{
return ArenaPush(arena, size, align,
shouldBeZeroed JULIET_DEBUG_PARAM(
[&]() -> const char*
{
return Format(GetDebugInfoArena(), std::forward<FirstDebugArg>(firstDebugArg),
std::forward<DebugArgs>(debugArgs)...)
.Data;
}()));
}
template <typename Type JULIET_DEBUG_ONLY(, typename... DebugArgs)>
[[nodiscard]] Type* ArenaPushStruct(NonNullPtr<Arena> arena JULIET_DEBUG_PARAM(DebugArgs&&... debugArgs))
{
return static_cast<Type*>(
ArenaPush(arena, sizeof(Type) * 1, AlignOf(Type),
true JULIET_DEBUG_PARAM(
[&]() -> const char*
template <typename Type JULIET_DEBUG_ONLY(, typename... DebugArgs)>
[[nodiscard]] Type* ArenaPushStruct(NonNullPtr<Arena> arena JULIET_DEBUG_PARAM(DebugArgs&&... debugArgs))
{
return static_cast<Type*>(
ArenaPush(arena, sizeof(Type) * 1, AlignOf(Type),
true JULIET_DEBUG_PARAM(
[&]() -> const char*
{
if constexpr (sizeof...(DebugArgs) > 0)
{
if constexpr (sizeof...(DebugArgs) > 0)
{
return Format(GetDebugInfoArena(), std::forward<DebugArgs>(debugArgs)...).Data;
}
return GetTypeName<Type>();
}())));
}
return Format(GetDebugInfoArena(), std::forward<DebugArgs>(debugArgs)...).Str;
}
return GetTypeName<Type>();
}())));
}
template <typename Type JULIET_DEBUG_ONLY(, typename... DebugArgs)>
[[nodiscard]] Type* ArenaPushArray(NonNullPtr<Arena> arena, size_t count JULIET_DEBUG_PARAM(DebugArgs&&... debugArgs))
{
return static_cast<Type*>(
ArenaPush(arena, sizeof(Type) * count, Max(8ull, AlignOf(Type)),
true JULIET_DEBUG_PARAM(
[&]() -> const char*
template <typename Type, bool shouldZero = true JULIET_DEBUG_ONLY(, typename... DebugArgs)>
[[nodiscard]] Type* ArenaPushArray(NonNullPtr<Arena> arena, size_t count JULIET_DEBUG_PARAM(DebugArgs&&... debugArgs))
{
return static_cast<Type*>(
ArenaPush(arena, sizeof(Type) * count, Max(8ull, AlignOf(Type)),
shouldZero JULIET_DEBUG_PARAM(
[&]() -> const char*
{
if constexpr (sizeof...(DebugArgs) > 0)
{
if constexpr (sizeof...(DebugArgs) > 0)
{
return Format(GetDebugInfoArena(), std::forward<DebugArgs>(debugArgs)...).Data;
}
return GetTypeName<Type>();
}())));
}
return Format(GetDebugInfoArena(), std::forward<DebugArgs>(debugArgs)...).Str;
}
return GetTypeName<Type>();
}())));
}
TempArena ArenaTempBegin(NonNullPtr<Arena> arena);
void ArenaTempEnd(TempArena temp);
} // namespace Juliet
TempArena ArenaTempBegin(NonNullPtr<Arena> arena);
void ArenaTempEnd(TempArena temp);
+33 -39
View File
@@ -1,52 +1,46 @@
#pragma once
#pragma once
#include <Core/Common/CoreTypes.h>
#include <Core/Common/NonNullPtr.h>
#include <Core/Common/String.h>
#include <Juliet.h>
#if JULIET_DEBUG
namespace Juliet
struct Arena;
struct MemoryBlock;
// Arena (Struct)
struct ArenaDebugInfo
{
struct Arena;
struct MemoryBlock;
const char* Tag;
size_t Offset;
size_t Size;
ArenaDebugInfo* Next;
};
// Arena (Struct)
struct ArenaDebugInfo
{
const char* Tag;
size_t Offset;
size_t Size;
ArenaDebugInfo* Next;
};
// MemoryArena (Pool-based)
struct ArenaAllocation
{
size_t Offset;
size_t Size;
String Tag;
ArenaAllocation* Next;
};
// MemoryArena (Pool-based)
struct ArenaAllocation
{
size_t Offset;
size_t Size;
String Tag;
ArenaAllocation* Next;
};
// Arena (Struct)
void DebugRegisterArena(NonNullPtr<Arena> arena);
void DebugUnregisterArena(NonNullPtr<Arena> arena);
void DebugArenaSetDebugName(NonNullPtr<Arena> arena, const char* name);
bool IsDebugInfoArena(const Arena* arena); // To prevent recursion
void DebugArenaFreeBlock(Arena* block); // To clear all debug infos in a block
void DebugArenaRemoveAllocation(Arena* block, size_t oldOffset);
void DebugArenaPopTo(Arena* block, size_t newPosition);
void DebugArenaAddDebugInfo(Arena* block, size_t size, size_t offset, const char* tag);
// Arena (Struct)
void DebugRegisterArena(NonNullPtr<Arena> arena);
void DebugUnregisterArena(NonNullPtr<Arena> arena);
void DebugArenaSetDebugName(NonNullPtr<Arena> arena, const char* name);
bool IsDebugInfoArena(const Arena* arena); // To prevent recursion
void DebugArenaFreeBlock(Arena* block); // To clear all debug infos in a block
void DebugArenaRemoveAllocation(Arena* block, size_t oldOffset);
void DebugArenaPopTo(Arena* block, size_t newPosition);
void DebugArenaAddDebugInfo(Arena* block, size_t size, size_t offset, const char* tag);
// MemoryArena (Pool-based)
void DebugFreeArenaAllocations(MemoryBlock* blk);
void DebugArenaAddAllocation(MemoryBlock* blk, size_t size, size_t offset, String tag);
void DebugArenaRemoveLastAllocation(MemoryBlock* blk);
// MemoryArena (Pool-based)
void DebugFreeArenaAllocations(MemoryBlock* blk);
void DebugArenaAddAllocation(MemoryBlock* blk, size_t size, size_t offset, String tag);
void DebugArenaRemoveLastAllocation(MemoryBlock* blk);
JULIET_API Arena* GetDebugInfoArena();
} // namespace Juliet
JULIET_API Arena* GetDebugInfoArena();
#endif
+50 -55
View File
@@ -1,65 +1,61 @@
#pragma once
#include <Core/Common/CoreTypes.h>
#pragma once
#define ArraySize(array) (sizeof(array) / sizeof(array[0]))
namespace Juliet
inline int32 MemCompare(const void* leftValue, const void* rightValue, size_t size)
{
inline int32 MemCompare(const void* leftValue, const void* rightValue, size_t size)
auto left = static_cast<const unsigned char*>(leftValue);
auto right = static_cast<const unsigned char*>(rightValue);
while (size && *left == *right)
{
auto left = static_cast<const unsigned char*>(leftValue);
auto right = static_cast<const unsigned char*>(rightValue);
while (size && *left == *right)
{
++left;
++right;
--size;
}
return size ? *left - *right : 0;
++left;
++right;
--size;
}
return size ? *left - *right : 0;
}
// Single linked list
void SingleLinkedListPushNext(auto*& stackTop, auto* node)
{
node->Next = stackTop;
stackTop = node;
}
void SingleLinkedListPushPrevious(auto*& stackTop, auto* node)
{
node->Previous = stackTop;
stackTop = node;
}
void SingleLinkedListPopNext(auto*& stackTop)
{
stackTop = stackTop->Next;
}
// Double linked list
template <typename QueueType, typename QueueTypeNode>
void Enqueue(QueueType& queue, QueueTypeNode* node)
{
if (queue.First == nullptr)
{
queue.First = queue.Last = node;
node->Next = nullptr;
}
else
{
queue.Last->Next = node, queue.Last = node;
node->Next = nullptr;
}
// Single linked list
void SingleLinkedListPushNext(auto*& stackTop, auto* node)
{
node->Next = stackTop;
stackTop = node;
}
queue.Nodecount += 1;
}
void SingleLinkedListPushPrevious(auto*& stackTop, auto* node)
{
node->Previous = stackTop;
stackTop = node;
}
void SingleLinkedListPopNext(auto*& stackTop)
{
stackTop = stackTop->Next;
}
// Double linked list
template <typename QueueType, typename QueueTypeNode>
void Enqueue(QueueType& queue, QueueTypeNode* node)
{
if (queue.First == nullptr)
{
queue.First = queue.Last = node;
node->Next = nullptr;
}
else
{
queue.Last->Next = node, queue.Last = node;
node->Next = nullptr;
}
queue.Nodecount += 1;
}
template <typename QueueType>
struct QueueNode
{
QueueType* Next;
};
template <typename QueueType>
struct QueueNode
{
QueueType* Next;
};
#define DECLARE_QUEUE(type) \
struct type##Queue \
@@ -75,4 +71,3 @@ namespace Juliet
#define MemCopy memcpy
#define MemoryZero(dst, size) MemSet(dst, 0, size)
} // namespace Juliet
+5 -10
View File
@@ -1,11 +1,6 @@
#pragma once
#pragma once
#include <Core/Common/CoreTypes.h>
namespace Juliet
{
// TODO : Do something better.
constexpr uint32 kLocalhost = (127 << 3) | (0 << 2) | (0 << 1) | 1;
constexpr uint32 kAnyIp = 0;
constexpr uint32 kBroadcastIp = (255 << 3) | (255 << 2) | (255 << 1) | 255;
} // namespace Juliet
// TODO : Do something better.
constexpr uint32 kLocalhost = (127 << 3) | (0 << 2) | (0 << 1) | 1;
constexpr uint32 kAnyIp = 0;
constexpr uint32 kBroadcastIp = (255 << 3) | (255 << 2) | (255 << 1) | 255;
+22 -26
View File
@@ -1,36 +1,32 @@
#pragma once
#pragma once
#include <Core/Common/CoreTypes.h>
#include <Core/Container/Vector.h>
namespace Juliet
class NetworkPacket
{
class NetworkPacket
{
public:
NetworkPacket();
NetworkPacket(Arena& arena);
virtual ~NetworkPacket();
NetworkPacket(NetworkPacket&);
NetworkPacket& operator=(const NetworkPacket&);
NetworkPacket(NetworkPacket&&) noexcept;
NetworkPacket& operator=(NetworkPacket&&) noexcept;
public:
NetworkPacket();
NetworkPacket(Arena& arena);
virtual ~NetworkPacket();
NetworkPacket(NetworkPacket&);
NetworkPacket& operator=(const NetworkPacket&);
NetworkPacket(NetworkPacket&&) noexcept;
NetworkPacket& operator=(NetworkPacket&&) noexcept;
void Create(Arena& arena);
void Create(Arena& arena);
[[nodiscard]] ByteBuffer GetRawData();
[[nodiscard]] ByteBuffer GetRawData();
// Pack
NetworkPacket& operator<<(uint32 value);
NetworkPacket& operator<<(char* data);
// Pack
NetworkPacket& operator<<(uint32 value);
NetworkPacket& operator<<(char* data);
protected:
void Append(ByteBuffer buffer);
protected:
void Append(ByteBuffer buffer);
friend class TcpSocket;
friend class TcpSocket;
private:
VectorArena<Byte, 4096> Data;
size_t PartialSendIndex = 0;
};
} // namespace Juliet
private:
VectorArena<Byte, 4096> Data;
size_t PartialSendIndex = 0;
};
+47 -50
View File
@@ -1,56 +1,53 @@
#pragma once
#pragma once
#include <Core/Networking/SocketHandle.h>
namespace Juliet
class Socket
{
class Socket
public:
virtual ~Socket();
Socket(Socket&& other) noexcept;
Socket& operator=(Socket&& socket) noexcept;
Socket(const Socket&) = delete;
Socket& operator=(const Socket&) = delete;
bool IsValid() const;
enum class Status : uint8
{
public:
virtual ~Socket();
Socket(Socket&& other) noexcept;
Socket& operator=(Socket&& socket) noexcept;
Socket(const Socket&) = delete;
Socket& operator=(const Socket&) = delete;
bool IsValid() const;
enum class Status : uint8
{
Done,
Partial,
Ready,
NotReady,
Disconnected,
Error
};
protected:
enum class Protocol : uint8
{
TCP,
UDP
};
// To store the result of a send/receive on the socket
struct RequestStatus
{
Status Status = Status::Done;
size_t Length = 0;
};
explicit Socket(Protocol protocol);
SocketHandle GetHandle() const { return Handle; }
void Create();
void CreateFromHandle(SocketHandle handle);
void Close();
private:
SocketHandle Handle;
Protocol ProtocolType;
Done,
Partial,
Ready,
NotReady,
Disconnected,
Error
};
} // namespace Juliet
protected:
enum class Protocol : uint8
{
TCP,
UDP
};
// To store the result of a send/receive on the socket
struct RequestStatus
{
Status Status = Status::Done;
size_t Length = 0;
};
explicit Socket(Protocol protocol);
SocketHandle GetHandle() const { return Handle; }
void Create();
void CreateFromHandle(SocketHandle handle);
void Close();
private:
SocketHandle Handle;
Protocol ProtocolType;
};
@@ -1,14 +1,11 @@
#pragma once
#pragma once
#if JULIET_WIN32
#include <basetsd.h>
#endif
namespace Juliet
{
#if JULIET_WIN32
using SocketHandle = UINT_PTR;
using SocketHandle = UINT_PTR;
#else
using SocketHandle = int;
using SocketHandle = int;
#endif
} // namespace Juliet
+10 -13
View File
@@ -1,21 +1,18 @@
#pragma once
#pragma once
#include <Core/Networking/IPAddress.h>
#include <Core/Networking/Socket.h>
#include <Core/Networking/TcpSocket.h>
namespace Juliet
class TcpListener : public Socket
{
class TcpListener : public Socket
{
public:
TcpListener();
public:
TcpListener();
TcpListener(const TcpListener&) = delete;
TcpListener& operator=(const TcpListener&) = delete;
TcpListener(const TcpListener&) = delete;
TcpListener& operator=(const TcpListener&) = delete;
Status Listen(uint16 port, uint32 address = kAnyIp);
Status Accept(TcpSocket& socket);
void Close();
};
} // namespace Juliet
Status Listen(uint16 port, uint32 address = kAnyIp);
Status Accept(TcpSocket& socket);
void Close();
};
+14 -17
View File
@@ -1,24 +1,21 @@
#pragma once
#pragma once
#include <Core/Networking/Socket.h>
namespace Juliet
class NetworkPacket;
class TcpSocket : public Socket
{
class NetworkPacket;
public:
TcpSocket();
class TcpSocket : public Socket
{
public:
TcpSocket();
TcpSocket(const TcpSocket&) = delete;
TcpSocket& operator=(const TcpSocket&) = delete;
TcpSocket(const TcpSocket&) = delete;
TcpSocket& operator=(const TcpSocket&) = delete;
RequestStatus Send(NetworkPacket& packet);
RequestStatus Send(ByteBuffer buffer);
Status Receive(NetworkPacket& outPacket);
RequestStatus Send(NetworkPacket& packet);
RequestStatus Send(ByteBuffer buffer);
Status Receive(NetworkPacket& outPacket);
private:
friend class TcpListener;
};
} // namespace Juliet
private:
friend class TcpListener;
};
+5 -3
View File
@@ -1,4 +1,4 @@
#pragma once
#pragma once
#include <algorithm>
#include <bit>
@@ -22,11 +22,13 @@
#include <list>
#include <memory>
#include <mutex>
#include <new>
#include <queue>
#include <source_location>
#include <stdexcept>
#include <string>
#include <thread>
#include <type_traits>
#include <vector>
#include <Juliet.h>
#include <Core/Common/CoreTypes.h>
+3 -6
View File
@@ -1,7 +1,4 @@
#pragma once
#pragma once
namespace Juliet
{
using Mutex = std::mutex;
using LockGuard = std::lock_guard<Mutex>;
} // namespace Juliet
using Mutex = std::mutex;
using LockGuard = std::lock_guard<Mutex>;
+12 -11
View File
@@ -1,15 +1,16 @@
#pragma once
#pragma once
namespace Juliet
#include <Core/Common/String.h>
uint32 thread_id();
void set_thread_name(String name);
// TODO : Proper wait
inline void wait_ms(int milliseconds)
{
using Thread = std::thread;
// TODO : Proper wait
inline void wait_ms(int milliseconds)
clock_t start_time = clock();
while (clock() < start_time + milliseconds)
{
clock_t start_time = clock();
while (clock() < start_time + milliseconds)
{
}
}
} // namespace Juliet
}
@@ -0,0 +1,20 @@
#pragma once
#include <Core/Memory/MemoryArena.h>
struct thread_context
{
Arena* ScratchArenas[2];
char ThreadName[64];
uint8 ThreadNameSize;
};
thread_context* thread_context_alloc();
void thread_context_release(NonNullPtr<thread_context> ctx);
void thread_context_select(NonNullPtr<thread_context> ctx);
thread_context* thread_context_current();
Arena* thread_context_get_scratch(Arena** conflicts, size_t count);
JULIET_API TempArena scratch_begin(Arena** conflicts, size_t count);
JULIET_API void scratch_end(TempArena scratch);
+2 -5
View File
@@ -1,9 +1,6 @@
#pragma once
#pragma once
#include <Core/Common/String.h>
#include <Graphics/MeshRenderer.h>
namespace Juliet
{
JULIET_API extern MeshAssetID LoadMesh(String filename);
}
JULIET_API extern MeshAssetID LoadMesh(String filename);
+72 -24
View File
@@ -1,33 +1,81 @@
#pragma once
#include <Core/Common/CRC32.h>
#include <Juliet.h>
namespace Juliet
#include <Core/Common/CRC32.h>
#include <Core/Common/String.h>
struct Archive;
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)
{
struct Class
{
uint32 CRC;
Assert(payload);
serialize(ar, version, *((Type*)payload));
}
template <typename Type>
void initialize_thunk(void* payload)
{
auto& typed_val = *(Type*)payload;
typed_val = {};
}
struct Class
{
uint32 CRC;
uint8 kind;
uint16 version;
const Class* base_class;
initialize_fct_ptr initialize_fct;
serialize_fct_ptr serialize_fct;
size_t size_of;
size_t alignment;
#if JULIET_DEBUG
// TODO: string struct may be
const char* Name;
size_t Name_Length;
String Name;
#endif
};
#define DECLARE_CLASS() static Class* kind;
#define DEFINE_CLASS_VERSIONED(cls, version, base_class) \
constexpr Class class_kind_##cls = MakeClass(ConstString(#cls), 0, (version), (base_class), sizeof(cls), \
alignof(cls), (&initialize_thunk<cls>), (&serialize_thunk<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,
initialize_fct_ptr init_fct, serialize_fct_ptr serde_fct)
{
Class cls = {};
cls.CRC = crc32(name.Str, name.Size);
cls.kind = kind;
cls.version = version;
cls.base_class = base_class;
cls.size_of = size;
cls.alignment = align;
cls.initialize_fct = init_fct;
cls.serialize_fct = serde_fct;
#if JULIET_DEBUG
cls.Name = name;
#endif
consteval Class(const char* className, size_t name_length)
{
CRC = crc32(className, name_length);
#if JULIET_DEBUG
// TODO: string struct may be
Name = className;
Name_Length = name_length;
#endif
}
};
return cls;
}
template <typename type>
bool IsA(Class& cls)
{
return cls.CRC == type::StaticClass->CRC;
}
} // namespace Juliet
bool IsA(const Class& query, const Class* target);
template <typename type>
bool IsA(const Class& cls)
{
return IsA(cls, type::kind);
}
JULIET_API void serialize(Archive& ar, NonNullPtr<const Class> cls, void* instance);
+3 -3
View File
@@ -1,12 +1,12 @@
#pragma once
#pragma once
#include <Juliet.h>
#if JULIET_DEBUG
namespace Juliet::Debug
namespace Debug
{
JULIET_API void DebugDrawMemoryArena();
} // namespace Juliet::Debug
} // namespace Debug
#endif
+14 -17
View File
@@ -1,25 +1,22 @@
#pragma once
#pragma once
#include <Core/Application/IApplication.h>
namespace Juliet
enum class JulietInit_Flags : uint8;
struct Engine
{
enum class JulietInit_Flags : uint8;
IApplication* Application = nullptr;
Arena* PlatformArena = nullptr;
Arena* AssetArena = nullptr;
};
struct Engine
{
IApplication* Application = nullptr;
Arena* PlatformArena = nullptr;
Arena* AssetArena = nullptr;
};
void InitializeEngine(JulietInit_Flags flags);
void ShutdownEngine();
void InitializeEngine(JulietInit_Flags flags);
void ShutdownEngine();
void LoadApplication(IApplication& app);
void UnloadApplication();
void LoadApplication(IApplication& app);
void UnloadApplication();
void RunEngine();
void RunEngine();
extern Arena* GetPlatformArena();
} // namespace Juliet
extern Arena* GetPlatformArena();
+26 -29
View File
@@ -1,38 +1,35 @@
#pragma once
#pragma once
#include <Core/Math/Matrix.h>
#include <Juliet.h>
namespace Juliet
struct Camera
{
struct Camera
{
index_t Index;
Vector3 Position;
Vector3 Target;
Vector3 Up;
float FOV; // In radians
float AspectRatio;
float NearPlane;
float FarPlane;
};
index_t Index;
Vector3 Position;
Vector3 Target;
Vector3 Up;
float FOV; // In radians
float AspectRatio;
float NearPlane;
float FarPlane;
};
inline Matrix Camera_GetViewMatrix(const Camera& cam)
{
return LookAt(cam.Position, cam.Target, cam.Up);
}
inline Matrix Camera_GetViewMatrix(const Camera& cam)
{
return LookAt(cam.Position, cam.Target, cam.Up);
}
inline Matrix Camera_GetProjectionMatrix(const Camera& cam)
{
return PerspectiveFov(cam.FOV, cam.AspectRatio, cam.NearPlane, cam.FarPlane);
}
inline Matrix Camera_GetProjectionMatrix(const Camera& cam)
{
return PerspectiveFov(cam.FOV, cam.AspectRatio, cam.NearPlane, cam.FarPlane);
}
inline Matrix Camera_GetViewProjectionMatrix(const Camera& cam)
{
return Camera_GetProjectionMatrix(cam) * Camera_GetViewMatrix(cam);
}
inline Matrix Camera_GetViewProjectionMatrix(const Camera& cam)
{
return Camera_GetProjectionMatrix(cam) * Camera_GetViewMatrix(cam);
}
JULIET_API extern void ReserveCamera(size_t amount);
JULIET_API extern Camera* GetCurrentCamera();
JULIET_API extern void SetCurrentCamera(index_t index);
} // namespace Juliet
JULIET_API extern void ReserveCamera(size_t amount);
JULIET_API extern Camera* GetCurrentCamera();
JULIET_API extern void SetCurrentCamera(index_t index);
+10 -13
View File
@@ -1,16 +1,13 @@
#pragma once
#pragma once
namespace Juliet
template <typename Type>
struct ColorType
{
template <typename Type>
struct ColorType
{
Type R;
Type G;
Type B;
Type A;
};
Type R;
Type G;
Type B;
Type A;
};
using FColor = ColorType<float>;
using Color = ColorType<uint8>;
} // namespace Juliet
using FColor = ColorType<float>;
using Color = ColorType<uint8>;
+7 -10
View File
@@ -1,4 +1,4 @@
#pragma once
#pragma once
#include <Core/Math/Vector.h>
#include <Graphics/Camera.h>
@@ -6,12 +6,9 @@
#include <Graphics/Graphics.h>
#include <Juliet.h>
namespace Juliet
{
extern JULIET_API void DebugDisplay_Initialize(GraphicsDevice* device);
extern JULIET_API void DebugDisplay_Shutdown(GraphicsDevice* device);
extern JULIET_API void DebugDisplay_DrawLine(const Vector3& start, const Vector3& end, const FColor& color, bool overlay);
extern JULIET_API void DebugDisplay_DrawSphere(const Vector3& center, float radius, const FColor& color, bool overlay);
extern JULIET_API void DebugDisplay_Prepare(CommandList* cmdList);
extern JULIET_API void DebugDisplay_Flush(CommandList* cmdList, RenderPass* renderPass, const Camera& camera);
} // namespace Juliet
extern JULIET_API void DebugDisplay_Initialize(NonNullPtr<Arena> arena, GraphicsDevice* device);
extern JULIET_API void DebugDisplay_Shutdown(GraphicsDevice* device);
extern JULIET_API void DebugDisplay_DrawLine(const Vector3& start, const Vector3& end, const FColor& color, bool overlay);
extern JULIET_API void DebugDisplay_DrawSphere(const Vector3& center, float radius, const FColor& color, bool overlay);
extern JULIET_API void DebugDisplay_Prepare(CommandList* cmdList);
extern JULIET_API void DebugDisplay_Flush(CommandList* cmdList, RenderPass* renderPass, const Camera& camera);
+134 -137
View File
@@ -12,166 +12,163 @@
#include <Juliet.h>
// Graphics Interface
namespace Juliet
// Opaque types
struct CommandList;
struct GraphicsDevice;
struct Fence;
// Parameters of an indirect draw command
struct IndirectDrawCommand
{
// Opaque types
struct CommandList;
struct GraphicsDevice;
struct Fence;
uint32 VertexCount; // Number of vertices to draw
uint32 InstanceCount; // Number of instanced to draw
uint32 FirstVertex; // Index of the first vertex to draw
uint32 FirstInstance; // ID of the first instance to draw
};
// Parameters of an indirect draw command
struct IndirectDrawCommand
{
uint32 VertexCount; // Number of vertices to draw
uint32 InstanceCount; // Number of instanced to draw
uint32 FirstVertex; // Index of the first vertex to draw
uint32 FirstInstance; // ID of the first instance to draw
};
// Parameters of an INDEXED indirect draw command
struct IndexedIndirectDrawCommand
{
uint32 VertexCount; // Number of vertices to draw
uint32 InstanceCount; // Number of instanced to draw
uint32 FirstIndex; // Base Index within the index buffer
int32 VertexOffset; // Offset the vertex index into the buffer
uint32 FirstInstance; // ID of the first instance to draw
};
// Parameters of an INDEXED indirect draw command
struct IndexedIndirectDrawCommand
{
uint32 VertexCount; // Number of vertices to draw
uint32 InstanceCount; // Number of instanced to draw
uint32 FirstIndex; // Base Index within the index buffer
int32 VertexOffset; // Offset the vertex index into the buffer
uint32 FirstInstance; // ID of the first instance to draw
};
// Parameters of an INDEXED Indirect Dispatch Command
struct IndirectDispatchCommand
{
uint32 X_WorkGroupCount; // Number of Workgroup to dispatch on dimension X
uint32 Y_WorkGroupCount; // Number of Workgroup to dispatch on dimension Y
uint32 Z_WorkGroupCount; // Number of Workgroup to dispatch on dimension Z
};
// Parameters of an INDEXED Indirect Dispatch Command
struct IndirectDispatchCommand
{
uint32 X_WorkGroupCount; // Number of Workgroup to dispatch on dimension X
uint32 Y_WorkGroupCount; // Number of Workgroup to dispatch on dimension Y
uint32 Z_WorkGroupCount; // Number of Workgroup to dispatch on dimension Z
};
enum class QueueType : uint8
{
Graphics = 0,
Compute,
Copy,
Count
};
enum class QueueType : uint8
{
Graphics = 0,
Compute,
Copy,
Count
};
enum class IndexFormat : uint8
{
UInt16,
UInt32
};
enum class IndexFormat : uint8
{
UInt16,
UInt32
};
enum struct SwapChainComposition : uint8
{
SDR,
SDR_LINEAR,
HDR_EXTENDED_LINEAR,
HDR10_ST2084
};
enum struct SwapChainComposition : uint8
{
SDR,
SDR_LINEAR,
HDR_EXTENDED_LINEAR,
HDR10_ST2084
};
// PresentMode from highest to lowest latency
// Vsync prevents tearing. Enqueue ready images.
// Mailbox prevents tearing. When image is ready, replace any pending image
// Immediate replace current image as soon as possible. Can cause tearing
enum struct PresentMode : uint8
{
VSync,
Mailbox,
Immediate
};
// PresentMode from highest to lowest latency
// Vsync prevents tearing. Enqueue ready images.
// Mailbox prevents tearing. When image is ready, replace any pending image
// Immediate replace current image as soon as possible. Can cause tearing
enum struct PresentMode : uint8
{
VSync,
Mailbox,
Immediate
};
struct GraphicsViewPort
{
float X;
float Y;
float Width;
float Height;
float MinDepth;
float MaxDepth;
};
struct GraphicsViewPort
{
float X;
float Y;
float Width;
float Height;
float MinDepth;
float MaxDepth;
};
extern JULIET_API GraphicsDevice* CreateGraphicsDevice(GraphicsConfig config);
extern JULIET_API void DestroyGraphicsDevice(NonNullPtr<GraphicsDevice> device);
extern JULIET_API GraphicsDevice* CreateGraphicsDevice(GraphicsConfig config);
extern JULIET_API void DestroyGraphicsDevice(NonNullPtr<GraphicsDevice> device);
// Attach To Window
extern JULIET_API bool AttachToWindow(NonNullPtr<GraphicsDevice> device, NonNullPtr<Window> window);
extern JULIET_API void DetachFromWindow(NonNullPtr<GraphicsDevice> device, NonNullPtr<Window> window);
// Attach To Window
extern JULIET_API bool AttachToWindow(NonNullPtr<GraphicsDevice> device, NonNullPtr<Window> window);
extern JULIET_API void DetachFromWindow(NonNullPtr<GraphicsDevice> device, NonNullPtr<Window> window);
// SwapChain
extern JULIET_API bool AcquireSwapChainTexture(NonNullPtr<CommandList> commandList, NonNullPtr<Window> window,
Texture** swapChainTexture);
extern JULIET_API bool WaitAndAcquireSwapChainTexture(NonNullPtr<CommandList> commandList,
NonNullPtr<Window> window, Texture** swapChainTexture);
extern JULIET_API bool WaitForSwapchain(NonNullPtr<GraphicsDevice> device, NonNullPtr<Window> window);
extern JULIET_API TextureFormat GetSwapChainTextureFormat(NonNullPtr<GraphicsDevice> device, NonNullPtr<Window> window);
// SwapChain
extern JULIET_API bool AcquireSwapChainTexture(NonNullPtr<CommandList> commandList, NonNullPtr<Window> window,
Texture** swapChainTexture);
extern JULIET_API bool WaitAndAcquireSwapChainTexture(NonNullPtr<CommandList> commandList,
NonNullPtr<Window> window, Texture** swapChainTexture);
extern JULIET_API bool WaitForSwapchain(NonNullPtr<GraphicsDevice> device, NonNullPtr<Window> window);
extern JULIET_API TextureFormat GetSwapChainTextureFormat(NonNullPtr<GraphicsDevice> device, NonNullPtr<Window> window);
// Textures
extern JULIET_API Texture* CreateTexture(NonNullPtr<GraphicsDevice> device, const TextureCreateInfo& createInfo);
extern JULIET_API void DestroyTexture(NonNullPtr<GraphicsDevice> device, NonNullPtr<Texture> texture);
// Textures
extern JULIET_API Texture* CreateTexture(NonNullPtr<GraphicsDevice> device, const TextureCreateInfo& createInfo);
extern JULIET_API void DestroyTexture(NonNullPtr<GraphicsDevice> device, NonNullPtr<Texture> texture);
// Command List
extern JULIET_API CommandList* AcquireCommandList(NonNullPtr<GraphicsDevice> device, QueueType queueType = QueueType::Graphics);
extern JULIET_API void SubmitCommandLists(NonNullPtr<CommandList> commandList);
// Command List
extern JULIET_API CommandList* AcquireCommandList(NonNullPtr<GraphicsDevice> device, QueueType queueType = QueueType::Graphics);
extern JULIET_API void SubmitCommandLists(NonNullPtr<CommandList> commandList);
// RenderPass
extern JULIET_API RenderPass* BeginRenderPass(NonNullPtr<CommandList> commandList, ColorTargetInfo& colorTargetInfo,
DepthStencilTargetInfo* depthStencilTargetInfo = nullptr);
extern JULIET_API RenderPass* BeginRenderPass(NonNullPtr<CommandList> commandList,
NonNullPtr<const ColorTargetInfo> colorTargetInfos, uint32 colorTargetInfoCount,
DepthStencilTargetInfo* depthStencilTargetInfo = nullptr);
extern JULIET_API void EndRenderPass(NonNullPtr<RenderPass> renderPass);
// RenderPass
extern JULIET_API RenderPass* BeginRenderPass(NonNullPtr<CommandList> commandList, ColorTargetInfo& colorTargetInfo,
DepthStencilTargetInfo* depthStencilTargetInfo = nullptr);
extern JULIET_API RenderPass* BeginRenderPass(NonNullPtr<CommandList> commandList,
NonNullPtr<const ColorTargetInfo> colorTargetInfos, uint32 colorTargetInfoCount,
DepthStencilTargetInfo* depthStencilTargetInfo = nullptr);
extern JULIET_API void EndRenderPass(NonNullPtr<RenderPass> renderPass);
extern JULIET_API void SetGraphicsViewPort(NonNullPtr<RenderPass> renderPass, const GraphicsViewPort& viewPort);
extern JULIET_API void SetScissorRect(NonNullPtr<RenderPass> renderPass, const struct Rectangle& rectangle);
extern JULIET_API void SetBlendConstants(NonNullPtr<RenderPass> renderPass, FColor blendConstants);
extern JULIET_API void SetStencilReference(NonNullPtr<RenderPass> renderPass, uint8 reference);
extern JULIET_API void SetGraphicsViewPort(NonNullPtr<RenderPass> renderPass, const GraphicsViewPort& viewPort);
extern JULIET_API void SetScissorRect(NonNullPtr<RenderPass> renderPass, const Rectangle& rectangle);
extern JULIET_API void SetBlendConstants(NonNullPtr<RenderPass> renderPass, FColor blendConstants);
extern JULIET_API void SetStencilReference(NonNullPtr<RenderPass> renderPass, uint8 reference);
extern JULIET_API void BindGraphicsPipeline(NonNullPtr<RenderPass> renderPass, NonNullPtr<GraphicsPipeline> graphicsPipeline);
extern JULIET_API void DrawPrimitives(NonNullPtr<RenderPass> renderPass, uint32 numVertices, uint32 numInstances,
uint32 firstVertex, uint32 firstInstance);
extern JULIET_API void DrawIndexedPrimitives(NonNullPtr<RenderPass> renderPass, uint32 numIndices, uint32 numInstances,
uint32 firstIndex, uint32 vertexOffset, uint32 firstInstance);
extern JULIET_API void BindGraphicsPipeline(NonNullPtr<RenderPass> renderPass, NonNullPtr<GraphicsPipeline> graphicsPipeline);
extern JULIET_API void DrawPrimitives(NonNullPtr<RenderPass> renderPass, uint32 numVertices, uint32 numInstances,
uint32 firstVertex, uint32 firstInstance);
extern JULIET_API void DrawIndexedPrimitives(NonNullPtr<RenderPass> renderPass, uint32 numIndices, uint32 numInstances,
uint32 firstIndex, uint32 vertexOffset, uint32 firstInstance);
extern JULIET_API void SetIndexBuffer(NonNullPtr<CommandList> commandList, NonNullPtr<GraphicsBuffer> buffer,
IndexFormat format, size_t indexCount, index_t offset);
extern JULIET_API void SetIndexBuffer(NonNullPtr<CommandList> commandList, NonNullPtr<GraphicsBuffer> buffer,
IndexFormat format, size_t indexCount, index_t offset);
extern JULIET_API void SetPushConstants(NonNullPtr<CommandList> commandList, ShaderStage stage,
uint32 rootParameterIndex, uint32 numConstants, const void* constants);
extern JULIET_API void SetPushConstants(NonNullPtr<CommandList> commandList, ShaderStage stage,
uint32 rootParameterIndex, uint32 numConstants, const void* constants);
// Fences
extern JULIET_API bool WaitUntilGPUIsIdle(NonNullPtr<GraphicsDevice> device);
// Fences
extern JULIET_API bool WaitUntilGPUIsIdle(NonNullPtr<GraphicsDevice> device);
// Shaders
extern JULIET_API Shader* CreateShader(NonNullPtr<GraphicsDevice> device, String filename, ShaderCreateInfo& shaderCreateInfo);
extern JULIET_API void DestroyShader(NonNullPtr<GraphicsDevice> device, NonNullPtr<Shader> shader);
// Shaders
extern JULIET_API Shader* CreateShader(NonNullPtr<GraphicsDevice> device, String filename, ShaderCreateInfo& shaderCreateInfo);
extern JULIET_API void DestroyShader(NonNullPtr<GraphicsDevice> device, NonNullPtr<Shader> shader);
// Pipelines
extern JULIET_API GraphicsPipeline* CreateGraphicsPipeline(NonNullPtr<GraphicsDevice> device,
const GraphicsPipelineCreateInfo& createInfo);
extern JULIET_API void DestroyGraphicsPipeline(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsPipeline> graphicsPipeline);
// Pipelines
extern JULIET_API GraphicsPipeline* CreateGraphicsPipeline(NonNullPtr<GraphicsDevice> device,
const GraphicsPipelineCreateInfo& createInfo);
extern JULIET_API void DestroyGraphicsPipeline(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsPipeline> graphicsPipeline);
#if ALLOW_SHADER_HOT_RELOAD
// Allows updating the graphics pipeline shaders. Can update either one or both shaders.
extern JULIET_API bool UpdateGraphicsPipelineShaders(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsPipeline> graphicsPipeline,
Shader* optional_vertexShader, Shader* optional_fragmentShader);
// Allows updating the graphics pipeline shaders. Can update either one or both shaders.
extern JULIET_API bool UpdateGraphicsPipelineShaders(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsPipeline> graphicsPipeline,
Shader* optional_vertexShader, Shader* optional_fragmentShader);
#endif
// Buffers
extern JULIET_API GraphicsBuffer* CreateGraphicsBuffer(NonNullPtr<GraphicsDevice> device, const BufferCreateInfo& createInfo);
extern JULIET_API GraphicsTransferBuffer* CreateGraphicsTransferBuffer(NonNullPtr<GraphicsDevice> device,
const TransferBufferCreateInfo& createInfo);
extern JULIET_API void* MapGraphicsBuffer(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsBuffer> buffer);
extern JULIET_API void UnmapGraphicsBuffer(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsBuffer> buffer);
extern JULIET_API void* MapGraphicsTransferBuffer(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsTransferBuffer> buffer);
extern JULIET_API void UnmapGraphicsTransferBuffer(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsTransferBuffer> buffer);
extern JULIET_API void CopyBuffer(NonNullPtr<CommandList> commandList, NonNullPtr<GraphicsBuffer> dst,
NonNullPtr<GraphicsTransferBuffer> src, size_t size, size_t dstOffset = 0,
size_t srcOffset = 0);
extern JULIET_API void CopyBufferToTexture(NonNullPtr<CommandList> commandList, NonNullPtr<Texture> dst,
NonNullPtr<GraphicsTransferBuffer> src);
// Buffers
extern JULIET_API GraphicsBuffer* CreateGraphicsBuffer(NonNullPtr<GraphicsDevice> device, const BufferCreateInfo& createInfo);
extern JULIET_API GraphicsTransferBuffer* CreateGraphicsTransferBuffer(NonNullPtr<GraphicsDevice> device,
const TransferBufferCreateInfo& createInfo);
extern JULIET_API void* MapGraphicsBuffer(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsBuffer> buffer);
extern JULIET_API void UnmapGraphicsBuffer(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsBuffer> buffer);
extern JULIET_API void* MapGraphicsTransferBuffer(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsTransferBuffer> buffer);
extern JULIET_API void UnmapGraphicsTransferBuffer(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsTransferBuffer> buffer);
extern JULIET_API void CopyBuffer(NonNullPtr<CommandList> commandList, NonNullPtr<GraphicsBuffer> dst,
NonNullPtr<GraphicsTransferBuffer> src, size_t size, size_t dstOffset = 0,
size_t srcOffset = 0);
extern JULIET_API void CopyBufferToTexture(NonNullPtr<CommandList> commandList, NonNullPtr<Texture> dst,
NonNullPtr<GraphicsTransferBuffer> src);
extern JULIET_API void TransitionBufferToReadable(NonNullPtr<CommandList> commandList, NonNullPtr<GraphicsBuffer> buffer);
extern JULIET_API uint32 GetDescriptorIndex(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsBuffer> buffer);
extern JULIET_API uint32 GetDescriptorIndex(NonNullPtr<GraphicsDevice> device, NonNullPtr<Texture> texture);
extern JULIET_API void TransitionBufferToReadable(NonNullPtr<CommandList> commandList, NonNullPtr<GraphicsBuffer> buffer);
extern JULIET_API uint32 GetDescriptorIndex(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsBuffer> buffer);
extern JULIET_API uint32 GetDescriptorIndex(NonNullPtr<GraphicsDevice> device, NonNullPtr<Texture> texture);
extern JULIET_API void DestroyGraphicsBuffer(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsBuffer> buffer);
extern JULIET_API void DestroyGraphicsTransferBuffer(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsTransferBuffer> buffer);
} // namespace Juliet
extern JULIET_API void DestroyGraphicsBuffer(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsBuffer> buffer);
extern JULIET_API void DestroyGraphicsTransferBuffer(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsTransferBuffer> buffer);
+27 -30
View File
@@ -1,36 +1,33 @@
#pragma once
#pragma once
namespace Juliet
enum class BufferUsage : uint8
{
enum class BufferUsage : uint8
{
None = 0,
IndexBuffer = 1 << 0,
ConstantBuffer = 1 << 1,
StructuredBuffer = 1 << 2,
};
None = 0,
IndexBuffer = 1 << 0,
ConstantBuffer = 1 << 1,
StructuredBuffer = 1 << 2,
};
enum class TransferBufferUsage : uint8
{
Download,
Upload
};
enum class TransferBufferUsage : uint8
{
Download,
Upload
};
struct BufferCreateInfo
{
size_t Size;
size_t Stride;
BufferUsage Usage;
bool IsDynamic;
};
struct BufferCreateInfo
{
size_t Size;
size_t Stride;
BufferUsage Usage;
bool IsDynamic;
};
struct TransferBufferCreateInfo
{
size_t Size;
TransferBufferUsage Usage;
};
struct TransferBufferCreateInfo
{
size_t Size;
TransferBufferUsage Usage;
};
// Opaque
struct GraphicsBuffer;
struct GraphicsTransferBuffer;
} // namespace Juliet
// Opaque
struct GraphicsBuffer;
struct GraphicsTransferBuffer;
+9 -16
View File
@@ -1,26 +1,19 @@
#pragma once
#include <Juliet.h>
#include <Core/Common/CoreTypes.h>
#if JULIET_DEBUG
#define ALLOW_SHADER_HOT_RELOAD 1
#else
#define ALLOW_SHADER_HOT_RELOAD 0
#endif
namespace Juliet
enum class GraphicsDriverType : uint8
{
enum class DriverType : uint8
{
Any = 0,
DX12 = 1,
};
Any = 0,
DX12 = 1,
};
struct GraphicsConfig
{
DriverType PreferredDriver = DriverType::DX12;
bool EnableDebug;
};
} // namespace Juliet
struct GraphicsConfig
{
GraphicsDriverType PreferredDriver = GraphicsDriverType::DX12;
bool EnableDebug;
};
+187 -190
View File
@@ -1,225 +1,222 @@
#pragma once
#pragma once
#include <Graphics/Shader.h>
#include <Graphics/Texture.h>
namespace Juliet
// Forward Declare
struct ColorTargetDescription;
enum class FillMode : uint8
{
// Forward Declare
struct ColorTargetDescription;
Solid,
Wireframe,
Count
};
enum class FillMode : uint8
{
Solid,
Wireframe,
Count
};
enum class CullMode : uint8
{
None,
Front,
Back,
Count
};
enum class CullMode : uint8
{
None,
Front,
Back,
Count
};
enum class FrontFace : uint8
{
CounterClockwise,
Clockwise,
Count
};
enum class FrontFace : uint8
{
CounterClockwise,
Clockwise,
Count
};
enum class PrimitiveType : uint8
{
TriangleList,
TriangleStrip,
LineList,
LineStrip,
PointList,
Count
};
enum class PrimitiveType : uint8
{
TriangleList,
TriangleStrip,
LineList,
LineStrip,
PointList,
Count
};
struct RasterizerState
{
FillMode FillMode;
CullMode CullMode;
FrontFace FrontFace;
struct RasterizerState
{
FillMode FillMode;
CullMode CullMode;
FrontFace FrontFace;
float DepthBiasConstantFactor; // How much depth value is added to each fragment
float DepthBiasClamp; // Maximum depth bias
float DepthBiasSlopeFactor; // Scalar applied to Fragment's slope
bool EnableDepthBias; // Bias fragment depth values
bool EnableDepthClip; // True to clip, false to clamp
};
float DepthBiasConstantFactor; // How much depth value is added to each fragment
float DepthBiasClamp; // Maximum depth bias
float DepthBiasSlopeFactor; // Scalar applied to Fragment's slope
bool EnableDepthBias; // Bias fragment depth values
bool EnableDepthClip; // True to clip, false to clamp
};
enum class VertexInputRate : uint8
{
Vertex, // Use vertex index
Instance, // Use instance index
Count
};
enum class VertexInputRate : uint8
{
Vertex, // Use vertex index
Instance, // Use instance index
Count
};
struct VertexBufferDescription
{
uint32 Slot; // Binding Slot
uint32 PitchInBytes; // Pitch between two elements
VertexInputRate InputRate;
uint32 InstanceStepRate; // Only used when input rate == Instance. Number of instances to draw before advancing in the instance buffer by 1
};
struct VertexBufferDescription
{
uint32 Slot; // Binding Slot
uint32 PitchInBytes; // Pitch between two elements
VertexInputRate InputRate;
uint32 InstanceStepRate; // Only used when input rate == Instance. Number of instances to draw before advancing in the instance buffer by 1
};
enum class VertexElementFormat : uint8
{
Invalid,
enum class VertexElementFormat : uint8
{
Invalid,
/* 32-bit Signed Integers */
Int,
Int2,
Int3,
Int4,
/* 32-bit Signed Integers */
Int,
Int2,
Int3,
Int4,
/* 32-bit Unsigned Integers */
UInt,
UInt2,
UInt3,
UInt4,
/* 32-bit Unsigned Integers */
UInt,
UInt2,
UInt3,
UInt4,
/* 32-bit Floats */
Float,
Float2,
Float3,
Float4,
/* 32-bit Floats */
Float,
Float2,
Float3,
Float4,
/* 8-bit Signed Integers */
Byte2,
Byte4,
/* 8-bit Signed Integers */
Byte2,
Byte4,
/* 8-bit Unsigned Integers */
UByte2,
UByte4,
/* 8-bit Unsigned Integers */
UByte2,
UByte4,
/* 8-bit Signed Normalized */
Byte2_Norm,
Byte4_Norm,
/* 8-bit Signed Normalized */
Byte2_Norm,
Byte4_Norm,
/* 8-bit Unsigned Normalized */
UByte2_Norm,
UByte4_Norm,
/* 8-bit Unsigned Normalized */
UByte2_Norm,
UByte4_Norm,
/* 16-bit Signed Integers */
Short2,
Short4,
/* 16-bit Signed Integers */
Short2,
Short4,
/* 16-bit Unsigned Integers */
UShort2,
UShort4,
/* 16-bit Unsigned Integers */
UShort2,
UShort4,
/* 16-bit Signed Normalized */
Short2_Norm,
Short4_Norm,
/* 16-bit Signed Normalized */
Short2_Norm,
Short4_Norm,
/* 16-bit Unsigned Normalized */
UShort2_Norm,
UShort4_Norm,
/* 16-bit Unsigned Normalized */
UShort2_Norm,
UShort4_Norm,
/* 16-bit Floats */
Half2,
Half4,
/* 16-bit Floats */
Half2,
Half4,
//
Count
};
//
Count
};
struct VertexAttribute
{
uint32 Location; // Shader input location index
uint32 BufferSlot; // Binding slot of associated vertex buffer
VertexElementFormat Format; // Size and type of attribute
uint32 Offset; // Offset of this attribute relative to the start of the vertex element
};
struct VertexAttribute
{
uint32 Location; // Shader input location index
uint32 BufferSlot; // Binding slot of associated vertex buffer
VertexElementFormat Format; // Size and type of attribute
uint32 Offset; // Offset of this attribute relative to the start of the vertex element
};
struct VertexInputState
{
const VertexBufferDescription* VertexBufferDescriptions;
uint32 NumVertexBufferDescriptions;
const VertexAttribute* VertexAttributes;
uint32 NumVertexAttributes;
};
struct VertexInputState
{
const VertexBufferDescription* VertexBufferDescriptions;
uint32 NumVertexBufferDescriptions;
const VertexAttribute* VertexAttributes;
uint32 NumVertexAttributes;
};
struct GraphicsPipelineTargetInfo
{
const ColorTargetDescription* ColorTargetDescriptions;
size_t NumColorTargets;
TextureFormat DepthStencilFormat;
bool HasDepthStencilTarget;
};
struct GraphicsPipelineTargetInfo
{
const ColorTargetDescription* ColorTargetDescriptions;
size_t NumColorTargets;
TextureFormat DepthStencilFormat;
bool HasDepthStencilTarget;
};
enum class CompareOperation : uint8
{
Invalid,
Never, // The comparison always evaluates false.
Less, // The comparison evaluates reference < test.
Equal, // The comparison evaluates reference == test.
LessOrEqual, // The comparison evaluates reference <= test.
Greater, // The comparison evaluates reference > test.
NotEqual, // The comparison evaluates reference != test.
GreaterOrEqual, // The comparison evalutes reference >= test.
Always, // The comparison always evaluates true.
Count
};
enum class CompareOperation : uint8
{
Invalid,
Never, // The comparison always evaluates false.
Less, // The comparison evaluates reference < test.
Equal, // The comparison evaluates reference == test.
LessOrEqual, // The comparison evaluates reference <= test.
Greater, // The comparison evaluates reference > test.
NotEqual, // The comparison evaluates reference != test.
GreaterOrEqual, // The comparison evalutes reference >= test.
Always, // The comparison always evaluates true.
Count
};
enum class StencilOperation : uint8
{
Invalid,
Keep, // Keeps the current value.
Zero, // Sets the value to 0.
Replace, // Sets the value to reference.
IncrementAndClamp, // Increments the current value and clamps to the maximum value.
DecrementAndClamp, // Decrements the current value and clamps to 0.
Invert, // Bitwise-inverts the current value.
IncrementAndWrap, // Increments the current value and wraps back to 0.
DecrementAndWrap, // Decrements the current value and wraps to the maximum value.
Count
};
enum class StencilOperation : uint8
{
Invalid,
Keep, // Keeps the current value.
Zero, // Sets the value to 0.
Replace, // Sets the value to reference.
IncrementAndClamp, // Increments the current value and clamps to the maximum value.
DecrementAndClamp, // Decrements the current value and clamps to 0.
Invert, // Bitwise-inverts the current value.
IncrementAndWrap, // Increments the current value and wraps back to 0.
DecrementAndWrap, // Decrements the current value and wraps to the maximum value.
Count
};
struct StencilOperationState
{
StencilOperation FailOperation; // The action performed on samples that fail the stencil test.
StencilOperation PassOperation; // The action performed on samples that pass the depth and stencil tests.
StencilOperation DepthFailOperation; // The action performed on samples that pass the stencil test and fail the depth test.
StencilOperation CompareOperation; // The comparison operator used in the stencil test.
};
struct StencilOperationState
{
StencilOperation FailOperation; // The action performed on samples that fail the stencil test.
StencilOperation PassOperation; // The action performed on samples that pass the depth and stencil tests.
StencilOperation DepthFailOperation; // The action performed on samples that pass the stencil test and fail the depth test.
StencilOperation CompareOperation; // The comparison operator used in the stencil test.
};
struct DepthStencilState
{
CompareOperation CompareOperation; // The comparison operator used for depth testing.
StencilOperationState BackStencilState; // The stencil op state for back-facing triangles.
StencilOperationState FrontStencilState; // The stencil op state for front-facing triangles.
uint8 CompareMask; // Selects the bits of the stencil values participating in the stencil test.
uint8 WriteMask; // Selects the bits of the stencil values updated by the stencil test.
bool EnableDepthTest : 1; // true enables the depth test.
bool EnableDepthWrite : 1; // true enables depth writes. Depth writes are always disabled when enable_depth_test is false.
bool EnableStencilTest : 1; // true enables the stencil test.
};
struct DepthStencilState
{
CompareOperation CompareOperation; // The comparison operator used for depth testing.
StencilOperationState BackStencilState; // The stencil op state for back-facing triangles.
StencilOperationState FrontStencilState; // The stencil op state for front-facing triangles.
uint8 CompareMask; // Selects the bits of the stencil values participating in the stencil test.
uint8 WriteMask; // Selects the bits of the stencil values updated by the stencil test.
bool EnableDepthTest : 1; // true enables the depth test.
bool EnableDepthWrite : 1; // true enables depth writes. Depth writes are always disabled when enable_depth_test is false.
bool EnableStencilTest : 1; // true enables the stencil test.
};
struct MultisampleState
{
TextureSampleCount SampleCount;
uint32 SampleMask; // Which sample should be updated. If Enabled mask == false -> 0xFFFFFFFF
bool EnableMask;
};
struct MultisampleState
{
TextureSampleCount SampleCount;
uint32 SampleMask; // Which sample should be updated. If Enabled mask == false -> 0xFFFFFFFF
bool EnableMask;
};
struct GraphicsPipelineCreateInfo
{
Shader* VertexShader;
Shader* FragmentShader;
PrimitiveType PrimitiveType;
GraphicsPipelineTargetInfo TargetInfo;
RasterizerState RasterizerState;
MultisampleState MultisampleState;
VertexInputState VertexInputState;
DepthStencilState DepthStencilState;
};
struct GraphicsPipelineCreateInfo
{
Shader* VertexShader;
Shader* FragmentShader;
PrimitiveType PrimitiveType;
GraphicsPipelineTargetInfo TargetInfo;
RasterizerState RasterizerState;
MultisampleState MultisampleState;
VertexInputState VertexInputState;
DepthStencilState DepthStencilState;
};
// Opaque type
struct GraphicsPipeline;
} // namespace Juliet
// Opaque type
struct GraphicsPipeline;
+5 -8
View File
@@ -1,12 +1,9 @@
#pragma once
#pragma once
#include <Graphics/Graphics.h>
#include <Juliet.h>
namespace Juliet
{
extern bool ImGuiRenderer_Initialize(GraphicsDevice* device);
extern void ImGuiRenderer_Shutdown(GraphicsDevice* device);
extern void ImGuiRenderer_NewFrame();
extern JULIET_API void ImGuiRenderer_Render(CommandList* cmdList, RenderPass* renderPass);
} // namespace Juliet
extern bool ImGuiRenderer_Initialize(GraphicsDevice* device);
extern void ImGuiRenderer_Shutdown(GraphicsDevice* device);
extern void ImGuiRenderer_NewFrame();
extern JULIET_API void ImGuiRenderer_Render(CommandList* cmdList, RenderPass* renderPass);
+7 -10
View File
@@ -1,15 +1,12 @@
#pragma once
#pragma once
#include <Juliet.h>
#include <Core/Math/Vector.h>
namespace Juliet
struct PointLight
{
struct PointLight
{
Vector3 Position;
float Radius;
Vector3 Color;
float Intensity;
};
} // namespace Juliet
Vector3 Position;
float Radius;
Vector3 Color;
float Intensity;
};
+25 -30
View File
@@ -1,40 +1,35 @@
#pragma once
#pragma once
#include <Core/Common/CoreTypes.h>
#include <Core/Common/NonNullPtr.h>
#include <Core/Common/String.h>
#include <Core/Math/Matrix.h>
#include <Core/Math/Vector.h>
#include <Juliet.h>
namespace Juliet
struct Arena;
struct Vertex;
using MeshAssetID = index_t;
using MaterialAssetID = index_t;
using MeshInstanceID = index_t;
struct MeshAsset
{
struct Arena;
struct Vertex;
String Name;
size_t VertexCount;
size_t IndexCount;
using MeshAssetID = index_t;
using MaterialAssetID = index_t;
using MeshInstanceID = index_t;
index_t VertexOffset;
index_t IndexOffset;
};
struct MeshAsset
{
String Name;
size_t VertexCount;
size_t IndexCount;
struct MaterialAsset
{
Vector4 AlbedoColor = { 1.0f, 1.0f, 1.0f, 1.0f };
};
index_t VertexOffset;
index_t IndexOffset;
};
struct MaterialAsset
{
Vector4 AlbedoColor = {1.0f, 1.0f, 1.0f, 1.0f};
};
struct MeshInstance
{
MeshAssetID MeshAsset;
MaterialAssetID MaterialAsset;
Matrix Transform = MatrixIdentity();
};
} // namespace Juliet
struct MeshInstance
{
MeshAssetID MeshAsset;
MaterialAssetID MaterialAsset;
Matrix Transform = MatrixIdentity();
};
+38 -41
View File
@@ -1,4 +1,4 @@
#pragma once
#pragma once
#include <Core/Container/Vector.h>
#include <Core/Math/Matrix.h>
@@ -9,52 +9,49 @@
#include <Graphics/Mesh.h>
#include <Juliet.h>
namespace Juliet
{
struct GraphicsTransferBuffer;
struct RenderPass;
struct CommandList;
struct GraphicsBuffer;
struct Window;
struct GraphicsPipeline;
struct GraphicsDevice;
using LightID = index_t;
struct GraphicsTransferBuffer;
struct RenderPass;
struct CommandList;
struct GraphicsBuffer;
struct Window;
struct GraphicsPipeline;
struct GraphicsDevice;
using LightID = index_t;
constexpr size_t kGeometryPage = Megabytes(64);
constexpr size_t kIndexPage = Megabytes(32);
constexpr size_t kDefaultMeshNumber = 500;
constexpr size_t kDefaultVertexCount = 2'000'000; // Fit less than one geometry page
constexpr size_t kDefaultIndexCount = 16'000'000; // Fit less than one index page
constexpr size_t kDefaultLightCount = 1024;
constexpr size_t kGeometryPage = Megabytes(64);
constexpr size_t kIndexPage = Megabytes(32);
constexpr size_t kDefaultMeshNumber = 500;
constexpr size_t kDefaultVertexCount = 2'000'000; // Fit less than one geometry page
constexpr size_t kDefaultIndexCount = 16'000'000; // Fit less than one index page
constexpr size_t kDefaultLightCount = 1024;
JULIET_API void InitializeMeshRenderer(NonNullPtr<Arena> assetArena, NonNullPtr<Arena> instanceArena);
[[nodiscard]] JULIET_API bool InitializeMeshRendererGraphics(NonNullPtr<GraphicsDevice> device, NonNullPtr<Window> window);
JULIET_API void ShutdownMeshRendererGraphics();
JULIET_API void ShutdownMeshRenderer();
JULIET_API void LoadMeshesOnGPU(NonNullPtr<CommandList> cmdList);
JULIET_API void RenderMeshes(NonNullPtr<CommandList> cmdList, NonNullPtr<RenderPass> pass, const Matrix& viewProjection);
JULIET_API void InitializeMeshRenderer(NonNullPtr<Arena> assetArena, NonNullPtr<Arena> instanceArena);
[[nodiscard]] JULIET_API bool InitializeMeshRendererGraphics(NonNullPtr<GraphicsDevice> device, NonNullPtr<Window> window);
JULIET_API void ShutdownMeshRendererGraphics();
JULIET_API void ShutdownMeshRenderer();
JULIET_API void LoadMeshesOnGPU(NonNullPtr<CommandList> cmdList);
JULIET_API void RenderMeshes(NonNullPtr<CommandList> cmdList, NonNullPtr<RenderPass> pass, const Matrix& viewProjection);
// Lights
[[nodiscard]] JULIET_API LightID AddPointLight(const PointLight& light);
JULIET_API void SetPointLightPosition(LightID id, const Vector3& position);
JULIET_API void SetPointLightColor(LightID id, const Vector3& color);
JULIET_API void SetPointLightRadius(LightID id, float radius);
JULIET_API void SetPointLightIntensity(LightID id, float intensity);
JULIET_API void ClearPointLights();
JULIET_API void SetGlobalLight(const Vector3& direction, const Vector3& color, float ambientIntensity);
// Lights
[[nodiscard]] JULIET_API LightID AddPointLight(const PointLight& light);
JULIET_API void SetPointLightPosition(LightID id, const Vector3& position);
JULIET_API void SetPointLightColor(LightID id, const Vector3& color);
JULIET_API void SetPointLightRadius(LightID id, float radius);
JULIET_API void SetPointLightIntensity(LightID id, float intensity);
JULIET_API void ClearPointLights();
JULIET_API void SetGlobalLight(const Vector3& direction, const Vector3& color, float ambientIntensity);
// Assets & Instances
[[nodiscard]] JULIET_API MeshAssetID GetOrCreateMeshAsset(String name);
[[nodiscard]] JULIET_API MeshInstanceID CreateMeshInstance(MeshAssetID meshAsset, MaterialAssetID materialAsset, const Matrix& transform);
JULIET_API void SetMeshInstanceTransform(MeshInstanceID id, const Matrix& transform);
// Assets & Instances
[[nodiscard]] JULIET_API MeshAssetID GetOrCreateMeshAsset(String name);
[[nodiscard]] JULIET_API MeshInstanceID CreateMeshInstance(MeshAssetID meshAsset, MaterialAssetID materialAsset, const Matrix& transform);
JULIET_API void SetMeshInstanceTransform(MeshInstanceID id, const Matrix& transform);
// Primitives
JULIET_API MeshAssetID GetCubePrimitiveMeshAssetID();
JULIET_API MeshAssetID GetQuadPrimitiveMeshAssetID();
JULIET_API MeshAssetID GetSpherePrimitiveMeshAssetID();
// Primitives
JULIET_API MeshAssetID GetCubePrimitiveMeshAssetID();
JULIET_API MeshAssetID GetQuadPrimitiveMeshAssetID();
JULIET_API MeshAssetID GetSpherePrimitiveMeshAssetID();
#if ALLOW_SHADER_HOT_RELOAD
JULIET_API void ReloadMeshRendererShaders();
JULIET_API void ReloadMeshRendererShaders();
#endif
} // namespace Juliet
+21 -24
View File
@@ -1,32 +1,29 @@
#pragma once
#pragma once
#include <Core/Math/Matrix.h>
#include <Core/Math/Vector.h>
#include <Juliet.h>
namespace Juliet
struct PushData
{
struct PushData
{
Matrix ViewProjection;
uint32 MeshIndex;
uint32 TransformsBufferIndex;
uint32 BufferIndex;
uint32 TextureIndex;
uint32 VertexOffset;
uint32 LightBufferIndex;
uint32 ActiveLightCount;
float GlobalAmbientIntensity;
Matrix ViewProjection;
uint32 MeshIndex;
uint32 TransformsBufferIndex;
uint32 BufferIndex;
uint32 TextureIndex;
uint32 VertexOffset;
uint32 LightBufferIndex;
uint32 ActiveLightCount;
float GlobalAmbientIntensity;
Vector3 GlobalLightDirection;
uint32 Pad1;
Vector3 GlobalLightDirection;
uint32 Pad1;
Vector3 GlobalLightColor;
uint32 Pad2;
float Scale[2];
float Translate[2];
Vector4 MeshAlbedo;
};
} // namespace Juliet
Vector3 GlobalLightColor;
uint32 Pad2;
float Scale[2];
float Translate[2];
Vector4 MeshAlbedo;
};
+95 -98
View File
@@ -1,115 +1,112 @@
#pragma once
#pragma once
#include <Graphics/Colors.h>
#include <Graphics/Texture.h>
namespace Juliet
enum struct LoadOperation : uint8
{
enum struct LoadOperation : uint8
Load, // Load the texture from memory (preserve)
Clear, // Clear the texture
Ignore // Ignore the content of the texture (undefined)
};
enum struct StoreOperation : uint8
{
Store, // Store the result of the render pass into memory
Ignore, // Whatever is generated is ignored (undefined)
Resolve, // Resolve MipMaps into non mip map texture. Discard MipMap content
ResolveAndStore // Same but store the MipMap content to memory
};
struct ColorTargetInfo
{
Texture* TargetTexture;
uint32 MipLevel;
union
{
Load, // Load the texture from memory (preserve)
Clear, // Clear the texture
Ignore // Ignore the content of the texture (undefined)
uint32 DepthPlane;
uint32 LayerIndex;
};
bool CycleTexture; // Whether the texture should be cycled if already bound (and load operation != LOAD)
enum struct StoreOperation : uint8
{
Store, // Store the result of the render pass into memory
Ignore, // Whatever is generated is ignored (undefined)
Resolve, // Resolve MipMaps into non mip map texture. Discard MipMap content
ResolveAndStore // Same but store the MipMap content to memory
};
Texture* ResolveTexture;
uint32 ResolveMipLevel;
uint32 ResolveLayerIndex;
bool CycleResolveTexture;
struct ColorTargetInfo
{
Texture* TargetTexture;
uint32 MipLevel;
union
{
uint32 DepthPlane;
uint32 LayerIndex;
};
bool CycleTexture; // Whether the texture should be cycled if already bound (and load operation != LOAD)
FColor ClearColor;
LoadOperation LoadOperation;
StoreOperation StoreOperation;
};
Texture* ResolveTexture;
uint32 ResolveMipLevel;
uint32 ResolveLayerIndex;
bool CycleResolveTexture;
struct DepthStencilTargetInfo
{
Texture* TargetTexture;
uint32 MipLevel;
uint32 LayerIndex;
FColor ClearColor;
LoadOperation LoadOperation;
StoreOperation StoreOperation;
};
struct DepthStencilTargetInfo
{
Texture* TargetTexture;
uint32 MipLevel;
uint32 LayerIndex;
float ClearDepth;
uint8 ClearStencil;
LoadOperation LoadOperation;
StoreOperation StoreOperation;
};
float ClearDepth;
uint8 ClearStencil;
LoadOperation LoadOperation;
StoreOperation StoreOperation;
};
enum class BlendFactor : uint8
{
Invalid,
Zero,
One,
Src_Color,
One_Minus_Src_Color,
Dst_Color,
One_Minus_Dst_Color,
Src_Alpha,
One_Minus_Src_Alpha,
Dst_Alpha,
One_Minus_Dst_Alpha,
Constant_Color,
One_MINUS_Constant_Color,
Src_Alpha_Saturate, // min(source alpha, 1 - destination alpha)
Count
};
enum class BlendFactor : uint8
{
Invalid,
Zero,
One,
Src_Color,
One_Minus_Src_Color,
Dst_Color,
One_Minus_Dst_Color,
Src_Alpha,
One_Minus_Src_Alpha,
Dst_Alpha,
One_Minus_Dst_Alpha,
Constant_Color,
One_MINUS_Constant_Color,
Src_Alpha_Saturate, // min(source alpha, 1 - destination alpha)
Count
};
enum class BlendOperation : uint8
{
Invalid,
Add, // (source * source_factor) + (destination * destination_factor)
Subtract, // (source * source_factor) - (destination * destination_factor)
ReverseSubtract, // (destination * destination_factor) - (source * source_factor)
Min, // min(source, destination)
Max, // max(source, destination)
Count
};
enum class BlendOperation : uint8
{
Invalid,
Add, // (source * source_factor) + (destination * destination_factor)
Subtract, // (source * source_factor) - (destination * destination_factor)
ReverseSubtract, // (destination * destination_factor) - (source * source_factor)
Min, // min(source, destination)
Max, // max(source, destination)
Count
};
enum class ColorComponentFlags : uint8
{
R = 1u << 0,
G = 1u << 1,
B = 1u << 2,
A = 1u << 3
};
enum class ColorComponentFlags : uint8
{
R = 1u << 0,
G = 1u << 1,
B = 1u << 2,
A = 1u << 3
};
struct ColorTargetBlendState
{
BlendFactor SourceColorBlendFactor; // The value to be multiplied by the source RGB value.
BlendFactor DestinationColorBlendFactor; // The value to be multiplied by the destination RGB value.
BlendOperation ColorBlendOperation; // The blend operation for the RGB components.
BlendFactor SourceAlphaBlendFactor; // The value to be multiplied by the source alpha.
BlendFactor DestinationAlphaBlendFactor; // The value to be multiplied by the destination alpha.
BlendOperation AlphaBlendOperation; // The blend operation for the alpha component.
ColorComponentFlags ColorWriteMask; // A bitmask specifying which of the RGBA components are enabled for writing. Writes to all channels if enable_color_write_mask is false.
bool EnableBlend : 1; // Whether blending is enabled for the color target.
bool EnableColorWriteMask : 1; // Whether the color write mask is enabled.
};
struct ColorTargetBlendState
{
BlendFactor SourceColorBlendFactor; // The value to be multiplied by the source RGB value.
BlendFactor DestinationColorBlendFactor; // The value to be multiplied by the destination RGB value.
BlendOperation ColorBlendOperation; // The blend operation for the RGB components.
BlendFactor SourceAlphaBlendFactor; // The value to be multiplied by the source alpha.
BlendFactor DestinationAlphaBlendFactor; // The value to be multiplied by the destination alpha.
BlendOperation AlphaBlendOperation; // The blend operation for the alpha component.
ColorComponentFlags ColorWriteMask; // A bitmask specifying which of the RGBA components are enabled for writing. Writes to all channels if enable_color_write_mask is false.
bool EnableBlend : 1; // Whether blending is enabled for the color target.
bool EnableColorWriteMask : 1; // Whether the color write mask is enabled.
};
struct ColorTargetDescription
{
TextureFormat Format;
ColorTargetBlendState BlendState;
};
struct ColorTargetDescription
{
TextureFormat Format;
ColorTargetBlendState BlendState;
};
// Opaque Type
struct RenderPass;
} // namespace Juliet
// Opaque Type
struct RenderPass;
+14 -17
View File
@@ -1,23 +1,20 @@
#pragma once
#pragma once
#include <Core/Common/String.h>
namespace Juliet
// Opaque type
struct Shader;
enum class ShaderStage : uint8
{
// Opaque type
struct Shader;
Vertex,
Fragment,
Compute
};
enum class ShaderStage : uint8
{
Vertex,
Fragment,
Compute
};
struct ShaderCreateInfo
{
ShaderStage Stage;
String EntryPoint;
};
struct ShaderCreateInfo
{
ShaderStage Stage;
String EntryPoint;
};
} // namespace Juliet
+15 -18
View File
@@ -1,4 +1,4 @@
#pragma once
#pragma once
#include <Juliet.h>
@@ -6,26 +6,23 @@
#include <Core/Math/Matrix.h>
#include <Graphics/GraphicsConfig.h>
namespace Juliet
struct RenderPass;
struct CommandList;
struct Window;
struct GraphicsPipeline;
struct GraphicsDevice;
struct SkyboxRenderer
{
struct RenderPass;
struct CommandList;
struct Window;
struct GraphicsPipeline;
struct GraphicsDevice;
GraphicsDevice* Device;
GraphicsPipeline* Pipeline;
};
struct SkyboxRenderer
{
GraphicsDevice* Device;
GraphicsPipeline* Pipeline;
};
[[nodiscard]] JULIET_API bool InitializeSkyboxRenderer(NonNullPtr<GraphicsDevice> device, NonNullPtr<Window> window);
JULIET_API void ShutdownSkyboxRenderer();
JULIET_API void RenderSkybox(NonNullPtr<CommandList> cmdList, NonNullPtr<RenderPass> pass, const Matrix& viewProjection);
[[nodiscard]] JULIET_API bool InitializeSkyboxRenderer(NonNullPtr<GraphicsDevice> device, NonNullPtr<Window> window);
JULIET_API void ShutdownSkyboxRenderer();
JULIET_API void RenderSkybox(NonNullPtr<CommandList> cmdList, NonNullPtr<RenderPass> pass, const Matrix& viewProjection);
#if ALLOW_SHADER_HOT_RELOAD
JULIET_API void ReloadSkyboxShaders();
JULIET_API void ReloadSkyboxShaders();
#endif
} // namespace Juliet
+176 -179
View File
@@ -1,183 +1,180 @@
#pragma once
#pragma once
namespace Juliet
enum struct TextureFormat : uint8
{
enum struct TextureFormat : uint8
Invalid,
/* Unsigned Normalized Float Color Formats */
A8_UNORM,
R8_UNORM,
R8G8_UNORM,
R8G8B8A8_UNORM,
R16_UNORM,
R16G16_UNORM,
R16G16B16A16_UNORM,
R10G10B10A2_UNORM,
B5G6R5_UNORM,
B5G5R5A1_UNORM,
B4G4R4A4_UNORM,
B8G8R8A8_UNORM,
/* Compressed Unsigned Normalized Float Color Formats */
BC1_RGBA_UNORM,
BC2_RGBA_UNORM,
BC3_RGBA_UNORM,
BC4_R_UNORM,
BC5_RG_UNORM,
BC7_RGBA_UNORM,
/* Compressed Signed Float Color Formats */
BC6H_RGB_FLOAT,
/* Compressed Unsigned Float Color Formats */
BC6H_RGB_UFLOAT,
/* Signed Normalized Float Color Formats */
R8_SNORM,
R8G8_SNORM,
R8G8B8A8_SNORM,
R16_SNORM,
R16G16_SNORM,
R16G16B16A16_SNORM,
/* Signed Float Color Formats */
R16_FLOAT,
R16G16_FLOAT,
R16G16B16A16_FLOAT,
R32_FLOAT,
R32G32_FLOAT,
R32G32B32A32_FLOAT,
/* Unsigned Float Color Formats */
R11G11B10_UFLOAT,
/* Unsigned Integer Color Formats */
R8_UINT,
R8G8_UINT,
R8G8B8A8_UINT,
R16_UINT,
R16G16_UINT,
R16G16B16A16_UINT,
R32_UINT,
R32G32_UINT,
R32G32B32A32_UINT,
/* Signed Integer Color Formats */
R8_INT,
R8G8_INT,
R8G8B8A8_INT,
R16_INT,
R16G16_INT,
R16G16B16A16_INT,
R32_INT,
R32G32_INT,
R32G32B32A32_INT,
/* SRGB Unsigned Normalized Color Formats */
R8G8B8A8_UNORM_SRGB,
B8G8R8A8_UNORM_SRGB,
/* Compressed SRGB Unsigned Normalized Color Formats */
BC1_RGBA_UNORM_SRGB,
BC2_RGBA_UNORM_SRGB,
BC3_RGBA_UNORM_SRGB,
BC7_RGBA_UNORM_SRGB,
/* Depth Formats */
D16_UNORM,
D24_UNORM,
D32_FLOAT,
D24_UNORM_S8_UINT,
D32_FLOAT_S8_UINT,
/* Compressed ASTC Normalized Float Color Formats*/
ASTC_4x4_UNORM,
ASTC_5x4_UNORM,
ASTC_5x5_UNORM,
ASTC_6x5_UNORM,
ASTC_6x6_UNORM,
ASTC_8x5_UNORM,
ASTC_8x6_UNORM,
ASTC_8x8_UNORM,
ASTC_10x5_UNORM,
ASTC_10x6_UNORM,
ASTC_10x8_UNORM,
ASTC_10x10_UNORM,
ASTC_12x10_UNORM,
ASTC_12x12_UNORM,
/* Compressed SRGB ASTC Normalized Float Color Formats*/
ASTC_4x4_UNORM_SRGB,
ASTC_5x4_UNORM_SRGB,
ASTC_5x5_UNORM_SRGB,
ASTC_6x5_UNORM_SRGB,
ASTC_6x6_UNORM_SRGB,
ASTC_8x5_UNORM_SRGB,
ASTC_8x6_UNORM_SRGB,
ASTC_8x8_UNORM_SRGB,
ASTC_10x5_UNORM_SRGB,
ASTC_10x6_UNORM_SRGB,
ASTC_10x8_UNORM_SRGB,
ASTC_10x10_UNORM_SRGB,
ASTC_12x10_UNORM_SRGB,
ASTC_12x12_UNORM_SRGB,
/* Compressed ASTC Signed Float Color Formats*/
ASTC_4x4_FLOAT,
ASTC_5x4_FLOAT,
ASTC_5x5_FLOAT,
ASTC_6x5_FLOAT,
ASTC_6x6_FLOAT,
ASTC_8x5_FLOAT,
ASTC_8x6_FLOAT,
ASTC_8x8_FLOAT,
ASTC_10x5_FLOAT,
ASTC_10x6_FLOAT,
ASTC_10x8_FLOAT,
ASTC_10x10_FLOAT,
ASTC_12x10_FLOAT,
ASTC_12x12_FLOAT,
Count
};
enum struct TextureUsageFlag : uint8
{
None = 0,
Sampler = 1 << 0, // Textures supports sampling
ColorTarget = 1 << 1, // Texture is color render target
DepthStencilTarget = 1 << 2, // Texture is depth stencil target
GraphicsStorageRead = 1 << 3, // Support Storage read at graphics stage
ComputeStorageRead = 1 << 4, // Support Storage read at compute stage
ComputeStorageWrite = 1 << 5, // Support Storage Write at compute stage
ComputeStorageSimultaneousReadWrite =
1 << 6, // Supports reads and writes in the same compute shader. Not equivalent to ComputeStorageRead | ComputeStorageWrite
};
enum struct TextureType : uint8
{
Texture_2D,
Texture_2DArray,
Texture_3D,
Texture_3DArray,
Texture_Cube,
Texture_CubeArray,
};
enum struct TextureSampleCount : uint8
{
One,
Two,
Four,
Eight,
};
// Create Information structs
struct TextureCreateInfo
{
TextureType Type;
TextureFormat Format;
TextureUsageFlag Flags;
TextureSampleCount SampleCount;
uint32 Width;
uint32 Height;
union
{
Invalid,
uint32 LayerCount;
uint32 DepthPlane;
}; // LayerCount is used in 2d array textures and Depth for 3d textures
uint32 MipLevelCount;
};
/* Unsigned Normalized Float Color Formats */
A8_UNORM,
R8_UNORM,
R8G8_UNORM,
R8G8B8A8_UNORM,
R16_UNORM,
R16G16_UNORM,
R16G16B16A16_UNORM,
R10G10B10A2_UNORM,
B5G6R5_UNORM,
B5G5R5A1_UNORM,
B4G4R4A4_UNORM,
B8G8R8A8_UNORM,
/* Compressed Unsigned Normalized Float Color Formats */
BC1_RGBA_UNORM,
BC2_RGBA_UNORM,
BC3_RGBA_UNORM,
BC4_R_UNORM,
BC5_RG_UNORM,
BC7_RGBA_UNORM,
/* Compressed Signed Float Color Formats */
BC6H_RGB_FLOAT,
/* Compressed Unsigned Float Color Formats */
BC6H_RGB_UFLOAT,
/* Signed Normalized Float Color Formats */
R8_SNORM,
R8G8_SNORM,
R8G8B8A8_SNORM,
R16_SNORM,
R16G16_SNORM,
R16G16B16A16_SNORM,
/* Signed Float Color Formats */
R16_FLOAT,
R16G16_FLOAT,
R16G16B16A16_FLOAT,
R32_FLOAT,
R32G32_FLOAT,
R32G32B32A32_FLOAT,
/* Unsigned Float Color Formats */
R11G11B10_UFLOAT,
/* Unsigned Integer Color Formats */
R8_UINT,
R8G8_UINT,
R8G8B8A8_UINT,
R16_UINT,
R16G16_UINT,
R16G16B16A16_UINT,
R32_UINT,
R32G32_UINT,
R32G32B32A32_UINT,
/* Signed Integer Color Formats */
R8_INT,
R8G8_INT,
R8G8B8A8_INT,
R16_INT,
R16G16_INT,
R16G16B16A16_INT,
R32_INT,
R32G32_INT,
R32G32B32A32_INT,
/* SRGB Unsigned Normalized Color Formats */
R8G8B8A8_UNORM_SRGB,
B8G8R8A8_UNORM_SRGB,
/* Compressed SRGB Unsigned Normalized Color Formats */
BC1_RGBA_UNORM_SRGB,
BC2_RGBA_UNORM_SRGB,
BC3_RGBA_UNORM_SRGB,
BC7_RGBA_UNORM_SRGB,
/* Depth Formats */
D16_UNORM,
D24_UNORM,
D32_FLOAT,
D24_UNORM_S8_UINT,
D32_FLOAT_S8_UINT,
/* Compressed ASTC Normalized Float Color Formats*/
ASTC_4x4_UNORM,
ASTC_5x4_UNORM,
ASTC_5x5_UNORM,
ASTC_6x5_UNORM,
ASTC_6x6_UNORM,
ASTC_8x5_UNORM,
ASTC_8x6_UNORM,
ASTC_8x8_UNORM,
ASTC_10x5_UNORM,
ASTC_10x6_UNORM,
ASTC_10x8_UNORM,
ASTC_10x10_UNORM,
ASTC_12x10_UNORM,
ASTC_12x12_UNORM,
/* Compressed SRGB ASTC Normalized Float Color Formats*/
ASTC_4x4_UNORM_SRGB,
ASTC_5x4_UNORM_SRGB,
ASTC_5x5_UNORM_SRGB,
ASTC_6x5_UNORM_SRGB,
ASTC_6x6_UNORM_SRGB,
ASTC_8x5_UNORM_SRGB,
ASTC_8x6_UNORM_SRGB,
ASTC_8x8_UNORM_SRGB,
ASTC_10x5_UNORM_SRGB,
ASTC_10x6_UNORM_SRGB,
ASTC_10x8_UNORM_SRGB,
ASTC_10x10_UNORM_SRGB,
ASTC_12x10_UNORM_SRGB,
ASTC_12x12_UNORM_SRGB,
/* Compressed ASTC Signed Float Color Formats*/
ASTC_4x4_FLOAT,
ASTC_5x4_FLOAT,
ASTC_5x5_FLOAT,
ASTC_6x5_FLOAT,
ASTC_6x6_FLOAT,
ASTC_8x5_FLOAT,
ASTC_8x6_FLOAT,
ASTC_8x8_FLOAT,
ASTC_10x5_FLOAT,
ASTC_10x6_FLOAT,
ASTC_10x8_FLOAT,
ASTC_10x10_FLOAT,
ASTC_12x10_FLOAT,
ASTC_12x12_FLOAT,
Count
};
enum struct TextureUsageFlag : uint8
{
None = 0,
Sampler = 1 << 0, // Textures supports sampling
ColorTarget = 1 << 1, // Texture is color render target
DepthStencilTarget = 1 << 2, // Texture is depth stencil target
GraphicsStorageRead = 1 << 3, // Support Storage read at graphics stage
ComputeStorageRead = 1 << 4, // Support Storage read at compute stage
ComputeStorageWrite = 1 << 5, // Support Storage Write at compute stage
ComputeStorageSimultaneousReadWrite =
1 << 6, // Supports reads and writes in the same compute shader. Not equivalent to ComputeStorageRead | ComputeStorageWrite
};
enum struct TextureType : uint8
{
Texture_2D,
Texture_2DArray,
Texture_3D,
Texture_3DArray,
Texture_Cube,
Texture_CubeArray,
};
enum struct TextureSampleCount : uint8
{
One,
Two,
Four,
Eight,
};
// Create Information structs
struct TextureCreateInfo
{
TextureType Type;
TextureFormat Format;
TextureUsageFlag Flags;
TextureSampleCount SampleCount;
uint32 Width;
uint32 Height;
union
{
uint32 LayerCount;
uint32 DepthPlane;
}; // LayerCount is used in 2d array textures and Depth for 3d textures
uint32 MipLevelCount;
};
// Opaque Type
struct Texture;
} // namespace Juliet
// Opaque Type
struct Texture;
+7 -10
View File
@@ -1,13 +1,10 @@
#pragma once
#pragma once
namespace Juliet
struct Vertex
{
struct Vertex
{
float Position[3];
float Normal[3];
float Color[4];
};
float Position[3];
float Normal[3];
float Color[4];
};
using Index = uint16;
} // namespace Juliet
using Index = uint16;
+1
View File
@@ -24,6 +24,7 @@
#define JULIET_DEBUG_ONLY(...) __VA_ARGS__
#define JULIET_DEBUG_PARAM_FIRST(...) __VA_ARGS__
#define JULIET_DEBUG_PARAM(...) , __VA_ARGS__
#define JULIET_EDITOR 1
#else
#define JULIET_DEBUG 0
#define JULIET_DEBUG_ONLY(...)
@@ -1,20 +1,17 @@
#include <Core/Application/ApplicationManager.h>
#include <Core/Application/ApplicationManager.h>
#include <Core/JulietInit.h>
#include <Engine/Engine.h>
namespace Juliet
void StartApplication(IApplication& app, JulietInit_Flags flags)
{
void StartApplication(IApplication& app, JulietInit_Flags flags)
{
InitializeEngine(flags);
InitializeEngine(flags);
LoadApplication(app);
LoadApplication(app);
RunEngine();
RunEngine();
UnloadApplication();
UnloadApplication();
ShutdownEngine();
}
} // namespace Juliet
ShutdownEngine();
}
+23 -26
View File
@@ -1,38 +1,35 @@
#include <Core/Logging/LogManager.h>
#include <Core/Logging/LogManager.h>
#include <Core/Logging/LogTypes.h>
#include <Core/Memory/Allocator.h>
#include <comdef.h> // For _com_error to decode HRESULTs
#include <intrin.h> // For __debugbreak
namespace Juliet
void JulietAssert(const char* expression, const char* message, std::source_location location, long handleResult)
{
void JulietAssert(const char* expression, const char* message, std::source_location location, long handleResult)
Log(LogLevel::Error, LogCategory::Core, "--- ASSERTION FAILED ---");
Log(LogLevel::Error, LogCategory::Core, "Expression: %s", expression);
Log(LogLevel::Error, LogCategory::Core, "Message: %s", message);
Log(LogLevel::Error, LogCategory::Core, "Location: %s(%u): %s", location.file_name(), location.line(),
location.function_name());
if (handleResult < 0)
{
Log(LogLevel::Error, LogCategory::Core, "--- ASSERTION FAILED ---");
Log(LogLevel::Error, LogCategory::Core, "Expression: %s", expression);
Log(LogLevel::Error, LogCategory::Core, "Message: %s", message);
Log(LogLevel::Error, LogCategory::Core, "Location: %s(%u): %s", location.file_name(), location.line(),
location.function_name());
if (handleResult < 0)
{
_com_error err(handleResult);
// Using %ls because ErrorMessage() returns a wide string (wchar_t*)
Log(LogLevel::Error, LogCategory::Graphics, "HRESULT: 0x%08X (%ls)", handleResult, err.ErrorMessage());
}
Log(LogLevel::Error, LogCategory::Core, "-------------------------");
JULIET_PLATFORM_BREAK();
_com_error err(handleResult);
// Using %ls because ErrorMessage() returns a wide string (wchar_t*)
Log(LogLevel::Error, LogCategory::Graphics, "HRESULT: 0x%08X (%ls)", handleResult, err.ErrorMessage());
}
void Free(ByteBuffer& buffer)
Log(LogLevel::Error, LogCategory::Core, "-------------------------");
JULIET_PLATFORM_BREAK();
}
void Free(ByteBuffer& buffer)
{
if (buffer.Data)
{
if (buffer.Data)
{
Free(buffer.Data);
}
buffer = {};
Free(buffer.Data);
}
} // namespace Juliet
buffer = {};
}
File diff suppressed because it is too large Load Diff
+382
View File
@@ -0,0 +1,382 @@
#include <Core/Common/serialization.h>
#include <Core/Common/CoreUtils.h>
#include <Core/Common/CRC32.h>
#include <Core/HAL/IO/IOStream.h>
#include <Core/Logging/LogManager.h>
#include <Core/Logging/LogTypes.h>
#include <Core/Math/Vector.h>
#include <Core/Memory/MemoryArena.h>
#include <Core/Memory/Utils.h>
void serialize(Archive& ar, void* data, size_t size)
{
if (ar.loading)
{
uint8* ptr = static_cast<uint8*>(ar.base_ptr) + ar.offset;
MemCopy(data, ptr, size);
ar.offset += size;
}
else
{
auto* ptr = ArenaPushSize(ar.arena, size, 8, false JULIET_DEBUG_PARAM("serialized save field"));
MemCopy(ptr, data, size);
ar.offset += size;
}
}
ParsedArchive tokenize_archive(NonNullPtr<Arena> arena, ByteBuffer file_buffer)
{
Assert(file_buffer.Data);
ParsedArchive archive = {};
uint8* cursor = (uint8*)file_buffer.Data;
uint8* end = cursor + file_buffer.Size;
// Pass 1: count properties
uint32 property_count = 0;
uint8* scan = cursor;
while (scan < end)
{
if (*scan == ';')
{
if (scan == cursor || *(scan - 1) == '\n')
{
property_count++;
}
}
scan++;
}
if (property_count > 0)
{
// Pass 2: extract properties
NonNullPtr nodes =
ArenaPushArray<ArchivePropertyNode>(arena, property_count JULIET_DEBUG_PARAM("tokenizer nodes"));
uint32 node_index = 0;
scan = cursor;
while (scan < end && node_index < property_count)
{
// Skip comments
if (*scan == '#' || (*scan == '/' && scan + 1 < end && *(scan + 1) == '/'))
{
while (scan < end && *scan != '\n')
{
++scan;
}
++scan; // skipping trailing \n
continue;
}
if (*scan == ';')
{
++scan; // skipping the ;
// extract property name
uint8* property_name_cursor = scan;
while (scan < end && *scan != '\r' && *scan != '\n')
{
++scan;
}
String raw_property_name = { .Str = (char*)property_name_cursor, .Size = (size_t)(scan - property_name_cursor) };
String property_name = trim_whitespace(raw_property_name);
Assert(property_name.Str && property_name.Size > 0);
// skip \r\n
while (scan < end && (*scan == '\r' || *scan == '\n'))
{
++scan;
}
// Value block is everything until comment or new line
uint8* value_start = scan;
uint8* value_end = scan;
while (scan < end)
{
if (*scan == '\r' || *scan == '\n')
{
break;
}
if (*scan == '#' || (*scan == '/' && scan + 1 < end && *(scan + 1) == '/'))
{
break;
}
++scan;
value_end = scan;
}
String raw_value = { .Str = (char*)value_start, .Size = static_cast<size_t>(value_end - value_start) };
String value = trim_whitespace(raw_value);
nodes[node_index].key_crc = crc32(property_name);
nodes[node_index].key = StringCopy(arena, property_name);
nodes[node_index].value = value;
nodes[node_index].consumed = false;
node_index++;
}
else
{
++scan;
}
}
archive.nodes = nodes.Get();
archive.property_count = node_index;
}
return archive;
}
ArchivePropertyNode* find_property(NonNullPtr<ParsedArchive> archive, uint32 property_crc)
{
Assert(archive->nodes && archive->property_count > 0);
ArchivePropertyNode* result = nullptr;
ArchivePropertyNode* nodes = archive->nodes;
for (index_t idx = 0; idx < archive->property_count; ++idx)
{
if (nodes[idx].key_crc == property_crc)
{
nodes[idx].consumed = true;
result = &nodes[idx];
break;
}
}
return result;
}
void write_property_header(Archive& ar, String property_name)
{
Assert(!ar.loading);
Assert(ar.stream);
Assert(IsValid(property_name));
IOPrintf(ar.stream, "; %s\n", CStr(property_name));
}
bool read_prop(Archive& ar, String value_raw, String& value)
{
String parsed = value_raw;
// Ignoring eventual "
if (parsed.Size >= 2 && parsed.Str[0] == '"' && parsed.Str[parsed.Size - 1] == '"')
{
parsed.Str++;
parsed.Size -= 2;
}
Assert(ar.arena);
value = StringCopy(ar.arena, parsed);
return true;
}
void write(NonNullPtr<IOStream> stream, String value)
{
if (ContainsChar(value, ' ')) // Add " " around string with spaces
{
IOPrintf(stream, "\"%.*s\"\n", (int32)value.Size, value.Str);
}
else
{
IOPrintf(stream, "%.*s\n", (int32)value.Size, value.Str);
}
}
bool read(const char* buffer, float& value, const char** next /* = nullptr */)
{
bool result = false;
char* end = nullptr;
float parsed = strtof(buffer, &end);
if (end != buffer)
{
value = parsed;
if (next)
{
*next = end;
}
result = true;
}
return result;
}
void write(NonNullPtr<IOStream> stream, float value)
{
IOPrintf(stream, "%.9g\n", value);
}
bool read(const char* buffer, int8& value)
{
bool result = false;
char* end = nullptr;
int32 parsed = strtol(buffer, &end, 10);
if (end != buffer && parsed >= int8Min && parsed <= int8Max)
{
value = (int8)parsed;
result = true;
}
return result;
}
void write(NonNullPtr<IOStream> stream, int8 value)
{
IOPrintf(stream, "%d\n", (int32)value);
}
bool read(const char* buffer, int16& value)
{
bool result = false;
char* end = nullptr;
int32 parsed = strtol(buffer, &end, 10);
if (end != buffer && parsed >= int16Min && parsed <= int16Max)
{
value = (int16)parsed;
result = true;
}
return result;
}
void write(NonNullPtr<IOStream> stream, int16 value)
{
IOPrintf(stream, "%d\n", (int32)value);
}
bool read(const char* buffer, int32& value)
{
char* end = nullptr;
value = strtol(buffer, &end, 10);
return end != buffer;
}
void write(NonNullPtr<IOStream> stream, int32 value)
{
IOPrintf(stream, "%d\n", value);
}
bool read(const char* buffer, int64& value)
{
char* end = nullptr;
value = strtoll(buffer, &end, 0);
return end != buffer;
}
void write(NonNullPtr<IOStream> stream, int64 value)
{
IOPrintf(stream, "%lld\n", value);
}
bool read(const char* buffer, uint8& value)
{
bool result = false;
char* end = nullptr;
uint32 parsed = strtoul(buffer, &end, 10);
if (end != buffer && parsed >= uint8Min && parsed <= uint8Max)
{
value = (uint8)parsed;
result = true;
}
return result;
}
void write(NonNullPtr<IOStream> stream, uint8 value)
{
IOPrintf(stream, "%u\n", (uint32)value);
}
bool read(const char* buffer, uint16& value)
{
bool result = false;
char* end = nullptr;
uint32 parsed = strtoul(buffer, &end, 10);
if (end != buffer && parsed >= uint16Min && parsed <= uint16Max)
{
value = (uint16)parsed;
result = true;
}
return result;
}
void write(NonNullPtr<IOStream> stream, uint16 value)
{
IOPrintf(stream, "%u\n", (uint32)value);
}
bool read(const char* buffer, uint32& value)
{
char* end = nullptr;
value = strtoul(buffer, &end, 10);
return end != buffer;
}
void write(NonNullPtr<IOStream> stream, uint32 value)
{
IOPrintf(stream, "%u\n", value);
}
bool read(const char* buffer, uint64& value)
{
char* end = nullptr;
value = strtoull(buffer, &end, 10);
return end != buffer;
}
void write(NonNullPtr<IOStream> stream, uint64 value)
{
IOPrintf(stream, "%llu\n", value);
}
bool read(const char* buffer, bool& value)
{
bool result = false;
value = buffer[0] == '1';
if (value || buffer[0] == '0')
{
result = true;
}
return result;
}
void write(NonNullPtr<IOStream> stream, bool value)
{
IOPrintf(stream, "%u\n", value);
}
bool read(const char* buffer, Vector4& value)
{
bool result = read(buffer, value.x, &buffer);
result &= read(buffer, value.y, &buffer);
result &= read(buffer, value.z, &buffer);
result &= read(buffer, value.w, &buffer);
return result;
}
void write(NonNullPtr<IOStream> stream, Vector4 value)
{
IOPrintf(stream, "%.9g %.9g %.9g %.9g\n", value.x, value.y, value.z, value.w);
}
#if JULIET_DEBUG
void audit_unconsumed_properties(NonNullPtr<ParsedArchive> archive, String context_name)
{
Assert(archive->nodes && archive->property_count > 0);
auto* nodes = archive->nodes;
for (index_t idx = 0; idx < archive->property_count; ++idx)
{
if (!nodes[idx].consumed)
{
LogWarning(LogCategory::Core, "[%s] Unconsumed or obsolete property detected: [%s] - CRC 0x%08X (Value: '%.*s')",
CStr(context_name), CStr(nodes[idx].key), nodes[idx].key_crc,
static_cast<int>(nodes[idx].value.Size), nodes[idx].value.Str);
}
}
}
#endif

Some files were not shown because too many files have changed in this diff Show More