Compare commits
66
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5fa75c116e | ||
|
|
2881b2c9d5 | ||
|
|
d79cf42a3d | ||
|
|
17af5348e9 | ||
|
|
dec8cbd059 | ||
|
|
f580776964 | ||
|
|
d2d9981efe | ||
|
|
f6b48a1c11 | ||
|
|
bf13f269fe | ||
|
|
cf86dd10a7 | ||
|
|
5995dd9a47 | ||
|
|
d5c6c79a76 | ||
|
|
7e8e626e24 | ||
|
|
f96b2e99cf | ||
|
|
4ec42f4bee | ||
|
|
886e722c71 | ||
|
|
575379e264 | ||
|
|
17e26fdd00 | ||
|
|
5d6ca639fd | ||
|
|
16664c68b5 | ||
|
|
ee1878cce7 | ||
|
|
7542d9200d | ||
|
|
f9356f9ba7 | ||
|
|
b70e2d21d2 | ||
|
|
16ba641d8a | ||
|
|
7075556bf2 | ||
|
|
f32a3f73a9 | ||
|
|
1367146503 | ||
|
|
2fd675cf11 | ||
|
|
f77b7fb901 | ||
|
|
10ac51f167 | ||
|
|
9e8bbad5d4 | ||
|
|
0f262fb06e | ||
|
|
c70cce463b | ||
|
|
2bd0cf39f1 | ||
|
|
58b79fb499 | ||
|
|
615d36b09c | ||
|
|
3123d2f2e6 | ||
|
|
b1de6ccc49 | ||
|
|
a462575af4 | ||
|
|
1a045c3578 | ||
|
|
62e6284976 | ||
|
|
d7825270ee | ||
|
|
cb615091ca | ||
|
|
4180622d6a | ||
|
|
678795b793 | ||
|
|
4d803fdf77 | ||
|
|
a50e3c75cb | ||
|
|
262d91dd49 | ||
|
|
d1b7c5dbbe | ||
|
|
3e7216f57d | ||
|
|
5566f76a94 | ||
|
|
ee20c903b2 | ||
|
|
e14931b27b | ||
|
|
5bc4375265 | ||
|
|
fb020d16e5 | ||
|
|
73aa4a846b | ||
|
|
30ea435b87 | ||
|
|
697579e72f | ||
|
|
7c18804df6 | ||
|
|
430cf35345 | ||
|
|
1a56c9e99c | ||
|
|
ae790f5a9e | ||
|
|
d52c77a017 | ||
|
|
681267f445 | ||
|
|
8582435f21 |
No files matched your search
@@ -5,12 +5,28 @@ trigger: always_on
|
|||||||
Code compiles with all warning active and warning as errors.
|
Code compiles with all warning active and warning as errors.
|
||||||
use static_cast or reinterpret_cast but not parenthesis for casting.
|
use static_cast or reinterpret_cast but not parenthesis for casting.
|
||||||
No exceptions
|
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 &
|
auto is allowed but when its a pointer add the * and when reference adds the &
|
||||||
Member variable are CamelCase
|
|
||||||
Types are CamelCase
|
|
||||||
Functions are CamelCase.
|
|
||||||
Add Assert to make sure all assumptions are good. Parameters of functions for example should be verified with Assert.
|
Add Assert to make sure all assumptions are good. Parameters of functions for example should be verified with Assert.
|
||||||
Code should be self commented using proper variable names, types and functions. No need to add comments most of the time, unless the algorithm is very complex and hard to read.
|
Code should be self commented using proper variable names, types and functions. No need to add comments most of the time, unless the algorithm is very complex and hard to read.
|
||||||
When creating a new system framework, make a unit test. To make the unit test we should not modify the framework code for special unit test case.
|
When creating a new system framework, make a unit test. To make the unit test we should not modify the framework code for special unit test case.
|
||||||
Always put braces for if,else,for,while etc.
|
Always put braces for if,else,for,while etc.
|
||||||
|
Coding Style:
|
||||||
|
Note: The code base do not fully use those, if in doubt, use this style and not legacy style.
|
||||||
|
Types (Structs/Enums/Unions) : PascalCase. Exemple : Arena, String8, DbgTarget, EvalValue
|
||||||
|
Primitive Types : lower_snake_case. Exemple: uint8, uint16, uint32, uint64, size_t, index_t, int32...
|
||||||
|
Function Names: lower_snake_case. Exemple: str16_from_8.
|
||||||
|
Namespace: do not use c++ namespace, add PascalCase prefix to function names. Exemple: W32_do_something
|
||||||
|
Local Variables and parameters: lower_snake_case. Exemple: arena, str, data.
|
||||||
|
Struct members: lower_snake_case. Exemple: data, node, next.
|
||||||
|
global/static variables: lower_snake_case prefixed. Exemple: g_my_global.
|
||||||
|
const variables: lower_snake_case prefixed. Exemple: k_my_const_var.
|
||||||
|
Defines and Macros: ALL_CAPS_SNAKE. Exemple: MY_MACRO
|
||||||
|
|
||||||
|
Modular Architecture & Include Rules:
|
||||||
|
- Juliet uses a single-pass unity build pattern.
|
||||||
|
- Layer aggregators `<layer>_inc.h` and `<layer>_inc.c` dictate header and source inclusion order.
|
||||||
|
- Top-level translation units (e.g. `appbin_main.c`) include the layer `_inc` files in dependency order (`core` -> `render` -> `ui`...).
|
||||||
|
- Sub-files within a module (e.g. `core.h`, `core_arena.c`, `render.c`) must NEVER directly `#include` other module headers, sibling headers, or configuration defines (`core_defines.h`).
|
||||||
|
- Never add `#include` directives to sub-files to satisfy standalone LSP/compiler diagnostics. Tooling (`new_project_gen.c` / `compile_commands.json`) must supply preceding includes dynamically based on the base file include order.
|
||||||
|
- Always explain root causes and confirm with the user before editing project architecture or files.
|
||||||
@@ -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.
|
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
|
## 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
|
## Focus Areas
|
||||||
1. **High Performance**: Always consider cache locality and CPU cycle cost.
|
1. **High Performance**: Always consider cache locality and CPU cycle cost.
|
||||||
|
|||||||
@@ -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.
|
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
|
## 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
|
## Workflows
|
||||||
- **Building**: Use the `/build` workflow to compile the project.
|
- **Building**: Use the `/build` workflow to compile the project.
|
||||||
|
|||||||
+5
-6
@@ -44,10 +44,14 @@ IncludeCategories:
|
|||||||
IncludeIsMainRegex: '([-_](test|unittest))?$'
|
IncludeIsMainRegex: '([-_](test|unittest))?$'
|
||||||
IndentAccessModifiers: false
|
IndentAccessModifiers: false
|
||||||
IndentCaseBlocks: true
|
IndentCaseBlocks: true
|
||||||
|
IndentPPDirectives: AfterHash
|
||||||
IndentWidth: 4
|
IndentWidth: 4
|
||||||
InsertNewlineAtEOF: true
|
InsertNewlineAtEOF: true
|
||||||
MacroBlockBegin: ''
|
MacroBlockBegin: ''
|
||||||
MacroBlockEnd: ''
|
MacroBlockEnd: ''
|
||||||
|
Macros:
|
||||||
|
- 'EachIndex(it, count)=(uint64 it = 0; it < (count); ++it)'
|
||||||
|
- 'EachNode(it, Type, first)=(Type* it = first; it != 0; it = it->next)'
|
||||||
NamespaceIndentation: All
|
NamespaceIndentation: All
|
||||||
ObjCSpaceAfterProperty: true
|
ObjCSpaceAfterProperty: true
|
||||||
PenaltyBreakBeforeFirstCallParameter: 100
|
PenaltyBreakBeforeFirstCallParameter: 100
|
||||||
@@ -57,15 +61,10 @@ PenaltyBreakString: 100
|
|||||||
PenaltyExcessCharacter: 1
|
PenaltyExcessCharacter: 1
|
||||||
PenaltyReturnTypeOnItsOwnLine: 1000
|
PenaltyReturnTypeOnItsOwnLine: 1000
|
||||||
PointerAlignment: Left
|
PointerAlignment: Left
|
||||||
|
PPIndentWidth: 1
|
||||||
SortIncludes: CaseInsensitive
|
SortIncludes: CaseInsensitive
|
||||||
SpacesInAngles: Never
|
SpacesInAngles: Never
|
||||||
SpacesInContainerLiterals: false
|
SpacesInContainerLiterals: false
|
||||||
Standard: Cpp11
|
Standard: Cpp11
|
||||||
StatementMacros:
|
|
||||||
- UPROPERTY
|
|
||||||
- UCLASS
|
|
||||||
- USTRUCT
|
|
||||||
- GENERATED_BODY
|
|
||||||
- UENUM
|
|
||||||
TabWidth: 4
|
TabWidth: 4
|
||||||
...
|
...
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
CompileFlags:
|
||||||
|
Add:
|
||||||
|
- --driver-mode=cl
|
||||||
|
- -Wno-unknown-pragmas
|
||||||
|
- -Wno-unused-command-line-argument
|
||||||
|
- -Wno-unused-function
|
||||||
|
- -Wno-undefined-internal
|
||||||
|
- -Wno-initializer-overrides
|
||||||
|
- /IJuliet
|
||||||
|
Remove:
|
||||||
|
- /Yu*
|
||||||
|
- /Yc*
|
||||||
|
- /Fp*
|
||||||
|
- /MP
|
||||||
|
- /Zi
|
||||||
|
- /Gw
|
||||||
|
- /Gy
|
||||||
|
- /Bt+
|
||||||
|
- /EHa-
|
||||||
|
|
||||||
|
Diagnostics:
|
||||||
|
UnusedIncludes: None
|
||||||
|
Suppress:
|
||||||
|
- unused-function
|
||||||
|
- undefined-internal
|
||||||
|
- initializer-overrides
|
||||||
|
|
||||||
|
InlayHints:
|
||||||
|
Enabled: true
|
||||||
|
ParameterNames: true
|
||||||
|
DeducedTypes: true
|
||||||
|
Designators: true
|
||||||
|
BlockEnd: false
|
||||||
|
TypeNameLimit: 24
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
# Binaries
|
# Binaries
|
||||||
[Bb]in/
|
[Bb]in/
|
||||||
|
[Bb]uild/
|
||||||
[Ll]ib/
|
[Ll]ib/
|
||||||
.idea/
|
.idea/
|
||||||
.vs/
|
.vs/
|
||||||
@@ -56,3 +57,7 @@ launch_*.txt
|
|||||||
*.log
|
*.log
|
||||||
misc/agent_error.log
|
misc/agent_error.log
|
||||||
misc/agent_output_ship.log
|
misc/agent_output_ship.log
|
||||||
|
|
||||||
|
# Clangd
|
||||||
|
compile_commands.json
|
||||||
|
.cache/
|
||||||
+11
@@ -1,3 +1,14 @@
|
|||||||
[submodule "External/Imgui"]
|
[submodule "External/Imgui"]
|
||||||
path = External/Imgui
|
path = External/Imgui
|
||||||
url = https://github.com/ocornut/imgui.git
|
url = https://github.com/ocornut/imgui.git
|
||||||
|
# Pinned to v1.93.0 (c1aaac65e)
|
||||||
|
|
||||||
|
[submodule "Juliet/external/xxHash"]
|
||||||
|
path = Juliet/external/xxHash
|
||||||
|
url = https://github.com/Cyan4973/xxHash.git
|
||||||
|
# Pinned to v0.8.4 (c87183a77)
|
||||||
|
|
||||||
|
[submodule "Juliet/external/stb_sprintf"]
|
||||||
|
path = Juliet/external/stb_sprintf
|
||||||
|
url = https://github.com/nothings/stb_sprintf.git
|
||||||
|
# Pinned to v1.10 / commit 2ffb3379e
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,2 @@
|
|||||||
|
; Paul
|
||||||
|
1
|
||||||
@@ -22,18 +22,20 @@ cbuffer RootConstants : register(b0, space0)
|
|||||||
uint BufferIndex;
|
uint BufferIndex;
|
||||||
uint TextureIndex;
|
uint TextureIndex;
|
||||||
uint VertexOffset; // Base vertex for indexed drawing with bindless buffers
|
uint VertexOffset; // Base vertex for indexed drawing with bindless buffers
|
||||||
uint _Padding; // Padding for alignment
|
|
||||||
float2 Scale; // 2D scale factor
|
|
||||||
float2 Translate; // 2D translation
|
|
||||||
float2 _Padding2; // Explicit padding to align LightDirection to 16 bytes
|
|
||||||
|
|
||||||
float3 GlobalLightDirection; // Normalized, world-space
|
|
||||||
float GlobalLightPad;
|
|
||||||
float3 GlobalLightColor;
|
|
||||||
float GlobalAmbientIntensity;
|
|
||||||
|
|
||||||
uint LightBufferIndex;
|
uint LightBufferIndex;
|
||||||
uint ActiveLightCount;
|
uint ActiveLightCount;
|
||||||
|
float GlobalAmbientIntensity;
|
||||||
|
|
||||||
|
float3 GlobalLightDirection; // Normalized, world-space
|
||||||
|
uint _Pad1;
|
||||||
|
|
||||||
|
float3 GlobalLightColor;
|
||||||
|
uint _Pad2;
|
||||||
|
|
||||||
|
float2 Scale; // 2D scale factor
|
||||||
|
float2 Translate; // 2D translation
|
||||||
|
|
||||||
|
float4 MeshAlbedo;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -11,11 +11,13 @@ float4 main(Input input) : SV_Target0
|
|||||||
{
|
{
|
||||||
float3 normal = normalize(input.WorldNormal);
|
float3 normal = normalize(input.WorldNormal);
|
||||||
|
|
||||||
// Initial ambient component
|
// Initial ambient component (Soft blue sky ambient)
|
||||||
float3 result = input.Color.rgb * GlobalLightColor * GlobalAmbientIntensity;
|
float3 ambientColor = float3(0.6f, 0.7f, 0.9f);
|
||||||
|
float3 result = input.Color.rgb * ambientColor * GlobalAmbientIntensity;
|
||||||
|
|
||||||
// Directional light contribution
|
// 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;
|
result += input.Color.rgb * GlobalLightColor * ndotl;
|
||||||
|
|
||||||
// Point lights
|
// 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);
|
return float4(result, input.Color.a);
|
||||||
}
|
}
|
||||||
@@ -27,7 +27,7 @@ Output main(uint vertexIndex : SV_VertexID)
|
|||||||
|
|
||||||
float4 worldPos = mul(Model, float4(pos, 1.0f));
|
float4 worldPos = mul(Model, float4(pos, 1.0f));
|
||||||
output.Position = mul(ViewProjection, worldPos);
|
output.Position = mul(ViewProjection, worldPos);
|
||||||
output.Color = col;
|
output.Color = col * MeshAlbedo;
|
||||||
output.WorldPosition = worldPos.xyz;
|
output.WorldPosition = worldPos.xyz;
|
||||||
|
|
||||||
float3 worldNormal = mul((float3x3)Model, normal);
|
float3 worldNormal = mul((float3x3)Model, normal);
|
||||||
|
|||||||
@@ -3,4 +3,7 @@
|
|||||||
<LLVMInstallDir>C:\Program Files\LLVM</LLVMInstallDir>
|
<LLVMInstallDir>C:\Program Files\LLVM</LLVMInstallDir>
|
||||||
<LLVMToolsVersion>19.1.0</LLVMToolsVersion>
|
<LLVMToolsVersion>19.1.0</LLVMToolsVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Natvis Include="$(MSBuildThisFileDirectory)Natvis\*.natvis" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
Vendored
+1
-1
Submodule External/Imgui updated: 9b7699f325...c1aaac65e8.
@@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Juliet.h>
|
||||||
|
|
||||||
|
#define kPlayCamera ((index_t)0)
|
||||||
|
#define kDebugCamera ((index_t)1)
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
#include <Controller/DebugCameraController.h>
|
||||||
|
|
||||||
|
#include <Controller/ControllerUtils.h>
|
||||||
|
#include <Core/Common/CoreUtils.h>
|
||||||
|
#include <Core/HAL/Keyboard/Keyboard.h>
|
||||||
|
#include <Core/HAL/Mouse/Mouse.h>
|
||||||
|
#include <Core/Memory/MemoryArena.h>
|
||||||
|
#include <Graphics/Camera.h>
|
||||||
|
#include <dcimgui.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
global bool8 gIsDebugCameraActive = false;
|
||||||
|
global index_t gPreviousCameraIndex = 0;
|
||||||
|
global float gPitch = 0.0f;
|
||||||
|
global float gYaw = 0.0f;
|
||||||
|
global bool8 gFirstUpdate = true;
|
||||||
|
global bool8 gIsFpsModeActive = false;
|
||||||
|
global bool8 gWasRightMouseButtonDown = false;
|
||||||
|
|
||||||
|
void ActivateDebugController(void)
|
||||||
|
{
|
||||||
|
Assert(gIsDebugCameraActive == false);
|
||||||
|
|
||||||
|
Camera* currentCam = GetCurrentCamera();
|
||||||
|
gPreviousCameraIndex = currentCam->Index;
|
||||||
|
|
||||||
|
SetCurrentCamera(kDebugCamera);
|
||||||
|
|
||||||
|
gIsDebugCameraActive = true;
|
||||||
|
gFirstUpdate = true;
|
||||||
|
gIsFpsModeActive = false;
|
||||||
|
gWasRightMouseButtonDown = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeactivateDebugController(void)
|
||||||
|
{
|
||||||
|
Assert(gIsDebugCameraActive);
|
||||||
|
|
||||||
|
gIsDebugCameraActive = false;
|
||||||
|
|
||||||
|
SetCurrentCamera(gPreviousCameraIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool8 IsDebugControllerActive(void)
|
||||||
|
{
|
||||||
|
return gIsDebugCameraActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateDebugController(float dt)
|
||||||
|
{
|
||||||
|
Camera* currentCam = GetCurrentCamera();
|
||||||
|
|
||||||
|
if (gFirstUpdate)
|
||||||
|
{
|
||||||
|
Vector3 origin = { 0.0f, 0.0f, 0.0f };
|
||||||
|
Vector3 dir = Vector3Sub(origin, currentCam->Position);
|
||||||
|
dir = Normalize(dir);
|
||||||
|
gPitch = asinf(dir.z);
|
||||||
|
gYaw = atan2f(dir.y, dir.x);
|
||||||
|
gFirstUpdate = false;
|
||||||
|
|
||||||
|
Vector3 forward = { cosf(gPitch) * cosf(gYaw), cosf(gPitch) * sinf(gYaw), sinf(gPitch) };
|
||||||
|
Vector3 worldUp = { 0.0f, 0.0f, 1.0f };
|
||||||
|
Vector3 right = Normalize(Cross(worldUp, forward));
|
||||||
|
currentCam->Target = Vector3Add(currentCam->Position, forward);
|
||||||
|
currentCam->Up = Cross(forward, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool8 isRightMouseButtonDown = IsMouseButtonDown(MouseButton_Right);
|
||||||
|
if (isRightMouseButtonDown && !gWasRightMouseButtonDown)
|
||||||
|
{
|
||||||
|
gIsFpsModeActive = !gIsFpsModeActive;
|
||||||
|
}
|
||||||
|
gWasRightMouseButtonDown = isRightMouseButtonDown;
|
||||||
|
|
||||||
|
if (gIsFpsModeActive)
|
||||||
|
{
|
||||||
|
MousePosition mouseDelta = GetMouseDelta();
|
||||||
|
|
||||||
|
float sensitivity = 0.005f;
|
||||||
|
gYaw += (float)mouseDelta.X * sensitivity;
|
||||||
|
gPitch -= (float)mouseDelta.Y * sensitivity;
|
||||||
|
|
||||||
|
gPitch = Min(gPitch, 1.5f);
|
||||||
|
gPitch = Max(gPitch, -1.5f);
|
||||||
|
|
||||||
|
if (IsKeyDown(ScanCode_Q))
|
||||||
|
{
|
||||||
|
gYaw -= 2.0f * dt;
|
||||||
|
}
|
||||||
|
if (IsKeyDown(ScanCode_E))
|
||||||
|
{
|
||||||
|
gYaw += 2.0f * dt;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3 forward = { cosf(gPitch) * cosf(gYaw), cosf(gPitch) * sinf(gYaw), sinf(gPitch) };
|
||||||
|
Vector3 worldUp = { 0.0f, 0.0f, 1.0f };
|
||||||
|
Vector3 right = Normalize(Cross(worldUp, forward));
|
||||||
|
Vector3 defaultUp = Cross(forward, right);
|
||||||
|
|
||||||
|
const float kMovementPerFrame = 10.f; // 10m/s
|
||||||
|
|
||||||
|
float speedPerFrame = kMovementPerFrame;
|
||||||
|
if (IsKeyDown(ScanCode_LeftShift))
|
||||||
|
{
|
||||||
|
speedPerFrame *= 10.f; // 100m/s
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsKeyDown(ScanCode_W))
|
||||||
|
{
|
||||||
|
currentCam->Position = Vector3Add(currentCam->Position, Vector3Scale(forward, speedPerFrame * dt));
|
||||||
|
}
|
||||||
|
if (IsKeyDown(ScanCode_S))
|
||||||
|
{
|
||||||
|
currentCam->Position = Vector3Sub(currentCam->Position, Vector3Scale(forward, speedPerFrame * dt));
|
||||||
|
}
|
||||||
|
if (IsKeyDown(ScanCode_D))
|
||||||
|
{
|
||||||
|
currentCam->Position = Vector3Add(currentCam->Position, Vector3Scale(right, speedPerFrame * dt));
|
||||||
|
}
|
||||||
|
if (IsKeyDown(ScanCode_A))
|
||||||
|
{
|
||||||
|
currentCam->Position = Vector3Sub(currentCam->Position, Vector3Scale(right, speedPerFrame * dt));
|
||||||
|
}
|
||||||
|
if (IsKeyDown(ScanCode_Space))
|
||||||
|
{
|
||||||
|
currentCam->Position = Vector3Add(currentCam->Position, Vector3Scale(defaultUp, speedPerFrame * dt));
|
||||||
|
}
|
||||||
|
if (IsKeyDown(ScanCode_LeftControl))
|
||||||
|
{
|
||||||
|
currentCam->Position = Vector3Sub(currentCam->Position, Vector3Scale(defaultUp, speedPerFrame * dt));
|
||||||
|
}
|
||||||
|
|
||||||
|
currentCam->Target = Vector3Add(currentCam->Position, forward);
|
||||||
|
currentCam->Up = defaultUp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if JULIET_DEBUG
|
||||||
|
void RenderImGuiDebugController(float dt)
|
||||||
|
{
|
||||||
|
Camera* currentCam = GetCurrentCamera();
|
||||||
|
|
||||||
|
ImGui_Text("Delta time: %f", (double)dt);
|
||||||
|
|
||||||
|
ImGui_Text("Camera:");
|
||||||
|
ImGui_Indent();
|
||||||
|
ImGui_BulletText("Index: %zu", (size_t)currentCam->Index);
|
||||||
|
ImGui_BulletText("Position: (%.3f, %.3f, %.3f)", (double)currentCam->Position.x, (double)currentCam->Position.y,
|
||||||
|
(double)currentCam->Position.z);
|
||||||
|
ImGui_BulletText("Target: (%.3f, %.3f, %.3f)", (double)currentCam->Target.x, (double)currentCam->Target.y,
|
||||||
|
(double)currentCam->Target.z);
|
||||||
|
ImGui_BulletText("Up: (%.3f, %.3f, %.3f)", (double)currentCam->Up.x, (double)currentCam->Up.y,
|
||||||
|
(double)currentCam->Up.z);
|
||||||
|
ImGui_BulletText("FOV: %.3f rad", (double)currentCam->FOV);
|
||||||
|
ImGui_BulletText("AspectRatio: %.3f", (double)currentCam->AspectRatio);
|
||||||
|
ImGui_BulletText("NearPlane: %.3f", (double)currentCam->NearPlane);
|
||||||
|
ImGui_BulletText("FarPlane: %.3f", (double)currentCam->FarPlane);
|
||||||
|
ImGui_BulletText("Pitch: %.3f, Yaw: %.3f", (double)gPitch, (double)gYaw);
|
||||||
|
ImGui_BulletText("FPS Mode: %s", gIsFpsModeActive ? "Active" : "Inactive");
|
||||||
|
ImGui_Unindent();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Juliet.h>
|
||||||
|
|
||||||
|
// TODO : Should support Debug/Profile/Release properly.
|
||||||
|
// I think debug profile should have this controller but not release
|
||||||
|
void ActivateDebugController(void);
|
||||||
|
void DeactivateDebugController(void);
|
||||||
|
bool8 IsDebugControllerActive(void);
|
||||||
|
void UpdateDebugController(float dt);
|
||||||
|
|
||||||
|
#if JULIET_DEBUG
|
||||||
|
void RenderImGuiDebugController(float dt);
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
#include <Data/World.h>
|
||||||
|
|
||||||
|
#include <Core/Common/CoreUtils.h>
|
||||||
|
#include <Core/Common/serialization.h>
|
||||||
|
#include <Core/HAL/Filesystem/Filesystem.h>
|
||||||
|
#include <Core/HAL/IO/IOStream.h>
|
||||||
|
#include <Core/Logging/LogManager.h>
|
||||||
|
#include <Core/Logging/LogTypes.h>
|
||||||
|
#include <Core/Thread/ThreadContext.h>
|
||||||
|
#include <Entity/EntityManager.h>
|
||||||
|
#include <Graphics/MeshRenderer.h>
|
||||||
|
|
||||||
|
#ifdef JULIET_ENABLE_IMGUI
|
||||||
|
#include <dcimgui.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void InitWorld(World* world, Arena* arena)
|
||||||
|
{
|
||||||
|
Assert(world != NULL);
|
||||||
|
Assert(arena != NULL);
|
||||||
|
world->WorldArena = arena;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShutdownWorld(World* world)
|
||||||
|
{
|
||||||
|
Assert(world != NULL);
|
||||||
|
world->WorldArena = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddToWorld(World* world, Entity* entity)
|
||||||
|
{
|
||||||
|
(void)world;
|
||||||
|
(void)entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoveWorldEntity(World* world, size_t index)
|
||||||
|
{
|
||||||
|
(void)world;
|
||||||
|
(void)index;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SerializeWorld(Archive* ar, World* world, String filename)
|
||||||
|
{
|
||||||
|
(void)world;
|
||||||
|
Assert(ar != NULL);
|
||||||
|
Assert(IsValidString(filename));
|
||||||
|
|
||||||
|
EntityManager* entityManager = GetEntityManager();
|
||||||
|
|
||||||
|
if (ar->loading)
|
||||||
|
{
|
||||||
|
// Load
|
||||||
|
ByteBuffer fileBuffer = LoadFile(ar->arena, filename);
|
||||||
|
|
||||||
|
// TEST SERIALIZATION
|
||||||
|
ParsedArchive archive = tokenize_archive(ar->arena, fileBuffer);
|
||||||
|
#if JULIET_DEBUG
|
||||||
|
audit_unconsumed_properties(&archive, ConstString("World"));
|
||||||
|
#else
|
||||||
|
(void)archive;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Save
|
||||||
|
IOStream* stream = IOFromFile(ar->arena, filename, WrapString("wb"));
|
||||||
|
|
||||||
|
index_t beginPos = ArenaPos(ar->arena);
|
||||||
|
|
||||||
|
// Headers
|
||||||
|
WorldFileHeader* header = ArenaPushStruct(ar->arena, WorldFileHeader);
|
||||||
|
header->Magic = kWorldMagic;
|
||||||
|
|
||||||
|
// TODO : Move to a one file per entity model
|
||||||
|
for (uint8 i = 0; i < ENTITY(Count); ++i)
|
||||||
|
{
|
||||||
|
typed_entity_array* type = &entityManager->by_type[i];
|
||||||
|
serialize_elem(ar, type->count);
|
||||||
|
if (type->count > 0 && type->array != NULL)
|
||||||
|
{
|
||||||
|
EntityTemplate* element = type->array;
|
||||||
|
uint8* rawElement = (uint8*)element;
|
||||||
|
size_t stride = element->base->derived_kind->size_of;
|
||||||
|
for (index_t idx = 0; idx < type->count; ++idx)
|
||||||
|
{
|
||||||
|
Entity* entity = ((EntityTemplate*)(rawElement + (idx * stride)))->base;
|
||||||
|
serialize_entity(ar, entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write
|
||||||
|
index_t endPos = ArenaPos(ar->arena);
|
||||||
|
ByteBuffer writeBuffer = { .Data = (Byte*)header, .Size = endPos - beginPos };
|
||||||
|
|
||||||
|
size_t written = IOWrite(stream, writeBuffer);
|
||||||
|
|
||||||
|
Assert(writeBuffer.Size == written);
|
||||||
|
|
||||||
|
IOClose(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool8 LoadWorld(World* world, String filename)
|
||||||
|
{
|
||||||
|
(void)world;
|
||||||
|
Assert(IsValidString(filename));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef JULIET_EDITOR
|
||||||
|
void RenderWorldEditorUI(World* world)
|
||||||
|
{
|
||||||
|
Assert(world != NULL);
|
||||||
|
|
||||||
|
if (ImGui_Begin("World Editor", NULL, ImGuiWindowFlags_None))
|
||||||
|
{
|
||||||
|
TempArena temp = scratch_begin(NULL, 0);
|
||||||
|
static char worldFilePath[256] = "/world.bin";
|
||||||
|
ImGui_InputText("World File", worldFilePath, sizeof(worldFilePath));
|
||||||
|
|
||||||
|
Path path = GetAssetPath(temp.Arena, AssetType_JAsset, WrapString(worldFilePath));
|
||||||
|
|
||||||
|
if (ImGui_Button("Save World"))
|
||||||
|
{
|
||||||
|
Archive data = { .arena = temp.Arena, .loading = false };
|
||||||
|
SerializeWorld(&data, world, path.FullPath);
|
||||||
|
}
|
||||||
|
ImGui_SameLine(0.0f, -1.0f);
|
||||||
|
if (ImGui_Button("Load World"))
|
||||||
|
{
|
||||||
|
Archive data = { .arena = temp.Arena, .loading = true };
|
||||||
|
SerializeWorld(&data, world, path.FullPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
scratch_end(temp);
|
||||||
|
}
|
||||||
|
ImGui_End();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Core/Common/String.h>
|
||||||
|
#include <Core/Container/Vector.h>
|
||||||
|
#include <Core/Memory/MemoryArena.h>
|
||||||
|
#include <Entity/Entity.h>
|
||||||
|
|
||||||
|
typedef struct Archive Archive;
|
||||||
|
typedef struct EntityManager EntityManager;
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
typedef struct WorldFileHeader WorldFileHeader;
|
||||||
|
struct WorldFileHeader
|
||||||
|
{
|
||||||
|
uint32 Magic; // 0x444C574A ('JWLD' in little-endian)
|
||||||
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
#define kWorldMagic ((uint32)0x444C574A) // 'JWLD'
|
||||||
|
#define kWorldVersion ((uint32)1)
|
||||||
|
|
||||||
|
typedef struct World World;
|
||||||
|
struct World
|
||||||
|
{
|
||||||
|
Arena* WorldArena;
|
||||||
|
EntityManager* EntityManager;
|
||||||
|
};
|
||||||
|
|
||||||
|
void InitWorld(World* world, Arena* arena);
|
||||||
|
void ShutdownWorld(World* world);
|
||||||
|
|
||||||
|
void AddToWorld(World* world, Entity* entity);
|
||||||
|
void RemoveWorldEntity(World* world, size_t index);
|
||||||
|
|
||||||
|
void SerializeWorld(Archive* data, World* world, String filename);
|
||||||
|
|
||||||
|
#if JULIET_EDITOR
|
||||||
|
void RenderWorldEditorUI(World* world);
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
#include <Debug/DebugTopBar.h>
|
||||||
|
|
||||||
|
#if JULIET_DEBUG
|
||||||
|
#include <game.h>
|
||||||
|
#include <dcimgui.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
internal String GetGameModeName(GameMode gameMode)
|
||||||
|
{
|
||||||
|
String result = WrapString("Unknown ERROR");
|
||||||
|
if (gameMode == GameMode_Editor)
|
||||||
|
{
|
||||||
|
result = WrapString("Editor");
|
||||||
|
}
|
||||||
|
else if (gameMode == GameMode_Play)
|
||||||
|
{
|
||||||
|
result = WrapString("Play");
|
||||||
|
}
|
||||||
|
else if (gameMode == GameMode_Debug)
|
||||||
|
{
|
||||||
|
result = WrapString("Debug");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawTopBar(void)
|
||||||
|
{
|
||||||
|
GameState* gameState = GetGameState();
|
||||||
|
|
||||||
|
ImVec2_c viewportPos = ImGui_GetMainViewportPos();
|
||||||
|
ImVec2_c viewportSize = ImGui_GetMainViewportSize();
|
||||||
|
ImGui_SetNextWindowPos(viewportPos);
|
||||||
|
ImVec2_c barSize = { viewportSize.x, 10.0f };
|
||||||
|
ImGui_SetNextWindowSize(barSize);
|
||||||
|
|
||||||
|
ImGuiWindowFlags_c flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove |
|
||||||
|
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing |
|
||||||
|
ImGuiWindowFlags_NoNav;
|
||||||
|
|
||||||
|
ImVec4_c bgCol = { 0.0f, 0.0f, 0.0f, 0.6f };
|
||||||
|
ImVec2_c padding = { 10.0f, 5.0f };
|
||||||
|
ImGui_PushStyleColor(ImGuiCol_WindowBg, bgCol);
|
||||||
|
ImGui_PushStyleVarVec2(ImGuiStyleVar_WindowPadding, padding);
|
||||||
|
|
||||||
|
if (ImGui_Begin("TopBarOverlay", NULL, flags))
|
||||||
|
{
|
||||||
|
ImGui_Text("Game Mode: %s", CStr(GetGameModeName(gameState->Mode)));
|
||||||
|
|
||||||
|
float framerate = ImGuiIO_GetFramerate();
|
||||||
|
char fps_text[32];
|
||||||
|
snprintf(fps_text, sizeof(fps_text), "FPS: %.1f (%.2f ms)", (double)framerate, (double)(1000.0f / framerate));
|
||||||
|
float text_width = ImGui_CalcTextSize(fps_text, NULL).x;
|
||||||
|
ImGui_SameLine(viewportSize.x - text_width - 15.0f, -1.0f); // 15px right padding
|
||||||
|
// Render FPS text
|
||||||
|
ImGui_Text("%s", fps_text);
|
||||||
|
}
|
||||||
|
ImGui_End();
|
||||||
|
|
||||||
|
ImGui_PopStyleVar(1);
|
||||||
|
ImGui_PopStyleColor(1);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Juliet.h>
|
||||||
|
|
||||||
|
#if JULIET_DEBUG
|
||||||
|
void DrawTopBar(void);
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
#include <Entity/Entity.h>
|
||||||
|
|
||||||
|
#include <Core/Common/serialization.h>
|
||||||
|
#include <Core/Memory/Utils.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
void serialize_entity(Archive* ar, Entity* entity)
|
||||||
|
{
|
||||||
|
Assert(ar != NULL);
|
||||||
|
Assert(entity != NULL);
|
||||||
|
|
||||||
|
// Entity fields
|
||||||
|
serialize_class(ar, ClassKind(Entity), entity);
|
||||||
|
|
||||||
|
// Derived fields
|
||||||
|
if (entity->derived_kind != NULL && entity->derived != NULL)
|
||||||
|
{
|
||||||
|
serialize_class(ar, entity->derived_kind, entity->derived);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void init_Entity(void* payload)
|
||||||
|
{
|
||||||
|
Assert(payload != NULL);
|
||||||
|
Entity* entity = (Entity*)payload;
|
||||||
|
MemZeroStruct(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void serialize_Entity(Archive* ar, uint16 version, void* payload)
|
||||||
|
{
|
||||||
|
(void)version;
|
||||||
|
Assert(ar != NULL);
|
||||||
|
Assert(payload != NULL);
|
||||||
|
Entity* entity = (Entity*)payload;
|
||||||
|
SERIALIZE_U64(ar, id, &entity->ID);
|
||||||
|
SERIALIZE_VEC4(ar, position, &entity->position);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_CLASS_VERSIONED(Entity, 1, NULL)
|
||||||
|
|
||||||
|
String format_entity_id(Arena* arena, EntityID entity_id)
|
||||||
|
{
|
||||||
|
Assert(arena != NULL);
|
||||||
|
|
||||||
|
// Format:
|
||||||
|
// U = UserID
|
||||||
|
// LC = Local Entity Counter
|
||||||
|
// UU_LCLCLCLCLCLCLC
|
||||||
|
uint8 user_id = get_user_id(entity_id);
|
||||||
|
uint64 counter = get_local_counter(entity_id);
|
||||||
|
|
||||||
|
const size_t kBufferSize = 18;
|
||||||
|
char* buffer = ArenaPushArray(arena, char, kBufferSize);
|
||||||
|
Assert(buffer != NULL);
|
||||||
|
|
||||||
|
juliet_snprintf(buffer, kBufferSize, "%02X_%014llX", (unsigned int)user_id, (unsigned long long)counter);
|
||||||
|
String result = { .Str = buffer, .Size = kBufferSize - 1 };
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool8 parse_entity_id(String str, EntityID* out_id)
|
||||||
|
{
|
||||||
|
Assert(out_id != NULL);
|
||||||
|
|
||||||
|
bool8 result = false;
|
||||||
|
*out_id = kInvalid_entity_id;
|
||||||
|
|
||||||
|
if (!(str.Size != 17 || str.Str == NULL || str.Str[2] != '_'))
|
||||||
|
{
|
||||||
|
char user_hex[3] = { str.Str[0], str.Str[1], '\0' };
|
||||||
|
char* end = user_hex;
|
||||||
|
uint8 user_id = (uint8)strtoul(user_hex, &end, 16);
|
||||||
|
if (end != user_hex && user_id != kInvalid_user_id)
|
||||||
|
{
|
||||||
|
char* counter_hex = str.Str + 3;
|
||||||
|
end = counter_hex;
|
||||||
|
uint64 counter = strtoull(counter_hex, &end, 16);
|
||||||
|
if (end != counter_hex && counter < kEntityID_user_id_mask)
|
||||||
|
{
|
||||||
|
*out_id = make_entity_id(user_id, counter);
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Class* resolve_entity_class(uint8 kind, uint32 crc)
|
||||||
|
{
|
||||||
|
Assert(kind <= ENTITY(Count));
|
||||||
|
|
||||||
|
const Class* class_ptr = kEntity_type_class_ptr[kind];
|
||||||
|
Assert(class_ptr != NULL);
|
||||||
|
Assert(class_get_crc(class_ptr) == crc);
|
||||||
|
|
||||||
|
return class_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Class* find_class_by_name(String name)
|
||||||
|
{
|
||||||
|
Assert(IsValidString(name));
|
||||||
|
|
||||||
|
const Class* result = NULL;
|
||||||
|
const uint32 name_crc = crc32_bytes(name.Str, name.Size);
|
||||||
|
for (uint8 kind = 0; kind < ENTITY(Count); ++kind)
|
||||||
|
{
|
||||||
|
const Class* class_ptr = resolve_entity_class(kind, name_crc);
|
||||||
|
if (class_ptr != NULL)
|
||||||
|
{
|
||||||
|
result = class_ptr;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
+83
-50
@@ -1,67 +1,100 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Core/Common/CoreUtils.h>
|
#include <Core/Common/CoreUtils.h>
|
||||||
#include <Core/Memory/Allocator.h>
|
#include <Core/Math/Vector.h>
|
||||||
#include <Core/Memory/MemoryArena.h>
|
|
||||||
#include <Engine/Class.h>
|
#include <Engine/Class.h>
|
||||||
|
#include <Entity/entity_common.h>
|
||||||
#include <Entity/EntityManager.h>
|
#include <Entity/EntityManager.h>
|
||||||
|
|
||||||
#define DECLARE_ENTITY() \
|
typedef struct Entity Entity;
|
||||||
Entity* Base; \
|
struct Entity
|
||||||
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;
|
EntityID ID;
|
||||||
const Juliet::Class* Kind;
|
const Class* derived_kind;
|
||||||
DerivedType Derived;
|
DerivedType derived;
|
||||||
float X, Y;
|
Vector4 position;
|
||||||
|
bool8 is_dirty;
|
||||||
|
};
|
||||||
|
DECLARE_CLASS(Entity)
|
||||||
|
|
||||||
|
// Can cast to this to have the offset of base for any entity
|
||||||
|
typedef struct EntityTemplate EntityTemplate;
|
||||||
|
struct EntityTemplate
|
||||||
|
{
|
||||||
|
DECLARE_ENTITY()
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename EntityType>
|
static inline EntityID make_entity_id(uint8 user_id, uint64 local_counter)
|
||||||
concept EntityConcept = requires(EntityType entity) {
|
|
||||||
requires std::same_as<decltype(entity.Kind), const Juliet::Class*>;
|
|
||||||
requires std::same_as<decltype(entity.Base), Entity*>;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename EntityType>
|
|
||||||
requires EntityConcept<EntityType>
|
|
||||||
bool IsA(const Entity* entity)
|
|
||||||
{
|
{
|
||||||
return entity->Kind == EntityType::Kind;
|
Assert(user_id != kInvalid_user_id);
|
||||||
}
|
Assert((local_counter & kEntityID_user_id_mask) == kInvalid_entity_id);
|
||||||
|
|
||||||
template <typename EntityType>
|
|
||||||
requires EntityConcept<EntityType>
|
|
||||||
EntityType* MakeEntity(EntityManager& manager, float x, float y)
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
|
|
||||||
result->Base = manager.Entities.Back();
|
|
||||||
|
|
||||||
RegisterEntity(manager, &base);
|
|
||||||
|
|
||||||
|
EntityID result = ((EntityID)user_id << kEntityID_user_id_shift) | (local_counter & kEntityID_user_id_mask);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename EntityType>
|
static inline uint8 get_user_id(EntityID entity_id)
|
||||||
requires EntityConcept<EntityType>
|
|
||||||
EntityType* DownCast(Entity* entity)
|
|
||||||
{
|
{
|
||||||
Assert(IsA<EntityType>(entity));
|
return (uint8)((entity_id & kEntityID_user_id_mask) >> kEntityID_user_id_shift);
|
||||||
return static_cast<EntityType*>(entity->Derived);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline uint64 get_local_counter(EntityID entity_id)
|
||||||
|
{
|
||||||
|
return (uint64)entity_id & ~kEntityID_user_id_mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool8 is_entity_id_valid(EntityID entity_id)
|
||||||
|
{
|
||||||
|
bool8 result = (entity_id != kInvalid_entity_id);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool8 IsEntityA(const Entity* entity, const Class* kind)
|
||||||
|
{
|
||||||
|
Assert(entity != NULL);
|
||||||
|
return entity->derived_kind == kind;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define IsEntityOfType(entity_ptr, EntityType) IsEntityA((entity_ptr), ClassKind(EntityType))
|
||||||
|
|
||||||
|
static inline void* make_entity_by_class(EntityManager* manager, const Class* kind, float x, float y, float z)
|
||||||
|
{
|
||||||
|
Assert(manager != NULL);
|
||||||
|
Assert(kind != NULL);
|
||||||
|
|
||||||
|
Entity* base_ptr = allocate_entity(manager, kind);
|
||||||
|
Assert(base_ptr != NULL);
|
||||||
|
|
||||||
|
base_ptr->ID = g_entity_manager_id++;
|
||||||
|
|
||||||
|
base_ptr->position.x = x;
|
||||||
|
base_ptr->position.y = y;
|
||||||
|
base_ptr->position.z = z;
|
||||||
|
base_ptr->position.w = 1.0f;
|
||||||
|
|
||||||
|
return base_ptr->derived;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define MakeEntity(EntityType, manager_ptr, x, y, z) \
|
||||||
|
((EntityType*)make_entity_by_class((manager_ptr), ClassKind(EntityType), (x), (y), (z)))
|
||||||
|
|
||||||
|
static inline void* down_cast_entity(Entity* entity, const Class* kind)
|
||||||
|
{
|
||||||
|
Assert(entity != NULL);
|
||||||
|
Assert(IsEntityA(entity, kind));
|
||||||
|
return entity->derived;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define DownCastEntity(EntityType, entity_ptr) ((EntityType*)down_cast_entity((entity_ptr), ClassKind(EntityType)))
|
||||||
|
|
||||||
|
void serialize_entity(Archive* ar, Entity* entity);
|
||||||
|
|
||||||
|
JULIET_API uint8 get_local_user_id(void);
|
||||||
|
|
||||||
|
JULIET_API String format_entity_id(Arena* arena, EntityID id);
|
||||||
|
|
||||||
|
JULIET_API bool8 parse_entity_id(String str, EntityID* outId);
|
||||||
|
|
||||||
|
const Class* find_class_by_name(String name);
|
||||||
|
|
||||||
|
const Class* resolve_entity_class(uint8 kind, uint32 crc);
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
#include <Entity/EntityManager.h>
|
||||||
|
|
||||||
|
#include <Core/Common/serialization.h>
|
||||||
|
#include <Core/Memory/Utils.h>
|
||||||
|
#include <Data/World.h>
|
||||||
|
#include <Engine/user.h>
|
||||||
|
#include <Entity/Entity.h>
|
||||||
|
#include <Entity/entity_types.h>
|
||||||
|
#include <game.h>
|
||||||
|
#include <Graphics/MeshRenderer.h>
|
||||||
|
|
||||||
|
EntityID g_entity_manager_id = 0;
|
||||||
|
|
||||||
|
void InitEntityManager(World* world)
|
||||||
|
{
|
||||||
|
Assert(world != NULL);
|
||||||
|
|
||||||
|
EntityManager* newManager = ArenaPushStruct(world->WorldArena, EntityManager);
|
||||||
|
newManager->next_local_counter = 1ULL;
|
||||||
|
world->EntityManager = newManager;
|
||||||
|
|
||||||
|
VectorArenaCreate(&newManager->Entities, Entity, world->WorldArena, 100000 JULIET_DEBUG_PARAM("Entities"));
|
||||||
|
|
||||||
|
ArenaParams by_type_params = { .ReserveSize = Kilobytes(1),
|
||||||
|
.CommitSize = Kilobytes(1),
|
||||||
|
.Name = "" JULIET_DEBUG_ONLY(, .DoNotReserveMore = true) };
|
||||||
|
for (uint8 i = 0; i < ENTITY(Count); ++i)
|
||||||
|
{
|
||||||
|
by_type_params.Name = kEntity_type_names[i];
|
||||||
|
newManager->by_type[i].arena = ArenaAllocate(by_type_params);
|
||||||
|
newManager->by_type[i].array = NULL;
|
||||||
|
newManager->by_type[i].count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShutdownEntityManager(void)
|
||||||
|
{
|
||||||
|
EntityManager* manager = GetEntityManager();
|
||||||
|
for (uint8 i = 0; i < ENTITY(Count); ++i)
|
||||||
|
{
|
||||||
|
ArenaRelease(manager->by_type[i].arena);
|
||||||
|
}
|
||||||
|
|
||||||
|
vector_arena_destroy(&manager->Entities);
|
||||||
|
}
|
||||||
|
|
||||||
|
EntityManager* GetEntityManager(void)
|
||||||
|
{
|
||||||
|
EntityManager* entityManager = GetGameState()->World->EntityManager;
|
||||||
|
Assert(entityManager != NULL);
|
||||||
|
return entityManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
EntityID generate_next_entity_id(EntityManager* manager)
|
||||||
|
{
|
||||||
|
Assert(manager != NULL);
|
||||||
|
|
||||||
|
UserID user = get_local_user_id();
|
||||||
|
Assert(user != kInvalid_user_id);
|
||||||
|
Assert(manager->next_local_counter != kEntityID_user_id_mask);
|
||||||
|
|
||||||
|
EntityID new_id = make_entity_id(user, manager->next_local_counter);
|
||||||
|
manager->next_local_counter += 1ULL;
|
||||||
|
return new_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
EntityTemplate* RegisterEntity(EntityManager* manager, Entity* base, DerivedType entity)
|
||||||
|
{
|
||||||
|
Assert(manager != NULL);
|
||||||
|
Assert(base != NULL);
|
||||||
|
|
||||||
|
base->ID = g_entity_manager_id++;
|
||||||
|
base->derived = entity;
|
||||||
|
|
||||||
|
VectorArenaPushPtr(Entity, &manager->Entities, base);
|
||||||
|
|
||||||
|
EntityTemplate* ptr = (EntityTemplate*)ArenaPushSize(
|
||||||
|
manager->by_type[base->derived_kind->kind].arena, base->derived_kind->size_of, base->derived_kind->alignment,
|
||||||
|
false JULIET_DEBUG_PARAM(kEntity_type_names[base->derived_kind->kind]));
|
||||||
|
MemCopy(ptr, entity, base->derived_kind->size_of);
|
||||||
|
manager->by_type[base->derived_kind->kind].count += 1;
|
||||||
|
|
||||||
|
ptr->base = VectorArenaBack(Entity, &manager->Entities);
|
||||||
|
|
||||||
|
if (manager->by_type[base->derived_kind->kind].array == NULL)
|
||||||
|
{
|
||||||
|
manager->by_type[base->derived_kind->kind].array = ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Entity* allocate_entity(EntityManager* manager, const Class* derived_type_class)
|
||||||
|
{
|
||||||
|
Assert(manager != NULL);
|
||||||
|
Assert(derived_type_class != NULL);
|
||||||
|
Assert(derived_type_class->kind < ENTITY(Count));
|
||||||
|
Assert(derived_type_class->size_of >= sizeof(EntityTemplate));
|
||||||
|
Assert(derived_type_class->alignment > 0);
|
||||||
|
Assert(derived_type_class->initialize_fct != NULL);
|
||||||
|
|
||||||
|
Entity base_template = { 0 };
|
||||||
|
base_template.derived_kind = derived_type_class;
|
||||||
|
base_template.is_dirty = true;
|
||||||
|
|
||||||
|
VectorArenaPushPtr(Entity, &manager->Entities, &base_template);
|
||||||
|
Entity* base_ptr = VectorArenaBack(Entity, &manager->Entities);
|
||||||
|
Assert(base_ptr != NULL);
|
||||||
|
|
||||||
|
typed_entity_array* typed_array = &manager->by_type[derived_type_class->kind];
|
||||||
|
Assert(typed_array->arena != NULL);
|
||||||
|
|
||||||
|
void* derived = ArenaPushSize(typed_array->arena, derived_type_class->size_of, derived_type_class->alignment,
|
||||||
|
false JULIET_DEBUG_PARAM(kEntity_type_names[derived_type_class->kind]));
|
||||||
|
Assert(derived != NULL);
|
||||||
|
derived_type_class->initialize_fct(derived);
|
||||||
|
|
||||||
|
EntityTemplate* derived_template = (EntityTemplate*)derived;
|
||||||
|
|
||||||
|
base_ptr->derived = derived;
|
||||||
|
derived_template->base = base_ptr;
|
||||||
|
|
||||||
|
if (typed_array->array == NULL)
|
||||||
|
{
|
||||||
|
typed_array->array = derived_template;
|
||||||
|
}
|
||||||
|
typed_array->count += 1;
|
||||||
|
|
||||||
|
return base_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateEntityManager(EntityManager* manager)
|
||||||
|
{
|
||||||
|
Assert(manager != NULL);
|
||||||
|
|
||||||
|
// Todo : inert by definition dont move, but this is for test
|
||||||
|
typed_entity_array* by_type = &manager->by_type[ENTITY(Inert)];
|
||||||
|
for (index_t i = 0; i < by_type->count; ++i)
|
||||||
|
{
|
||||||
|
Inert* inert = ((Inert*)by_type->array) + i;
|
||||||
|
if (inert->MeshInstance != indexMax)
|
||||||
|
{
|
||||||
|
Matrix transform =
|
||||||
|
MatrixTranslation(inert->base->position.x, inert->base->position.y, inert->base->position.z);
|
||||||
|
SetMeshInstanceTransform(inert->MeshInstance, &transform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Entity* deserialize_entity(Archive* ar, EntityManager* manager)
|
||||||
|
{
|
||||||
|
Assert(ar != NULL);
|
||||||
|
Assert(manager != NULL);
|
||||||
|
Assert(ar->loading);
|
||||||
|
|
||||||
|
String class_name = { 0 };
|
||||||
|
SERIALIZE_STR(ar, Class, &class_name);
|
||||||
|
|
||||||
|
const Class* class_ptr = find_class_by_name(class_name);
|
||||||
|
Assert(class_ptr != NULL);
|
||||||
|
|
||||||
|
Entity* entity_base = allocate_entity(manager, class_ptr);
|
||||||
|
Assert(entity_base != NULL);
|
||||||
|
|
||||||
|
serialize_entity(ar, entity_base);
|
||||||
|
|
||||||
|
if (entity_base->ID >= g_entity_manager_id)
|
||||||
|
{
|
||||||
|
g_entity_manager_id = entity_base->ID + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
entity_base->is_dirty = false;
|
||||||
|
|
||||||
|
return entity_base;
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_local_counter(EntityManager* manager, EntityID entity_id)
|
||||||
|
{
|
||||||
|
Assert(manager != NULL);
|
||||||
|
|
||||||
|
uint8 user_id = get_local_user_id();
|
||||||
|
if (get_user_id(entity_id) == user_id)
|
||||||
|
{
|
||||||
|
uint64 counter = get_local_counter(entity_id);
|
||||||
|
if (counter >= manager->next_local_counter)
|
||||||
|
{
|
||||||
|
manager->next_local_counter = counter + 1ULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
#include <Entity/EntityManager.h>
|
|
||||||
|
|
||||||
#include <Entity/Entity.h>
|
|
||||||
|
|
||||||
EntityID EntityManager::ID = 0;
|
|
||||||
|
|
||||||
void InitEntityManager(Juliet::NonNullPtr<World> world)
|
|
||||||
{
|
|
||||||
EntityManager* newManager = Juliet::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"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShutdownEntityManager()
|
|
||||||
{
|
|
||||||
GetEntityManager().Entities.Destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
EntityManager& GetEntityManager()
|
|
||||||
{
|
|
||||||
Juliet::NonNullPtr entityManager = GetGameState()->World->EntityManager;
|
|
||||||
return *entityManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RegisterEntity(EntityManager& /*manager*/, Entity* entity)
|
|
||||||
{
|
|
||||||
entity->ID = EntityManager::ID++;
|
|
||||||
}
|
|
||||||
+32
-15
@@ -1,23 +1,40 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Core/Common/CoreTypes.h>
|
|
||||||
#include <Core/Container/Vector.h>
|
#include <Core/Container/Vector.h>
|
||||||
#include <game.h>
|
#include <Entity/entity_common.h>
|
||||||
|
|
||||||
using EntityID = uint64_t;
|
typedef struct World World;
|
||||||
|
typedef struct EntityTemplate EntityTemplate;
|
||||||
|
typedef struct Entity Entity;
|
||||||
|
typedef struct Class Class;
|
||||||
|
typedef struct Archive Archive;
|
||||||
|
|
||||||
struct Entity;
|
typedef struct typed_entity_array typed_entity_array;
|
||||||
struct EntityManager
|
struct typed_entity_array
|
||||||
{
|
{
|
||||||
static EntityID ID;
|
Arena* arena;
|
||||||
|
EntityTemplate* array;
|
||||||
Juliet::Arena* Arena;
|
size_t count;
|
||||||
|
|
||||||
// TODO: Should be a pool
|
|
||||||
Juliet::VectorArena<Entity, 1024> Entities;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void InitEntityManager(Juliet::NonNullPtr<World> world);
|
typedef struct EntityManager EntityManager;
|
||||||
void ShutdownEntityManager();
|
struct EntityManager
|
||||||
EntityManager& GetEntityManager();
|
{
|
||||||
void RegisterEntity(EntityManager& manager, Entity* entity);
|
uint64 next_local_counter;
|
||||||
|
|
||||||
|
// TODO: Should be a pool
|
||||||
|
VectorArena Entities;
|
||||||
|
|
||||||
|
typed_entity_array by_type[Entity_Type_Count];
|
||||||
|
};
|
||||||
|
|
||||||
|
extern EntityID g_entity_manager_id;
|
||||||
|
|
||||||
|
void InitEntityManager(World* world);
|
||||||
|
void ShutdownEntityManager(void);
|
||||||
|
EntityManager* GetEntityManager(void);
|
||||||
|
EntityTemplate* RegisterEntity(EntityManager* manager, Entity* base, DerivedType entity);
|
||||||
|
Entity* allocate_entity(EntityManager* manager, const Class* derived_type_class);
|
||||||
|
void UpdateEntityManager(EntityManager* manager);
|
||||||
|
Entity* deserialize_entity(Archive* archive, EntityManager* manager);
|
||||||
|
void update_local_counter(EntityManager* manager, EntityID entity_id);
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#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) &class_kind_##name
|
||||||
|
const Class* kEntity_type_class_ptr[] = {
|
||||||
|
ENTITY_TYPE_LIST(AS_CLASS)
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
#undef AS_CLASS
|
||||||
|
// clang-format on
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Engine/Class.h>
|
||||||
|
|
||||||
|
typedef void* DerivedType;
|
||||||
|
typedef uint64 EntityID;
|
||||||
|
|
||||||
|
#define kInvalid_user_id ((uint8)0U)
|
||||||
|
#define kInvalid_entity_id ((EntityID)0ULL)
|
||||||
|
#define kEntityID_user_id_shift ((EntityID)56ULL)
|
||||||
|
#define kEntityID_user_id_mask ((EntityID)0xFF00000000000000ULL)
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
#define ENTITY_TYPE_LIST(X) X(Inert),
|
||||||
|
|
||||||
|
typedef uint8 Entity_Type;
|
||||||
|
#define AS_ENUM(name) Entity_Type_##name
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
ENTITY_TYPE_LIST(AS_ENUM)
|
||||||
|
Entity_Type_Count
|
||||||
|
};
|
||||||
|
#undef AS_ENUM
|
||||||
|
|
||||||
|
extern const char* kEntity_type_names[];
|
||||||
|
extern const Class* kEntity_type_class_ptr[];
|
||||||
|
|
||||||
|
#define ENTITY(kind) ((uint8)Entity_Type_##kind)
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
typedef struct Entity Entity;
|
||||||
|
|
||||||
|
#define DECLARE_ENTITY() Entity* base;
|
||||||
|
|
||||||
|
#define DEFINE_ENTITY_VERSIONED(entity, version_num) \
|
||||||
|
const Class class_kind_##entity = { \
|
||||||
|
.Name = ConstString(#entity), \
|
||||||
|
.kind = (uint8)Entity_Type_##entity, \
|
||||||
|
.version = (version_num), \
|
||||||
|
.base_class = &class_kind_Entity, \
|
||||||
|
.initialize_fct = init_##entity, \
|
||||||
|
.serialize_fct = serialize_##entity, \
|
||||||
|
.size_of = sizeof(entity), \
|
||||||
|
.alignment = AlignOf(entity) \
|
||||||
|
};
|
||||||
|
|
||||||
|
// Note: Needed by every entity class (see define above)
|
||||||
|
extern const Class class_kind_Entity;
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#include <Entity/entity_types.h>
|
||||||
|
|
||||||
|
#include <Core/Common/serialization.h>
|
||||||
|
|
||||||
|
internal void init_Inert(void* payload)
|
||||||
|
{
|
||||||
|
Assert(payload != NULL);
|
||||||
|
Inert* inert = (Inert*)payload;
|
||||||
|
inert->base = NULL;
|
||||||
|
inert->MeshInstance = indexMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void serialize_Inert(Archive* ar, uint16 version, void* payload)
|
||||||
|
{
|
||||||
|
(void)version;
|
||||||
|
Assert(ar != NULL);
|
||||||
|
Assert(payload != NULL);
|
||||||
|
Inert* inert = (Inert*)payload;
|
||||||
|
SERIALIZE_U64(ar, MeshInstance, &inert->MeshInstance);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_ENTITY_VERSIONED(Inert, 1)
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Entity/entity_common.h>
|
||||||
|
|
||||||
|
typedef struct Inert Inert;
|
||||||
|
struct Inert
|
||||||
|
{
|
||||||
|
DECLARE_ENTITY()
|
||||||
|
|
||||||
|
index_t MeshInstance;
|
||||||
|
};
|
||||||
|
DECLARE_CLASS(Inert)
|
||||||
@@ -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
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
#include <UnitTest/WorldUnitTest.h>
|
||||||
|
|
||||||
|
#if JULIET_DEBUG
|
||||||
|
|
||||||
|
#include <Core/Common/CoreUtils.h>
|
||||||
|
#include <Core/Common/serialization.h>
|
||||||
|
#include <Core/HAL/IO/IOStream.h>
|
||||||
|
#include <Core/Logging/LogManager.h>
|
||||||
|
#include <Core/Logging/LogTypes.h>
|
||||||
|
#include <Core/Memory/MemoryArena.h>
|
||||||
|
#include <Core/Thread/ThreadContext.h>
|
||||||
|
#include <Data/World.h>
|
||||||
|
#include <Entity/Entity.h>
|
||||||
|
#include <Entity/entity_types.h>
|
||||||
|
#include <game.h>
|
||||||
|
|
||||||
|
internal void TestEntityAllocationAndWiring(void)
|
||||||
|
{
|
||||||
|
Log(LogLevel_Message, LogCategory_Game, "[UnitTest] Running TestEntityAllocationAndWiring...");
|
||||||
|
|
||||||
|
TempArena tempArena = scratch_begin(NULL, 0);
|
||||||
|
|
||||||
|
GameState* gameState = GetGameState();
|
||||||
|
World testWorld = { 0 };
|
||||||
|
gameState->World = &testWorld;
|
||||||
|
InitWorld(&testWorld, tempArena.Arena);
|
||||||
|
|
||||||
|
InitEntityManager(&testWorld);
|
||||||
|
EntityManager* manager = testWorld.EntityManager;
|
||||||
|
|
||||||
|
// 1. Allocate Inert Entity via allocate_entity
|
||||||
|
Entity* base_entity = allocate_entity(manager, ClassKind(Inert));
|
||||||
|
Assert(base_entity != NULL);
|
||||||
|
Assert(base_entity->ID == 0); // Pure memory allocator leaves ID unassigned (0) until MakeEntity or deserialization
|
||||||
|
base_entity->ID = ++g_entity_manager_id;
|
||||||
|
Assert(base_entity->ID > 0);
|
||||||
|
Assert(base_entity->derived_kind == ClassKind(Inert));
|
||||||
|
Assert(base_entity->derived != NULL);
|
||||||
|
Assert(base_entity->is_dirty == true);
|
||||||
|
|
||||||
|
// 2. Validate mutual back-pointer wiring
|
||||||
|
EntityTemplate* derived = (EntityTemplate*)base_entity->derived;
|
||||||
|
Assert(derived->base == base_entity);
|
||||||
|
|
||||||
|
// 3. DownCast verification
|
||||||
|
Inert* inert = DownCastEntity(Inert, base_entity);
|
||||||
|
Assert(inert != NULL);
|
||||||
|
Assert(inert->base == base_entity);
|
||||||
|
|
||||||
|
// 4. Validate typed array tracking
|
||||||
|
typed_entity_array* inert_array = &manager->by_type[ENTITY(Inert)];
|
||||||
|
Assert(inert_array->count == 1);
|
||||||
|
Assert(inert_array->array == derived);
|
||||||
|
|
||||||
|
ShutdownEntityManager();
|
||||||
|
ShutdownWorld(&testWorld);
|
||||||
|
|
||||||
|
gameState->World = NULL;
|
||||||
|
|
||||||
|
scratch_end(tempArena);
|
||||||
|
|
||||||
|
Log(LogLevel_Message, LogCategory_Game, "[UnitTest] PASSED: TestEntityAllocationAndWiring");
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void TestInPlaceDeserialization(void)
|
||||||
|
{
|
||||||
|
Log(LogLevel_Message, LogCategory_Game, "[UnitTest] Running TestInPlaceDeserialization...");
|
||||||
|
TempArena tempArena = scratch_begin(NULL, 0);
|
||||||
|
|
||||||
|
GameState* gameState = GetGameState();
|
||||||
|
World testWorld = { 0 };
|
||||||
|
gameState->World = &testWorld;
|
||||||
|
InitWorld(&testWorld, tempArena.Arena);
|
||||||
|
|
||||||
|
InitEntityManager(&testWorld);
|
||||||
|
EntityManager* manager = testWorld.EntityManager;
|
||||||
|
// 1. In-memory .jasset text fixture (no streams or files needed)
|
||||||
|
String asset_content = ConstString("; Class\n"
|
||||||
|
"Inert\n"
|
||||||
|
"; version\n"
|
||||||
|
"1\n"
|
||||||
|
"; class_version\n"
|
||||||
|
"1\n"
|
||||||
|
"; id\n"
|
||||||
|
"42\n"
|
||||||
|
"; position\n"
|
||||||
|
"12.5 -44.0 108.2 1.0\n"
|
||||||
|
"; MeshInstance\n"
|
||||||
|
"42\n");
|
||||||
|
ByteBuffer buffer = { .Data = (Byte*)asset_content.Str, .Size = asset_content.Size };
|
||||||
|
// 2. Tokenize into Archive
|
||||||
|
Archive load_ar = { 0 };
|
||||||
|
load_ar.arena = tempArena.Arena;
|
||||||
|
load_ar.loading = true;
|
||||||
|
load_ar.base = tokenize_archive(tempArena.Arena, buffer);
|
||||||
|
// 3. Deserialize in-place
|
||||||
|
Entity* loaded_base = deserialize_entity(&load_ar, manager);
|
||||||
|
// 4. Verify in-place allocations and values
|
||||||
|
Assert(loaded_base != NULL);
|
||||||
|
Assert(loaded_base->ID == 42);
|
||||||
|
Assert(g_entity_manager_id > 42); // Generator counter advanced past loaded ID
|
||||||
|
Assert(loaded_base->position.x == 12.5f);
|
||||||
|
Assert(loaded_base->position.y == -44.0f);
|
||||||
|
Assert(loaded_base->position.z == 108.2f);
|
||||||
|
Assert(loaded_base->is_dirty == false);
|
||||||
|
Inert* loaded_inert = DownCastEntity(Inert, loaded_base);
|
||||||
|
Assert(loaded_inert != NULL);
|
||||||
|
Assert(loaded_inert->base == loaded_base);
|
||||||
|
Assert(loaded_inert->MeshInstance == 42);
|
||||||
|
ShutdownEntityManager();
|
||||||
|
ShutdownWorld(&testWorld);
|
||||||
|
|
||||||
|
gameState->World = NULL;
|
||||||
|
|
||||||
|
scratch_end(tempArena);
|
||||||
|
Log(LogLevel_Message, LogCategory_Game, "[UnitTest] PASSED: TestInPlaceDeserialization");
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void TestDirtyTrackingLifecycle(void)
|
||||||
|
{
|
||||||
|
Log(LogLevel_Message, LogCategory_Game, "[UnitTest] Running TestDirtyTrackingLifecycle...");
|
||||||
|
|
||||||
|
TempArena tempArena = scratch_begin(NULL, 0);
|
||||||
|
|
||||||
|
GameState* gameState = GetGameState();
|
||||||
|
World testWorld = { 0 };
|
||||||
|
gameState->World = &testWorld;
|
||||||
|
InitWorld(&testWorld, tempArena.Arena);
|
||||||
|
|
||||||
|
InitEntityManager(&testWorld);
|
||||||
|
EntityManager* manager = testWorld.EntityManager;
|
||||||
|
|
||||||
|
Entity* entity = allocate_entity(manager, ClassKind(Inert));
|
||||||
|
Assert(entity->is_dirty == true);
|
||||||
|
|
||||||
|
// TODO : proper save and mutation
|
||||||
|
// Simulate save
|
||||||
|
entity->is_dirty = false;
|
||||||
|
Assert(entity->is_dirty == false);
|
||||||
|
|
||||||
|
// Simulate mutation
|
||||||
|
entity->position.x += 1.0f;
|
||||||
|
entity->is_dirty = true;
|
||||||
|
Assert(entity->is_dirty == true);
|
||||||
|
|
||||||
|
ShutdownEntityManager();
|
||||||
|
ShutdownWorld(&testWorld);
|
||||||
|
|
||||||
|
gameState->World = NULL;
|
||||||
|
|
||||||
|
scratch_end(tempArena);
|
||||||
|
Log(LogLevel_Message, LogCategory_Game, "[UnitTest] PASSED: TestDirtyTrackingLifecycle");
|
||||||
|
}
|
||||||
|
|
||||||
|
void UnitTest_WorldUnitTest(void)
|
||||||
|
{
|
||||||
|
Log(LogLevel_Message, LogCategory_Game, "==================================================");
|
||||||
|
Log(LogLevel_Message, LogCategory_Game, "Starting Entity Allocation & Lifecycle Unit Tests");
|
||||||
|
Log(LogLevel_Message, LogCategory_Game, "==================================================");
|
||||||
|
|
||||||
|
TestEntityAllocationAndWiring();
|
||||||
|
TestInPlaceDeserialization();
|
||||||
|
// TestSwapAndPopPointerFixup(); // Deferred to Entity_Removal_Brainstorm.md
|
||||||
|
TestDirtyTrackingLifecycle();
|
||||||
|
|
||||||
|
Log(LogLevel_Message, LogCategory_Game, "==================================================");
|
||||||
|
Log(LogLevel_Message, LogCategory_Game, "All Entity Lifecycle Unit Tests PASSED Successfully");
|
||||||
|
Log(LogLevel_Message, LogCategory_Game, "==================================================");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Juliet.h>
|
||||||
|
|
||||||
|
#if JULIET_DEBUG
|
||||||
|
|
||||||
|
void UnitTest_WorldUnitTest(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
+167
@@ -0,0 +1,167 @@
|
|||||||
|
#include <game.h>
|
||||||
|
|
||||||
|
#include <Controller/DebugCameraController.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 <dcimgui.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#if JULIET_DEBUG
|
||||||
|
#include <Debug/DebugTopBar.h>
|
||||||
|
#include <UnitTest/WorldUnitTest.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
global GameState* gGameState = NULL;
|
||||||
|
|
||||||
|
GameState* GetGameState(void)
|
||||||
|
{
|
||||||
|
return gGameState;
|
||||||
|
}
|
||||||
|
|
||||||
|
JULIET_API void __cdecl GameShutdown(void)
|
||||||
|
{
|
||||||
|
printf("Shutting down game...\n");
|
||||||
|
|
||||||
|
if (gGameState != NULL && gGameState->World != NULL)
|
||||||
|
{
|
||||||
|
ShutdownWorld(gGameState->World);
|
||||||
|
}
|
||||||
|
|
||||||
|
ShutdownEntityManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
JULIET_API void __cdecl GameUpdate(GameData* params, float deltaTime)
|
||||||
|
{
|
||||||
|
Assert(params != NULL);
|
||||||
|
|
||||||
|
gGameState = params->GameState;
|
||||||
|
if (gGameState == NULL)
|
||||||
|
{
|
||||||
|
ArenaParams gameStateParams = { .ReserveSize = Megabytes(1), .Name = "Game Total Arena" };
|
||||||
|
Arena* gameStateArena = ArenaAllocate(gameStateParams);
|
||||||
|
GameState* gameState = ArenaPushStruct(gameStateArena, GameState);
|
||||||
|
gGameState = params->GameState = gameState;
|
||||||
|
gameState->TotalArena = gameStateArena;
|
||||||
|
gameState->Mode = GameMode_Editor;
|
||||||
|
|
||||||
|
gameState->TotalTime = 0.0f;
|
||||||
|
gameState->Score = 0;
|
||||||
|
|
||||||
|
printf("Game Arena Allocated: %p\n", (void*)gameState);
|
||||||
|
|
||||||
|
// Reserve cameras. Lets go with 4 for now
|
||||||
|
ReserveCamera(4);
|
||||||
|
|
||||||
|
#if JULIET_DEBUG
|
||||||
|
UnitTest_WorldUnitTest();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Bootstrap world
|
||||||
|
ArenaParams worldParams = { .ReserveSize = Megabytes(1), .Name = "World Arena" };
|
||||||
|
Arena* worldArena = ArenaAllocate(worldParams);
|
||||||
|
World* world = ArenaPushStructTag(worldArena, World, "World");
|
||||||
|
gameState->World = world;
|
||||||
|
InitWorld(gameState->World, worldArena);
|
||||||
|
|
||||||
|
// Entity Use case
|
||||||
|
InitEntityManager(gameState->World);
|
||||||
|
EntityManager* manager = GetEntityManager();
|
||||||
|
|
||||||
|
Inert* mesh = MakeEntity(Inert, manager, 1.f, 2.f, 0.f);
|
||||||
|
Assert(mesh != NULL);
|
||||||
|
|
||||||
|
MeshAssetID cubeAsset = GetCubePrimitiveMeshAssetID();
|
||||||
|
Matrix identity = MatrixIdentity();
|
||||||
|
MeshInstanceID meshInst = CreateMeshInstance(cubeAsset, 0, &identity);
|
||||||
|
mesh->MeshInstance = meshInst;
|
||||||
|
|
||||||
|
Inert* mesh2 = MakeEntity(Inert, manager, 4.f, 0.f, 2.f);
|
||||||
|
Assert(mesh2 != NULL);
|
||||||
|
|
||||||
|
MeshInstanceID meshInst2 = CreateMeshInstance(cubeAsset, 0, &identity);
|
||||||
|
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;
|
||||||
|
SetGlobalLight(sunDirection, sunColor, ambientIntensity);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if JULIET_DEBUG
|
||||||
|
DrawTopBar();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
GameMode previousFrameMode = gGameState->Mode;
|
||||||
|
if (IsKeyPressed(ScanCode_F1))
|
||||||
|
{
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
EntityManager* manager = GetEntityManager();
|
||||||
|
UpdateEntityManager(manager);
|
||||||
|
|
||||||
|
if (gGameState->Mode == GameMode_Debug)
|
||||||
|
{
|
||||||
|
if (previousFrameMode == GameMode_Play)
|
||||||
|
{
|
||||||
|
ActivateDebugController();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsKeyPressed(ScanCode_Escape))
|
||||||
|
{
|
||||||
|
gGameState->Mode = GameMode_Editor;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateDebugController(deltaTime);
|
||||||
|
|
||||||
|
#if JULIET_DEBUG
|
||||||
|
ImGui_Begin("Debug", NULL, ImGuiWindowFlags_None);
|
||||||
|
RenderImGuiDebugController(deltaTime);
|
||||||
|
ImGui_End();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
if (gGameState->Mode != GameMode_Debug && previousFrameMode == GameMode_Debug)
|
||||||
|
{
|
||||||
|
DeactivateDebugController();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
|
|
||||||
#include <Windows.h> // TODO: remove because our dll should not be platform dependant
|
|
||||||
#undef min
|
|
||||||
#undef max
|
|
||||||
|
|
||||||
#include <game.h>
|
|
||||||
|
|
||||||
#include <Core/HAL/Filesystem/Filesystem.h>
|
|
||||||
#include <Core/JulietInit.h>
|
|
||||||
#include <Core/Logging/LogManager.h>
|
|
||||||
#include <Core/Memory/MemoryArena.h>
|
|
||||||
#include <Entity/Entity.h>
|
|
||||||
#include <Entity/EntityManager.h>
|
|
||||||
#include <Graphics/Camera.h>
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
ShutdownEntityManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" JULIET_API void __cdecl GameUpdate(Juliet::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"));
|
|
||||||
auto* gameState = ArenaPushStruct<GameState>(gameStateArena);
|
|
||||||
gGameState = params->GameState = gameState;
|
|
||||||
gameState->TotalArena = gameStateArena;
|
|
||||||
|
|
||||||
gameState->TotalTime = 0.0f;
|
|
||||||
gameState->Score = 0;
|
|
||||||
|
|
||||||
printf("Game Arena Allocated: %p\n", static_cast<void*>(gameState));
|
|
||||||
|
|
||||||
// Reserve cameras. Lets go with 4 for now
|
|
||||||
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;
|
|
||||||
|
|
||||||
// Entity Use case
|
|
||||||
InitEntityManager(gameState->World);
|
|
||||||
auto& manager = GetEntityManager();
|
|
||||||
Door* door = MakeEntity<Door>(manager, 10.0f, 2.0f);
|
|
||||||
door->IsOpened = true;
|
|
||||||
|
|
||||||
Entity* ent = door->Base;
|
|
||||||
[[maybe_unused]] Door* stillDoor = DownCast<Door>(ent);
|
|
||||||
Assert(door == stillDoor);
|
|
||||||
|
|
||||||
Rock* rock = MakeEntity<Rock>(manager, 1.f, 2.f);
|
|
||||||
rock->Health = 100;
|
|
||||||
Assert(door->Base != rock->Base);
|
|
||||||
|
|
||||||
printf("Door is %s\n", door->IsOpened ? "Opened" : "Closed");
|
|
||||||
printf("Rock has %d health points\n", rock->Health);
|
|
||||||
}
|
|
||||||
|
|
||||||
// printf("Updating game...\n");
|
|
||||||
}
|
|
||||||
+12
-6
@@ -1,23 +1,29 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Core/Memory/MemoryArena.h>
|
#include <Core/Memory/MemoryArena.h>
|
||||||
|
#include <Data/World.h>
|
||||||
|
|
||||||
struct EntityManager;
|
typedef struct EntityManager EntityManager;
|
||||||
|
|
||||||
struct World
|
typedef uint32 GameMode;
|
||||||
|
enum
|
||||||
{
|
{
|
||||||
Juliet::Arena* WorldArena;
|
GameMode_Editor = 0,
|
||||||
EntityManager* EntityManager;
|
GameMode_Play,
|
||||||
|
GameMode_Debug
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef struct GameState GameState;
|
||||||
struct GameState
|
struct GameState
|
||||||
{
|
{
|
||||||
Juliet::Arena* TotalArena;
|
Arena* TotalArena;
|
||||||
|
|
||||||
World* World;
|
World* World;
|
||||||
|
|
||||||
|
GameMode Mode;
|
||||||
|
|
||||||
float TotalTime;
|
float TotalTime;
|
||||||
int Score;
|
int Score;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern GameState* GetGameState();
|
extern GameState* GetGameState(void);
|
||||||
@@ -1,270 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<Project DefaultTargets="Build" ToolsVersion="17.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
|
||||||
<ItemGroup Label="ProjectConfigurations">
|
|
||||||
<ProjectConfiguration Include="Debug|x64">
|
|
||||||
<Configuration>Debug</Configuration>
|
|
||||||
<Platform>x64</Platform>
|
|
||||||
</ProjectConfiguration>
|
|
||||||
<ProjectConfiguration Include="Profile|x64">
|
|
||||||
<Configuration>Profile</Configuration>
|
|
||||||
<Platform>x64</Platform>
|
|
||||||
</ProjectConfiguration>
|
|
||||||
<ProjectConfiguration Include="Release|x64">
|
|
||||||
<Configuration>Release</Configuration>
|
|
||||||
<Platform>x64</Platform>
|
|
||||||
</ProjectConfiguration>
|
|
||||||
</ItemGroup>
|
|
||||||
<PropertyGroup Label="Globals">
|
|
||||||
<ProjectGuid>{a93aa30f-8f29-02fe-57e2-cd3626948b68}</ProjectGuid>
|
|
||||||
<Keyword>MakeFileProj</Keyword>
|
|
||||||
</PropertyGroup>
|
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
|
||||||
<ConfigurationType>Makefile</ConfigurationType>
|
|
||||||
<UseDebugLibraries>false</UseDebugLibraries>
|
|
||||||
<PlatformToolset>v143</PlatformToolset>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile|x64'" Label="Configuration">
|
|
||||||
<ConfigurationType>Makefile</ConfigurationType>
|
|
||||||
<UseDebugLibraries>false</UseDebugLibraries>
|
|
||||||
<PlatformToolset>v143</PlatformToolset>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
|
||||||
<ConfigurationType>Makefile</ConfigurationType>
|
|
||||||
<UseDebugLibraries>false</UseDebugLibraries>
|
|
||||||
<PlatformToolset>v143</PlatformToolset>
|
|
||||||
</PropertyGroup>
|
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
|
||||||
<ImportGroup Label="ExtensionSettings">
|
|
||||||
</ImportGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
|
||||||
<NMakeBuildCommandLine>cd $(SolutionDir) & misc\build.bat Debug $(SolutionName)</NMakeBuildCommandLine>
|
|
||||||
<NMakeReBuildCommandLine>cd $(SolutionDir) & misc\build.bat -clean Debug $(SolutionName)</NMakeReBuildCommandLine>
|
|
||||||
<NMakeCleanCommandLine>cd $(SolutionDir) & misc\build.bat -clean Debug $(SolutionName)</NMakeCleanCommandLine>
|
|
||||||
<NMakeOutput>$(SolutionDir)bin\x64-Debug\Juliet.exe</NMakeOutput>
|
|
||||||
<NMakeIncludeSearchPath>$(SolutionDir)Juliet\include;$(SolutionDir)Juliet\src;$(SolutionDir)Game;$(SolutionDir)External\imgui;$(SolutionDir)External\imgui\backends;$(NMakeIncludeSearchPath)</NMakeIncludeSearchPath>
|
|
||||||
<IncludePath>$(SolutionDir)Juliet\include;$(SolutionDir)Juliet\src;$(SolutionDir)Game;$(SolutionDir)External\imgui;$(SolutionDir)External\imgui\backends;$(IncludePath)</IncludePath>
|
|
||||||
<NMakeForcedIncludes>Core/PCH.h;$(NMakeForcedIncludes)</NMakeForcedIncludes>
|
|
||||||
<NMakePreprocessorDefinitions>JULIET_WIN32;UNICODE;_UNICODE;WIN32_LEAN_AND_MEAN;_CRT_SECURE_NO_WARNINGS;DEBUG;JULIET_DEBUG;PROFILING_ENABLED;JULIET_ENABLE_IMGUI;JULIET_EXPORT;$(NMakePreprocessorDefinitions)</NMakePreprocessorDefinitions>
|
|
||||||
<AdditionalOptions>/std:c++20</AdditionalOptions>
|
|
||||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
|
||||||
<LocalDebuggerWorkingDirectory>$(SolutionDir)bin\x64-Debug\</LocalDebuggerWorkingDirectory>
|
|
||||||
<IntDir>$(SolutionDir)Intermediate\x64-Debug\Juliet\</IntDir>
|
|
||||||
<OutDir>$(SolutionDir)bin\x64-Debug\</OutDir>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile|x64'">
|
|
||||||
<NMakeBuildCommandLine>cd $(SolutionDir) & misc\build.bat Profile $(SolutionName)</NMakeBuildCommandLine>
|
|
||||||
<NMakeReBuildCommandLine>cd $(SolutionDir) & misc\build.bat -clean Profile $(SolutionName)</NMakeReBuildCommandLine>
|
|
||||||
<NMakeCleanCommandLine>cd $(SolutionDir) & misc\build.bat -clean Profile $(SolutionName)</NMakeCleanCommandLine>
|
|
||||||
<NMakeOutput>$(SolutionDir)bin\x64-Profile\Juliet.exe</NMakeOutput>
|
|
||||||
<NMakeIncludeSearchPath>$(SolutionDir)Juliet\include;$(SolutionDir)Juliet\src;$(SolutionDir)Game;$(SolutionDir)External\imgui;$(SolutionDir)External\imgui\backends;$(NMakeIncludeSearchPath)</NMakeIncludeSearchPath>
|
|
||||||
<IncludePath>$(SolutionDir)Juliet\include;$(SolutionDir)Juliet\src;$(SolutionDir)Game;$(SolutionDir)External\imgui;$(SolutionDir)External\imgui\backends;$(IncludePath)</IncludePath>
|
|
||||||
<NMakeForcedIncludes>Core/PCH.h;$(NMakeForcedIncludes)</NMakeForcedIncludes>
|
|
||||||
<NMakePreprocessorDefinitions>JULIET_WIN32;UNICODE;_UNICODE;WIN32_LEAN_AND_MEAN;_CRT_SECURE_NO_WARNINGS;RELEASE;PROFILING_ENABLED;JULIET_ENABLE_IMGUI;JULIET_EXPORT;$(NMakePreprocessorDefinitions)</NMakePreprocessorDefinitions>
|
|
||||||
<AdditionalOptions>/std:c++20</AdditionalOptions>
|
|
||||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
|
||||||
<LocalDebuggerWorkingDirectory>$(SolutionDir)bin\x64-Profile\</LocalDebuggerWorkingDirectory>
|
|
||||||
<IntDir>$(SolutionDir)Intermediate\x64-Profile\Juliet\</IntDir>
|
|
||||||
<OutDir>$(SolutionDir)bin\x64-Profile\</OutDir>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
|
||||||
<NMakeBuildCommandLine>cd $(SolutionDir) & misc\build.bat Release $(SolutionName)</NMakeBuildCommandLine>
|
|
||||||
<NMakeReBuildCommandLine>cd $(SolutionDir) & misc\build.bat -clean Release $(SolutionName)</NMakeReBuildCommandLine>
|
|
||||||
<NMakeCleanCommandLine>cd $(SolutionDir) & misc\build.bat -clean Release $(SolutionName)</NMakeCleanCommandLine>
|
|
||||||
<NMakeOutput>$(SolutionDir)bin\x64-Release\Juliet.exe</NMakeOutput>
|
|
||||||
<NMakeIncludeSearchPath>$(SolutionDir)Juliet\include;$(SolutionDir)Juliet\src;$(SolutionDir)Game;$(SolutionDir)External\imgui;$(SolutionDir)External\imgui\backends;$(NMakeIncludeSearchPath)</NMakeIncludeSearchPath>
|
|
||||||
<IncludePath>$(SolutionDir)Juliet\include;$(SolutionDir)Juliet\src;$(SolutionDir)Game;$(SolutionDir)External\imgui;$(SolutionDir)External\imgui\backends;$(IncludePath)</IncludePath>
|
|
||||||
<NMakeForcedIncludes>Core/PCH.h;$(NMakeForcedIncludes)</NMakeForcedIncludes>
|
|
||||||
<NMakePreprocessorDefinitions>JULIET_WIN32;UNICODE;_UNICODE;WIN32_LEAN_AND_MEAN;_CRT_SECURE_NO_WARNINGS;RELEASE;JULIET_EXPORT;$(NMakePreprocessorDefinitions)</NMakePreprocessorDefinitions>
|
|
||||||
<AdditionalOptions>/std:c++20</AdditionalOptions>
|
|
||||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
|
||||||
<LocalDebuggerWorkingDirectory>$(SolutionDir)bin\x64-Release\</LocalDebuggerWorkingDirectory>
|
|
||||||
<IntDir>$(SolutionDir)Intermediate\x64-Release\Juliet\</IntDir>
|
|
||||||
<OutDir>$(SolutionDir)bin\x64-Release\</OutDir>
|
|
||||||
</PropertyGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<ClCompile Include="src\Core\Application\ApplicationManager.cpp" />
|
|
||||||
<ClCompile Include="src\Core\Common\CoreUtils.cpp" />
|
|
||||||
<ClCompile Include="src\Core\Common\String.cpp" />
|
|
||||||
<ClCompile Include="src\Core\Container\Vector.cpp" />
|
|
||||||
<ClCompile Include="src\Core\HAL\Display\Display.cpp" />
|
|
||||||
<ClCompile Include="src\Core\HAL\Display\Win32\Win32DisplayDevice.cpp" />
|
|
||||||
<ClCompile Include="src\Core\HAL\Display\Win32\Win32DisplayEvent.cpp" />
|
|
||||||
<ClCompile Include="src\Core\HAL\Display\Win32\Win32Window.cpp" />
|
|
||||||
<ClCompile Include="src\Core\HAL\DynLib\Win32\DynamicLibrary.cpp" />
|
|
||||||
<ClCompile Include="src\Core\HAL\Event\Keyboard.cpp" />
|
|
||||||
<ClCompile Include="src\Core\HAL\Event\KeyboardMapping.cpp" />
|
|
||||||
<ClCompile Include="src\Core\HAL\Event\Mouse.cpp" />
|
|
||||||
<ClCompile Include="src\Core\HAL\Event\SystemEvent.cpp" />
|
|
||||||
<ClCompile Include="src\Core\HAL\Event\WindowEvent.cpp" />
|
|
||||||
<ClCompile Include="src\Core\HAL\Filesystem\Filesystem.cpp" />
|
|
||||||
<ClCompile Include="src\Core\HAL\Filesystem\Win32\Win32Filesystem.cpp" />
|
|
||||||
<ClCompile Include="src\Core\HAL\IO\IOStream.cpp" />
|
|
||||||
<ClCompile Include="src\Core\HAL\IO\Win32\Win32IOStream.cpp" />
|
|
||||||
<ClCompile Include="src\Core\HAL\OS\OS.cpp" />
|
|
||||||
<ClCompile Include="src\Core\HAL\OS\Win32\Win32OS.cpp" />
|
|
||||||
<ClCompile Include="src\Core\HotReload\HotReload.cpp" />
|
|
||||||
<ClCompile Include="src\Core\HotReload\Win32\Win32HotReload.cpp" />
|
|
||||||
<ClCompile Include="src\Core\ImGui\ImGuiService.cpp" />
|
|
||||||
<ClCompile Include="src\Core\ImGui\ImGuiTests.cpp" />
|
|
||||||
<ClCompile Include="src\Core\Juliet.cpp" />
|
|
||||||
<ClCompile Include="src\Core\Logging\LogManager.cpp" />
|
|
||||||
<ClCompile Include="src\Core\Math\MathRound.cpp" />
|
|
||||||
<ClCompile Include="src\Core\Memory\Allocator.cpp" />
|
|
||||||
<ClCompile Include="src\Core\Memory\MemoryArena.cpp" />
|
|
||||||
<ClCompile Include="src\Core\Memory\MemoryArenaDebug.cpp" />
|
|
||||||
<ClCompile Include="src\Core\Memory\MemoryArenaTests.cpp" />
|
|
||||||
<ClCompile Include="src\Core\Memory\ScratchArena.cpp" />
|
|
||||||
<ClCompile Include="src\Core\Networking\NetworkPacket.cpp" />
|
|
||||||
<ClCompile Include="src\Core\Networking\Socket.cpp" />
|
|
||||||
<ClCompile Include="src\Core\Networking\TcpListener.cpp" />
|
|
||||||
<ClCompile Include="src\Core\Networking\TcpSocket.cpp" />
|
|
||||||
<ClCompile Include="src\Core\Networking\Win32\Win32SocketPlatformImpl.cpp" />
|
|
||||||
<ClCompile Include="src\Core\PCH.cpp" />
|
|
||||||
<ClCompile Include="src\Engine\Debug\MemoryDebugger.cpp" />
|
|
||||||
<ClCompile Include="src\Engine\Engine.cpp" />
|
|
||||||
<ClCompile Include="src\Graphics\Camera.cpp" />
|
|
||||||
<ClCompile Include="src\Graphics\D3D12\AgilitySDK\d3dx12\d3dx12_property_format_table.cpp" />
|
|
||||||
<ClCompile Include="src\Graphics\D3D12\D3D12Buffer.cpp" />
|
|
||||||
<ClCompile Include="src\Graphics\D3D12\D3D12CommandList.cpp" />
|
|
||||||
<ClCompile Include="src\Graphics\D3D12\D3D12Common.cpp" />
|
|
||||||
<ClCompile Include="src\Graphics\D3D12\D3D12DescriptorHeap.cpp" />
|
|
||||||
<ClCompile Include="src\Graphics\D3D12\D3D12GraphicsDevice.cpp" />
|
|
||||||
<ClCompile Include="src\Graphics\D3D12\D3D12GraphicsPipeline.cpp" />
|
|
||||||
<ClCompile Include="src\Graphics\D3D12\D3D12InternalTests.cpp" />
|
|
||||||
<ClCompile Include="src\Graphics\D3D12\D3D12RenderPass.cpp" />
|
|
||||||
<ClCompile Include="src\Graphics\D3D12\D3D12Shader.cpp" />
|
|
||||||
<ClCompile Include="src\Graphics\D3D12\D3D12SwapChain.cpp" />
|
|
||||||
<ClCompile Include="src\Graphics\D3D12\D3D12Synchronization.cpp" />
|
|
||||||
<ClCompile Include="src\Graphics\D3D12\D3D12Texture.cpp" />
|
|
||||||
<ClCompile Include="src\Graphics\D3D12\D3D12Utils.cpp" />
|
|
||||||
<ClCompile Include="src\Graphics\DebugDisplayRenderer.cpp" />
|
|
||||||
<ClCompile Include="src\Graphics\Graphics.cpp" />
|
|
||||||
<ClCompile Include="src\Graphics\ImGuiRenderer.cpp" />
|
|
||||||
<ClCompile Include="src\Graphics\Mesh.cpp" />
|
|
||||||
<ClCompile Include="src\Graphics\MeshRenderer.cpp" />
|
|
||||||
<ClCompile Include="src\Graphics\SkyboxRenderer.cpp" />
|
|
||||||
<ClCompile Include="src\UnitTest\Container\VectorUnitTest.cpp" />
|
|
||||||
<ClCompile Include="src\UnitTest\RunUnitTests.cpp" />
|
|
||||||
<ClInclude Include="include\Core\Application\ApplicationManager.h" />
|
|
||||||
<ClInclude Include="include\Core\Application\IApplication.h" />
|
|
||||||
<ClInclude Include="include\Core\Common\CoreTypes.h" />
|
|
||||||
<ClInclude Include="include\Core\Common\CoreUtils.h" />
|
|
||||||
<ClInclude Include="include\Core\Common\CRC32.h" />
|
|
||||||
<ClInclude Include="include\Core\Common\EnumUtils.h" />
|
|
||||||
<ClInclude Include="include\Core\Common\NonNullPtr.h" />
|
|
||||||
<ClInclude Include="include\Core\Common\String.h" />
|
|
||||||
<ClInclude Include="include\Core\Container\Vector.h" />
|
|
||||||
<ClInclude Include="include\Core\HAL\Display\Display.h" />
|
|
||||||
<ClInclude Include="include\Core\HAL\DynLib\DynamicLibrary.h" />
|
|
||||||
<ClInclude Include="include\Core\HAL\Event\SystemEvent.h" />
|
|
||||||
<ClInclude Include="include\Core\HAL\Filesystem\Filesystem.h" />
|
|
||||||
<ClInclude Include="include\Core\HAL\IO\IOStream.h" />
|
|
||||||
<ClInclude Include="include\Core\HAL\Keyboard\Keyboard.h" />
|
|
||||||
<ClInclude Include="include\Core\HAL\Keyboard\KeyCode.h" />
|
|
||||||
<ClInclude Include="include\Core\HAL\Keyboard\ScanCode.h" />
|
|
||||||
<ClInclude Include="include\Core\HAL\Mouse\Mouse.h" />
|
|
||||||
<ClInclude Include="include\Core\HAL\OS\OS.h" />
|
|
||||||
<ClInclude Include="include\Core\HotReload\HotReload.h" />
|
|
||||||
<ClInclude Include="include\Core\ImGui\ImGuiService.h" />
|
|
||||||
<ClInclude Include="include\Core\ImGui\ImGuiTests.h" />
|
|
||||||
<ClInclude Include="include\Core\JulietInit.h" />
|
|
||||||
<ClInclude Include="include\Core\Logging\LogManager.h" />
|
|
||||||
<ClInclude Include="include\Core\Logging\LogTypes.h" />
|
|
||||||
<ClInclude Include="include\Core\Main.h" />
|
|
||||||
<ClInclude Include="include\Core\Math\MathUtils.h" />
|
|
||||||
<ClInclude Include="include\Core\Math\Matrix.h" />
|
|
||||||
<ClInclude Include="include\Core\Math\Shape.h" />
|
|
||||||
<ClInclude Include="include\Core\Math\Vector.h" />
|
|
||||||
<ClInclude Include="include\Core\Memory\Allocator.h" />
|
|
||||||
<ClInclude Include="include\Core\Memory\MemoryArena.h" />
|
|
||||||
<ClInclude Include="include\Core\Memory\MemoryArenaDebug.h" />
|
|
||||||
<ClInclude Include="include\Core\Memory\ScratchArena.h" />
|
|
||||||
<ClInclude Include="include\Core\Memory\Utils.h" />
|
|
||||||
<ClInclude Include="include\Core\Networking\IPAddress.h" />
|
|
||||||
<ClInclude Include="include\Core\Networking\NetworkPacket.h" />
|
|
||||||
<ClInclude Include="include\Core\Networking\Socket.h" />
|
|
||||||
<ClInclude Include="include\Core\Networking\SocketHandle.h" />
|
|
||||||
<ClInclude Include="include\Core\Networking\TcpListener.h" />
|
|
||||||
<ClInclude Include="include\Core\Networking\TcpSocket.h" />
|
|
||||||
<ClInclude Include="include\Core\PCH.h" />
|
|
||||||
<ClInclude Include="include\Core\Thread\Mutex.h" />
|
|
||||||
<ClInclude Include="include\Core\Thread\Thread.h" />
|
|
||||||
<ClInclude Include="include\Engine\Class.h" />
|
|
||||||
<ClInclude Include="include\Engine\Debug\MemoryDebugger.h" />
|
|
||||||
<ClInclude Include="include\Engine\Engine.h" />
|
|
||||||
<ClInclude Include="include\Graphics\Camera.h" />
|
|
||||||
<ClInclude Include="include\Graphics\Colors.h" />
|
|
||||||
<ClInclude Include="include\Graphics\DebugDisplay.h" />
|
|
||||||
<ClInclude Include="include\Graphics\Graphics.h" />
|
|
||||||
<ClInclude Include="include\Graphics\GraphicsBuffer.h" />
|
|
||||||
<ClInclude Include="include\Graphics\GraphicsConfig.h" />
|
|
||||||
<ClInclude Include="include\Graphics\GraphicsPipeline.h" />
|
|
||||||
<ClInclude Include="include\Graphics\ImGuiRenderer.h" />
|
|
||||||
<ClInclude Include="include\Graphics\Lighting.h" />
|
|
||||||
<ClInclude Include="include\Graphics\Mesh.h" />
|
|
||||||
<ClInclude Include="include\Graphics\MeshRenderer.h" />
|
|
||||||
<ClInclude Include="include\Graphics\PushConstants.h" />
|
|
||||||
<ClInclude Include="include\Graphics\RenderPass.h" />
|
|
||||||
<ClInclude Include="include\Graphics\Shader.h" />
|
|
||||||
<ClInclude Include="include\Graphics\SkyboxRenderer.h" />
|
|
||||||
<ClInclude Include="include\Graphics\Texture.h" />
|
|
||||||
<ClInclude Include="include\Graphics\VertexData.h" />
|
|
||||||
<ClInclude Include="include\Juliet.h" />
|
|
||||||
<ClInclude Include="src\Core\HAL\Display\DisplayDevice.h" />
|
|
||||||
<ClInclude Include="src\Core\HAL\Display\Display_Private.h" />
|
|
||||||
<ClInclude Include="src\Core\HAL\Display\Win32\Win32DisplayEvent.h" />
|
|
||||||
<ClInclude Include="src\Core\HAL\Display\Win32\Win32Window.h" />
|
|
||||||
<ClInclude Include="src\Core\HAL\Display\Window.h" />
|
|
||||||
<ClInclude Include="src\Core\HAL\Event\KeyboardMapping.h" />
|
|
||||||
<ClInclude Include="src\Core\HAL\Event\Keyboard_Private.h" />
|
|
||||||
<ClInclude Include="src\Core\HAL\Event\Mouse_Private.h" />
|
|
||||||
<ClInclude Include="src\Core\HAL\Event\Win32ScanCode.h" />
|
|
||||||
<ClInclude Include="src\Core\HAL\Event\WindowEvent.h" />
|
|
||||||
<ClInclude Include="src\Core\HAL\Filesystem\Filesystem_Platform.h" />
|
|
||||||
<ClInclude Include="src\Core\HAL\Filesystem\Filesystem_Private.h" />
|
|
||||||
<ClInclude Include="src\Core\HAL\IO\IOStream_Private.h" />
|
|
||||||
<ClInclude Include="src\Core\HAL\OS\OS_Private.h" />
|
|
||||||
<ClInclude Include="src\Core\HAL\Win32.h" />
|
|
||||||
<ClInclude Include="src\Core\Math\Math_Private.h" />
|
|
||||||
<ClInclude Include="src\Core\Networking\SocketPlatformImpl.h" />
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\d3d12.h" />
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\d3d12compatibility.h" />
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\d3d12sdklayers.h" />
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\d3d12shader.h" />
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\d3d12video.h" />
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\d3dcommon.h" />
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\d3dx12\d3dx12.h" />
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\d3dx12\d3dx12_barriers.h" />
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\d3dx12\d3dx12_check_feature_support.h" />
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\d3dx12\d3dx12_core.h" />
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\d3dx12\d3dx12_default.h" />
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\d3dx12\d3dx12_pipeline_state_stream.h" />
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\d3dx12\d3dx12_property_format_table.h" />
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\d3dx12\d3dx12_render_pass.h" />
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\d3dx12\d3dx12_resource_helpers.h" />
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\d3dx12\d3dx12_root_signature.h" />
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\d3dx12\d3dx12_state_object.h" />
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\dxgiformat.h" />
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\D3D12Buffer.h" />
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\D3D12CommandList.h" />
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\D3D12Common.h" />
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\D3D12DescriptorHeap.h" />
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\D3D12GraphicsDevice.h" />
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\D3D12GraphicsPipeline.h" />
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\D3D12Includes.h" />
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\D3D12InternalTests.h" />
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\D3D12RenderPass.h" />
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\D3D12Shader.h" />
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\D3D12SwapChain.h" />
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\D3D12Synchronization.h" />
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\D3D12Texture.h" />
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\D3D12Utils.h" />
|
|
||||||
<ClInclude Include="src\Graphics\GraphicsDevice.h" />
|
|
||||||
<ClInclude Include="src\UnitTest\VectorTestHelper.h" />
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\D3D12TokenizedProgramFormat.hpp" />
|
|
||||||
</ItemGroup>
|
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
|
||||||
<ImportGroup Label="ExtensionTargets">
|
|
||||||
</ImportGroup>
|
|
||||||
</Project>
|
|
||||||
@@ -1,658 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
|
||||||
<ItemGroup>
|
|
||||||
<Filter Include="src">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="src\Core">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="src\Core\Application">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="src\Core\Common">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="src\Core\Container">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="src\Core\HAL">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="src\Core\HAL\Display">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="src\Core\HAL\Display\Win32">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="src\Core\HAL\DynLib">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="src\Core\HAL\DynLib\Win32">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="src\Core\HAL\Event">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="src\Core\HAL\Filesystem">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="src\Core\HAL\Filesystem\Win32">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="src\Core\HAL\IO">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="src\Core\HAL\IO\Win32">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="src\Core\HAL\OS">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="src\Core\HAL\OS\Win32">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="src\Core\HotReload">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="src\Core\HotReload\Win32">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="src\Core\ImGui">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="src\Core\Logging">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="src\Core\Math">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="src\Core\Memory">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="src\Core\Networking">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="src\Core\Networking\Win32">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="src\Engine">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="src\Engine\Debug">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="src\Graphics">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="src\Graphics\D3D12">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="src\Graphics\D3D12\AgilitySDK">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="src\Graphics\D3D12\AgilitySDK\d3dx12">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="src\UnitTest">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="src\UnitTest\Container">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="include">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="include\Core">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="include\Core\Application">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="include\Core\Common">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="include\Core\Container">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="include\Core\HAL">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="include\Core\HAL\Display">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="include\Core\HAL\DynLib">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="include\Core\HAL\Event">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="include\Core\HAL\Filesystem">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="include\Core\HAL\IO">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="include\Core\HAL\Keyboard">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="include\Core\HAL\Mouse">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="include\Core\HAL\OS">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="include\Core\HotReload">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="include\Core\ImGui">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="include\Core\Logging">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="include\Core\Math">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="include\Core\Memory">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="include\Core\Networking">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="include\Core\Thread">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="include\Engine">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="include\Engine\Debug">
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="include\Graphics">
|
|
||||||
</Filter>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<ClCompile Include="src\Core\Application\ApplicationManager.cpp">
|
|
||||||
<Filter>src\Core\Application</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Core\Common\CoreUtils.cpp">
|
|
||||||
<Filter>src\Core\Common</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Core\Common\String.cpp">
|
|
||||||
<Filter>src\Core\Common</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Core\Container\Vector.cpp">
|
|
||||||
<Filter>src\Core\Container</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Core\HAL\Display\Display.cpp">
|
|
||||||
<Filter>src\Core\HAL\Display</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Core\HAL\Display\Win32\Win32DisplayDevice.cpp">
|
|
||||||
<Filter>src\Core\HAL\Display\Win32</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Core\HAL\Display\Win32\Win32DisplayEvent.cpp">
|
|
||||||
<Filter>src\Core\HAL\Display\Win32</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Core\HAL\Display\Win32\Win32Window.cpp">
|
|
||||||
<Filter>src\Core\HAL\Display\Win32</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Core\HAL\DynLib\Win32\DynamicLibrary.cpp">
|
|
||||||
<Filter>src\Core\HAL\DynLib\Win32</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Core\HAL\Event\Keyboard.cpp">
|
|
||||||
<Filter>src\Core\HAL\Event</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Core\HAL\Event\KeyboardMapping.cpp">
|
|
||||||
<Filter>src\Core\HAL\Event</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Core\HAL\Event\Mouse.cpp">
|
|
||||||
<Filter>src\Core\HAL\Event</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Core\HAL\Event\SystemEvent.cpp">
|
|
||||||
<Filter>src\Core\HAL\Event</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Core\HAL\Event\WindowEvent.cpp">
|
|
||||||
<Filter>src\Core\HAL\Event</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Core\HAL\Filesystem\Filesystem.cpp">
|
|
||||||
<Filter>src\Core\HAL\Filesystem</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Core\HAL\Filesystem\Win32\Win32Filesystem.cpp">
|
|
||||||
<Filter>src\Core\HAL\Filesystem\Win32</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Core\HAL\IO\IOStream.cpp">
|
|
||||||
<Filter>src\Core\HAL\IO</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Core\HAL\IO\Win32\Win32IOStream.cpp">
|
|
||||||
<Filter>src\Core\HAL\IO\Win32</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Core\HAL\OS\OS.cpp">
|
|
||||||
<Filter>src\Core\HAL\OS</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Core\HAL\OS\Win32\Win32OS.cpp">
|
|
||||||
<Filter>src\Core\HAL\OS\Win32</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Core\HotReload\HotReload.cpp">
|
|
||||||
<Filter>src\Core\HotReload</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Core\HotReload\Win32\Win32HotReload.cpp">
|
|
||||||
<Filter>src\Core\HotReload\Win32</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Core\ImGui\ImGuiService.cpp">
|
|
||||||
<Filter>src\Core\ImGui</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Core\ImGui\ImGuiTests.cpp">
|
|
||||||
<Filter>src\Core\ImGui</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Core\Juliet.cpp">
|
|
||||||
<Filter>src\Core</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Core\Logging\LogManager.cpp">
|
|
||||||
<Filter>src\Core\Logging</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Core\Math\MathRound.cpp">
|
|
||||||
<Filter>src\Core\Math</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Core\Memory\Allocator.cpp">
|
|
||||||
<Filter>src\Core\Memory</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Core\Memory\MemoryArena.cpp">
|
|
||||||
<Filter>src\Core\Memory</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Core\Memory\MemoryArenaDebug.cpp">
|
|
||||||
<Filter>src\Core\Memory</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Core\Memory\MemoryArenaTests.cpp">
|
|
||||||
<Filter>src\Core\Memory</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Core\Memory\ScratchArena.cpp">
|
|
||||||
<Filter>src\Core\Memory</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Core\Networking\NetworkPacket.cpp">
|
|
||||||
<Filter>src\Core\Networking</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Core\Networking\Socket.cpp">
|
|
||||||
<Filter>src\Core\Networking</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Core\Networking\TcpListener.cpp">
|
|
||||||
<Filter>src\Core\Networking</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Core\Networking\TcpSocket.cpp">
|
|
||||||
<Filter>src\Core\Networking</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Core\Networking\Win32\Win32SocketPlatformImpl.cpp">
|
|
||||||
<Filter>src\Core\Networking\Win32</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Core\PCH.cpp">
|
|
||||||
<Filter>src\Core</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Engine\Debug\MemoryDebugger.cpp">
|
|
||||||
<Filter>src\Engine\Debug</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Engine\Engine.cpp">
|
|
||||||
<Filter>src\Engine</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Graphics\D3D12\AgilitySDK\d3dx12\d3dx12_property_format_table.cpp">
|
|
||||||
<Filter>src\Graphics\D3D12\AgilitySDK\d3dx12</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Graphics\D3D12\D3D12Buffer.cpp">
|
|
||||||
<Filter>src\Graphics\D3D12</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Graphics\D3D12\D3D12CommandList.cpp">
|
|
||||||
<Filter>src\Graphics\D3D12</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Graphics\D3D12\D3D12Common.cpp">
|
|
||||||
<Filter>src\Graphics\D3D12</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Graphics\D3D12\D3D12DescriptorHeap.cpp">
|
|
||||||
<Filter>src\Graphics\D3D12</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Graphics\D3D12\D3D12GraphicsDevice.cpp">
|
|
||||||
<Filter>src\Graphics\D3D12</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Graphics\D3D12\D3D12GraphicsPipeline.cpp">
|
|
||||||
<Filter>src\Graphics\D3D12</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Graphics\D3D12\D3D12InternalTests.cpp">
|
|
||||||
<Filter>src\Graphics\D3D12</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Graphics\D3D12\D3D12RenderPass.cpp">
|
|
||||||
<Filter>src\Graphics\D3D12</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Graphics\D3D12\D3D12Shader.cpp">
|
|
||||||
<Filter>src\Graphics\D3D12</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Graphics\D3D12\D3D12SwapChain.cpp">
|
|
||||||
<Filter>src\Graphics\D3D12</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Graphics\D3D12\D3D12Synchronization.cpp">
|
|
||||||
<Filter>src\Graphics\D3D12</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Graphics\D3D12\D3D12Texture.cpp">
|
|
||||||
<Filter>src\Graphics\D3D12</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Graphics\D3D12\D3D12Utils.cpp">
|
|
||||||
<Filter>src\Graphics\D3D12</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Graphics\DebugDisplayRenderer.cpp">
|
|
||||||
<Filter>src\Graphics</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Graphics\Graphics.cpp">
|
|
||||||
<Filter>src\Graphics</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Graphics\ImGuiRenderer.cpp">
|
|
||||||
<Filter>src\Graphics</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Graphics\Mesh.cpp">
|
|
||||||
<Filter>src\Graphics</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Graphics\MeshRenderer.cpp">
|
|
||||||
<Filter>src\Graphics</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\Graphics\SkyboxRenderer.cpp">
|
|
||||||
<Filter>src\Graphics</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\UnitTest\Container\VectorUnitTest.cpp">
|
|
||||||
<Filter>src\UnitTest\Container</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="src\UnitTest\RunUnitTests.cpp">
|
|
||||||
<Filter>src\UnitTest</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClInclude Include="include\Core\Application\ApplicationManager.h">
|
|
||||||
<Filter>include\Core\Application</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\Application\IApplication.h">
|
|
||||||
<Filter>include\Core\Application</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\Common\CoreTypes.h">
|
|
||||||
<Filter>include\Core\Common</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\Common\CoreUtils.h">
|
|
||||||
<Filter>include\Core\Common</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\Common\CRC32.h">
|
|
||||||
<Filter>include\Core\Common</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\Common\EnumUtils.h">
|
|
||||||
<Filter>include\Core\Common</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\Common\NonNullPtr.h">
|
|
||||||
<Filter>include\Core\Common</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\Common\String.h">
|
|
||||||
<Filter>include\Core\Common</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\Container\Vector.h">
|
|
||||||
<Filter>include\Core\Container</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\HAL\Display\Display.h">
|
|
||||||
<Filter>include\Core\HAL\Display</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\HAL\DynLib\DynamicLibrary.h">
|
|
||||||
<Filter>include\Core\HAL\DynLib</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\HAL\Event\SystemEvent.h">
|
|
||||||
<Filter>include\Core\HAL\Event</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\HAL\Filesystem\Filesystem.h">
|
|
||||||
<Filter>include\Core\HAL\Filesystem</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\HAL\IO\IOStream.h">
|
|
||||||
<Filter>include\Core\HAL\IO</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\HAL\Keyboard\Keyboard.h">
|
|
||||||
<Filter>include\Core\HAL\Keyboard</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\HAL\Keyboard\KeyCode.h">
|
|
||||||
<Filter>include\Core\HAL\Keyboard</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\HAL\Keyboard\ScanCode.h">
|
|
||||||
<Filter>include\Core\HAL\Keyboard</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\HAL\Mouse\Mouse.h">
|
|
||||||
<Filter>include\Core\HAL\Mouse</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\HAL\OS\OS.h">
|
|
||||||
<Filter>include\Core\HAL\OS</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\HotReload\HotReload.h">
|
|
||||||
<Filter>include\Core\HotReload</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\ImGui\ImGuiService.h">
|
|
||||||
<Filter>include\Core\ImGui</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\ImGui\ImGuiTests.h">
|
|
||||||
<Filter>include\Core\ImGui</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\JulietInit.h">
|
|
||||||
<Filter>include\Core</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\Logging\LogManager.h">
|
|
||||||
<Filter>include\Core\Logging</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\Logging\LogTypes.h">
|
|
||||||
<Filter>include\Core\Logging</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\Main.h">
|
|
||||||
<Filter>include\Core</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\Math\MathUtils.h">
|
|
||||||
<Filter>include\Core\Math</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\Math\Matrix.h">
|
|
||||||
<Filter>include\Core\Math</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\Math\Shape.h">
|
|
||||||
<Filter>include\Core\Math</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\Math\Vector.h">
|
|
||||||
<Filter>include\Core\Math</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\Memory\Allocator.h">
|
|
||||||
<Filter>include\Core\Memory</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\Memory\MemoryArena.h">
|
|
||||||
<Filter>include\Core\Memory</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\Memory\MemoryArenaDebug.h">
|
|
||||||
<Filter>include\Core\Memory</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\Memory\ScratchArena.h">
|
|
||||||
<Filter>include\Core\Memory</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\Memory\Utils.h">
|
|
||||||
<Filter>include\Core\Memory</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\Networking\IPAddress.h">
|
|
||||||
<Filter>include\Core\Networking</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\Networking\NetworkPacket.h">
|
|
||||||
<Filter>include\Core\Networking</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\Networking\Socket.h">
|
|
||||||
<Filter>include\Core\Networking</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\Networking\SocketHandle.h">
|
|
||||||
<Filter>include\Core\Networking</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\Networking\TcpListener.h">
|
|
||||||
<Filter>include\Core\Networking</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\Networking\TcpSocket.h">
|
|
||||||
<Filter>include\Core\Networking</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\PCH.h">
|
|
||||||
<Filter>include\Core</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\Thread\Mutex.h">
|
|
||||||
<Filter>include\Core\Thread</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Core\Thread\Thread.h">
|
|
||||||
<Filter>include\Core\Thread</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Engine\Class.h">
|
|
||||||
<Filter>include\Engine</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Engine\Debug\MemoryDebugger.h">
|
|
||||||
<Filter>include\Engine\Debug</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Engine\Engine.h">
|
|
||||||
<Filter>include\Engine</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Graphics\Camera.h">
|
|
||||||
<Filter>include\Graphics</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Graphics\Colors.h">
|
|
||||||
<Filter>include\Graphics</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Graphics\DebugDisplay.h">
|
|
||||||
<Filter>include\Graphics</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Graphics\Graphics.h">
|
|
||||||
<Filter>include\Graphics</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Graphics\GraphicsBuffer.h">
|
|
||||||
<Filter>include\Graphics</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Graphics\GraphicsConfig.h">
|
|
||||||
<Filter>include\Graphics</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Graphics\GraphicsPipeline.h">
|
|
||||||
<Filter>include\Graphics</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Graphics\ImGuiRenderer.h">
|
|
||||||
<Filter>include\Graphics</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Graphics\Lighting.h">
|
|
||||||
<Filter>include\Graphics</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Graphics\Mesh.h">
|
|
||||||
<Filter>include\Graphics</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Graphics\MeshRenderer.h">
|
|
||||||
<Filter>include\Graphics</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Graphics\PushConstants.h">
|
|
||||||
<Filter>include\Graphics</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Graphics\RenderPass.h">
|
|
||||||
<Filter>include\Graphics</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Graphics\Shader.h">
|
|
||||||
<Filter>include\Graphics</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Graphics\SkyboxRenderer.h">
|
|
||||||
<Filter>include\Graphics</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Graphics\Texture.h">
|
|
||||||
<Filter>include\Graphics</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Graphics\VertexData.h">
|
|
||||||
<Filter>include\Graphics</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\Juliet.h">
|
|
||||||
<Filter>include</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Core\HAL\Display\DisplayDevice.h">
|
|
||||||
<Filter>src\Core\HAL\Display</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Core\HAL\Display\Display_Private.h">
|
|
||||||
<Filter>src\Core\HAL\Display</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Core\HAL\Display\Win32\Win32DisplayEvent.h">
|
|
||||||
<Filter>src\Core\HAL\Display\Win32</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Core\HAL\Display\Win32\Win32Window.h">
|
|
||||||
<Filter>src\Core\HAL\Display\Win32</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Core\HAL\Display\Window.h">
|
|
||||||
<Filter>src\Core\HAL\Display</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Core\HAL\Event\KeyboardMapping.h">
|
|
||||||
<Filter>src\Core\HAL\Event</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Core\HAL\Event\Keyboard_Private.h">
|
|
||||||
<Filter>src\Core\HAL\Event</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Core\HAL\Event\Mouse_Private.h">
|
|
||||||
<Filter>src\Core\HAL\Event</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Core\HAL\Event\Win32ScanCode.h">
|
|
||||||
<Filter>src\Core\HAL\Event</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Core\HAL\Event\WindowEvent.h">
|
|
||||||
<Filter>src\Core\HAL\Event</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Core\HAL\Filesystem\Filesystem_Platform.h">
|
|
||||||
<Filter>src\Core\HAL\Filesystem</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Core\HAL\Filesystem\Filesystem_Private.h">
|
|
||||||
<Filter>src\Core\HAL\Filesystem</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Core\HAL\IO\IOStream_Private.h">
|
|
||||||
<Filter>src\Core\HAL\IO</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Core\HAL\OS\OS_Private.h">
|
|
||||||
<Filter>src\Core\HAL\OS</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Core\HAL\Win32.h">
|
|
||||||
<Filter>src\Core\HAL</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Core\Math\Math_Private.h">
|
|
||||||
<Filter>src\Core\Math</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Core\Networking\SocketPlatformImpl.h">
|
|
||||||
<Filter>src\Core\Networking</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\d3d12.h">
|
|
||||||
<Filter>src\Graphics\D3D12\AgilitySDK</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\d3d12compatibility.h">
|
|
||||||
<Filter>src\Graphics\D3D12\AgilitySDK</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\d3d12sdklayers.h">
|
|
||||||
<Filter>src\Graphics\D3D12\AgilitySDK</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\d3d12shader.h">
|
|
||||||
<Filter>src\Graphics\D3D12\AgilitySDK</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\d3d12video.h">
|
|
||||||
<Filter>src\Graphics\D3D12\AgilitySDK</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\d3dcommon.h">
|
|
||||||
<Filter>src\Graphics\D3D12\AgilitySDK</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\d3dx12\d3dx12.h">
|
|
||||||
<Filter>src\Graphics\D3D12\AgilitySDK\d3dx12</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\d3dx12\d3dx12_barriers.h">
|
|
||||||
<Filter>src\Graphics\D3D12\AgilitySDK\d3dx12</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\d3dx12\d3dx12_check_feature_support.h">
|
|
||||||
<Filter>src\Graphics\D3D12\AgilitySDK\d3dx12</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\d3dx12\d3dx12_core.h">
|
|
||||||
<Filter>src\Graphics\D3D12\AgilitySDK\d3dx12</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\d3dx12\d3dx12_default.h">
|
|
||||||
<Filter>src\Graphics\D3D12\AgilitySDK\d3dx12</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\d3dx12\d3dx12_pipeline_state_stream.h">
|
|
||||||
<Filter>src\Graphics\D3D12\AgilitySDK\d3dx12</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\d3dx12\d3dx12_property_format_table.h">
|
|
||||||
<Filter>src\Graphics\D3D12\AgilitySDK\d3dx12</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\d3dx12\d3dx12_render_pass.h">
|
|
||||||
<Filter>src\Graphics\D3D12\AgilitySDK\d3dx12</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\d3dx12\d3dx12_resource_helpers.h">
|
|
||||||
<Filter>src\Graphics\D3D12\AgilitySDK\d3dx12</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\d3dx12\d3dx12_root_signature.h">
|
|
||||||
<Filter>src\Graphics\D3D12\AgilitySDK\d3dx12</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\d3dx12\d3dx12_state_object.h">
|
|
||||||
<Filter>src\Graphics\D3D12\AgilitySDK\d3dx12</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\dxgiformat.h">
|
|
||||||
<Filter>src\Graphics\D3D12\AgilitySDK</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\D3D12Buffer.h">
|
|
||||||
<Filter>src\Graphics\D3D12</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\D3D12CommandList.h">
|
|
||||||
<Filter>src\Graphics\D3D12</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\D3D12Common.h">
|
|
||||||
<Filter>src\Graphics\D3D12</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\D3D12DescriptorHeap.h">
|
|
||||||
<Filter>src\Graphics\D3D12</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\D3D12GraphicsDevice.h">
|
|
||||||
<Filter>src\Graphics\D3D12</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\D3D12GraphicsPipeline.h">
|
|
||||||
<Filter>src\Graphics\D3D12</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\D3D12Includes.h">
|
|
||||||
<Filter>src\Graphics\D3D12</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\D3D12InternalTests.h">
|
|
||||||
<Filter>src\Graphics\D3D12</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\D3D12RenderPass.h">
|
|
||||||
<Filter>src\Graphics\D3D12</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\D3D12Shader.h">
|
|
||||||
<Filter>src\Graphics\D3D12</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\D3D12SwapChain.h">
|
|
||||||
<Filter>src\Graphics\D3D12</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\D3D12Synchronization.h">
|
|
||||||
<Filter>src\Graphics\D3D12</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\D3D12Texture.h">
|
|
||||||
<Filter>src\Graphics\D3D12</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\D3D12Utils.h">
|
|
||||||
<Filter>src\Graphics\D3D12</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Graphics\GraphicsDevice.h">
|
|
||||||
<Filter>src\Graphics</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\UnitTest\VectorTestHelper.h">
|
|
||||||
<Filter>src\UnitTest</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="src\Graphics\D3D12\AgilitySDK\D3D12TokenizedProgramFormat.hpp">
|
|
||||||
<Filter>src\Graphics\D3D12\AgilitySDK</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// Safe Casts
|
||||||
|
|
||||||
|
internal uint16 safe_cast_uint16(uint32 val)
|
||||||
|
{
|
||||||
|
AssertAlways(val <= max_uint16);
|
||||||
|
uint16 result = (uint16)val;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal uint32 safe_cast_uint32(uint64 val)
|
||||||
|
{
|
||||||
|
AssertAlways(val <= max_uint32);
|
||||||
|
uint32 result = (uint32)val;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal int32 safe_cast_int32(int64 val)
|
||||||
|
{
|
||||||
|
AssertAlways(val <= max_int32);
|
||||||
|
int32 result = (int32)val;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Third party includes
|
||||||
|
|
||||||
|
#define STB_SPRINTF_IMPLEMENTATION
|
||||||
|
#define STB_SPRINTF_STATIC
|
||||||
|
#include "external/stb_sprintf/stb_sprintf.h"
|
||||||
@@ -0,0 +1,299 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Third party includes
|
||||||
|
|
||||||
|
#define STB_SPRINTF_DECORATE(name) juliet_##name
|
||||||
|
#define STB_SPRINTF_STATIC
|
||||||
|
#include "external/stb_sprintf/stb_sprintf.h"
|
||||||
|
|
||||||
|
#define internal static
|
||||||
|
#define global static
|
||||||
|
#define local_persist static
|
||||||
|
|
||||||
|
#if (COMPILER_CLANG && OS_WINDOWS) || COMPILER_MSVC
|
||||||
|
# pragma section(".rdata$", read)
|
||||||
|
# define read_only __declspec(allocate(".rdata$"))
|
||||||
|
#elif (COMPILER_CLANG && OS_LINUX)
|
||||||
|
# define read_only __attribute__((section(".rodata")))
|
||||||
|
#else
|
||||||
|
// Note: not possible with GCC
|
||||||
|
# define read_only
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if COMPILER_CLANG || COMPILER_GCC
|
||||||
|
# define thread_static __thread
|
||||||
|
#elif COMPILER_MSVC
|
||||||
|
# define thread_static __declspec(thread)
|
||||||
|
#else
|
||||||
|
# error thread_static not defined for this compiler
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Units
|
||||||
|
|
||||||
|
#define KB(d) ((uint64)(d) << 10)
|
||||||
|
#define MB(d) ((uint64)(d) << 20)
|
||||||
|
#define GB(d) ((uint64)(d) << 30)
|
||||||
|
#define Million(n) ((n) * 1000000)
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Branch predictor hints
|
||||||
|
|
||||||
|
#if defined(__clang__)
|
||||||
|
# define Expect(expr, val) __builtin_expect((expr), (val))
|
||||||
|
#else
|
||||||
|
# define Expect(expr, val) (expr)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define Likely(expr) Expect(expr, 1)
|
||||||
|
#define Unlikely(expr) Expect(expr, 0)
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Min Max Clamp
|
||||||
|
|
||||||
|
#define Min(A, B) (((A) < (B) ? (A) : (B)))
|
||||||
|
#define Max(A, B) (((A) > (B) ? (A) : (B)))
|
||||||
|
#define ClampTop(A, X) Min(A, X)
|
||||||
|
#define ClampBottom(X, B) Max(X, B)
|
||||||
|
#define Clamp(A, X, B) (((X) < (A)) ? (A) : ((X) > (B)) ? (B) : (X))
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Alignment
|
||||||
|
#if COMPILER_CLANG || COMPILER_GCC
|
||||||
|
# define AlignOf(Type) __alignof(Type)
|
||||||
|
#elif COMPILER_MSVC
|
||||||
|
# define AlignOf(Type) __alignof(Type)
|
||||||
|
#else
|
||||||
|
# error AlignOf not defined for this compiler
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// For loop helpers
|
||||||
|
|
||||||
|
#define DeferLoop(begin, end) for (int _i_ = ((begin), 0); !_i_; ++_i_, (end))
|
||||||
|
|
||||||
|
#define EachIndex(it, count) (uint64 it = 0; it < (count); ++it)
|
||||||
|
#define EachNode(it, Type, first) (Type* it = first; it != 0; it = it->next)
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Memory Helpers
|
||||||
|
|
||||||
|
#define MemoryCopy(dst, src, size) memmove((dst), (src), (size))
|
||||||
|
#define MemorySet(dst, src, size) memset((dst), (src), (size))
|
||||||
|
#define MemoryCompare(a, b, size) memcmp((a), (b), (size))
|
||||||
|
|
||||||
|
#define MemoryZero(ptr, size) memset((ptr), 0, (size))
|
||||||
|
#define MemoryZeroStruct(s) MemoryZero((s), sizeof(*(s)))
|
||||||
|
#define MemoryZeroArray(arr) MemoryZero((arr), sizeof(arr))
|
||||||
|
|
||||||
|
#define MemoryMatch(a, b, size) (MemoryCompare((a), (b), (size)) == 0)
|
||||||
|
#define MemoryMatchStruct(a, b) MemoryMatch((a), (b), sizeof(*(a)))
|
||||||
|
#define MemoryMatchArray(a, b) MemoryMatch((a), (b), sizeof(a))
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Asserts
|
||||||
|
|
||||||
|
#if COMPILER_CLANG || COMPILER_GCC
|
||||||
|
# define Trap() __builtin_trap()
|
||||||
|
#elif COMPILER_MSVC
|
||||||
|
# define Trap() __debugbreak()
|
||||||
|
#else
|
||||||
|
# error Unknown trap intrinsic for this compiler
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define AssertAlways(cond) \
|
||||||
|
do \
|
||||||
|
{ \
|
||||||
|
if (!(cond)) \
|
||||||
|
{ \
|
||||||
|
Trap(); \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
while (0)
|
||||||
|
#if BUILD_DEBUG
|
||||||
|
# define Assert(cond) AssertAlways(cond)
|
||||||
|
#else
|
||||||
|
# define Assert(cond) (void)(cond)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define StaticAssert(cond, ID) global uint8 GLUE(ID, __LINE__)[(cond) ? 1 : -1]
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Linked List macro
|
||||||
|
|
||||||
|
// Linked list macro helpers
|
||||||
|
#define CheckNil(nil, ptr) ((ptr) == 0 || (ptr) == nil)
|
||||||
|
#define SetNil(nil, ptr) ((ptr) = nil)
|
||||||
|
|
||||||
|
// Doubly-linked-lists
|
||||||
|
// clang-format off
|
||||||
|
#define DLL_insert_next_prev_zero(nil, first, last, ptr, node, next, previous) \
|
||||||
|
(CheckNil(nil, first) ? ((first) = (last) = (node), SetNil(nil, (node)->next), SetNil(nil, (node)->previous)) \
|
||||||
|
: CheckNil(nil, ptr) ? ((node)->next = (first), (first)->previous = (node), (first) = (node), SetNil(nil, (node)->previous)) \
|
||||||
|
: ((ptr) == (last)) ? ((last)->next = (node), (node )->previous = (last), (last ) = (node), SetNil(nil, (node)->next)) \
|
||||||
|
: (((CheckNil(nil, ptr) && CheckNil(nil, (ptr)->next)) ? (0) : ((ptr)->next->previous = (node))), \
|
||||||
|
((node)->next = (ptr)->next), ((ptr)->next = (node)), ((node)->previous = (ptr))))
|
||||||
|
#define DLL_push_back_next_prev_zero(nil, first, last, node, next, previous) \
|
||||||
|
DLL_insert_next_prev_zero(nil, first, last, last, node, next, previous)
|
||||||
|
#define DLL_remove_next_prev_zero(nil, first, last, node, next, previous) \
|
||||||
|
(((node) == (first) ? (first) = (node)->next : (0)), ((node) == (last) ? (last) = (last)->previous : (0)), \
|
||||||
|
(CheckNil(nil, (node)->previous) ? (0) : ((node)->previous->next = (node)->next)), \
|
||||||
|
(CheckNil(nil, (node)->next) ? (0) : ((node)->next->previous = (node)->previous)))
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
// Single link double head lists (= queues)
|
||||||
|
#define SLL_queue_push_next_zero(nil, first, last, node, next) \
|
||||||
|
(CheckNil(nil, first) ? ((first) = (last) = (node), SetNil(nil, (node)->next)) \
|
||||||
|
: ((last)->next = (node), (last) = (node), SetNil(nil, (node)->next)))
|
||||||
|
|
||||||
|
// Single (link & head) lists (= stacks)
|
||||||
|
#define SLL_stack_push_next(first, node, next) ((node)->next = (first), (first) = (node))
|
||||||
|
#define SLL_stack_pop_next(first, next) ((first) = (first)->next);
|
||||||
|
|
||||||
|
// Double linked list helpers
|
||||||
|
#define DLL_push_back_next_prev(first, last, node, next, previous) \
|
||||||
|
DLL_push_back_next_prev_zero(0, first, last, node, next, previous)
|
||||||
|
#define DLL_remove_next_prev(first, last, node, next, prev) DLL_remove_next_prev_zero(0, first, last, node, next, prev)
|
||||||
|
#define DLL_push_back(first, last, node) DLL_push_back_next_prev_zero(0, first, last, node, next, previous)
|
||||||
|
#define DLL_remove(first, last, node) DLL_remove_next_prev_zero(0, first, last, node, next, previous)
|
||||||
|
|
||||||
|
// Queue helpers
|
||||||
|
#define SLL_queue_push(first, last, node) SLL_queue_push_next_zero(0, first, last, node, next)
|
||||||
|
|
||||||
|
// Stack helpers
|
||||||
|
#define SLL_stack_push(first, node) SLL_stack_push_next(first, node, next)
|
||||||
|
#define SLL_stack_pop(first) SLL_stack_pop_next(first, next)
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Helpers
|
||||||
|
|
||||||
|
#define AlignPow2(x, align) (((x) + (align) - 1) & (~((align) - 1)))
|
||||||
|
|
||||||
|
#define Stringify_(S) #S
|
||||||
|
#define Stringify(S) Stringify_(S)
|
||||||
|
|
||||||
|
#define GLUE_(A, B) A##B
|
||||||
|
#define GLUE(A, B) GLUE_(A, B)
|
||||||
|
|
||||||
|
#define ArrayCount(arr) (sizeof(arr) / sizeof((arr)[0]))
|
||||||
|
|
||||||
|
#if ARCH_64BIT
|
||||||
|
# define IntFromPtr(ptr) ((uint64)(ptr))
|
||||||
|
#else
|
||||||
|
# error Missing pointer to integer cast for this architecture
|
||||||
|
#endif
|
||||||
|
#define PtrFromInt(i) (void*)(i)
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Core Types
|
||||||
|
|
||||||
|
typedef uint8_t uint8;
|
||||||
|
typedef uint16_t uint16;
|
||||||
|
typedef uint32_t uint32;
|
||||||
|
typedef uint64_t uint64;
|
||||||
|
typedef int8_t int8;
|
||||||
|
typedef int16_t int16;
|
||||||
|
typedef int32_t int32;
|
||||||
|
typedef int64_t int64;
|
||||||
|
typedef float float32;
|
||||||
|
typedef double float64;
|
||||||
|
|
||||||
|
typedef int8 bool8;
|
||||||
|
typedef int16 bool16;
|
||||||
|
typedef int32 bool32;
|
||||||
|
typedef int64 bool64;
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Core Constants
|
||||||
|
|
||||||
|
global uint8 max_uint8 = 0xFF;
|
||||||
|
global uint16 max_uint16 = 0xFFFF;
|
||||||
|
global uint32 max_uint32 = 0xFFFFFFFF;
|
||||||
|
global uint64 max_uint64 = 0xFFFFFFFFFFFFFFFFll;
|
||||||
|
|
||||||
|
global int8 max_int8 = (int8)0x7F;
|
||||||
|
global int16 max_int16 = (int16)0x7FFF;
|
||||||
|
global int32 max_int32 = (int32)0x7FFFFFFF;
|
||||||
|
global int64 max_int64 = (int64)0x7FFFFFFFFFFFFFFFll;
|
||||||
|
|
||||||
|
global const uint32 bitmask1 = 0x00000001;
|
||||||
|
global const uint32 bitmask2 = 0x00000003;
|
||||||
|
global const uint32 bitmask3 = 0x00000007;
|
||||||
|
global const uint32 bitmask4 = 0x0000000f;
|
||||||
|
global const uint32 bitmask5 = 0x0000001f;
|
||||||
|
global const uint32 bitmask6 = 0x0000003f;
|
||||||
|
global const uint32 bitmask7 = 0x0000007f;
|
||||||
|
global const uint32 bitmask8 = 0x000000ff;
|
||||||
|
global const uint32 bitmask9 = 0x000001ff;
|
||||||
|
global const uint32 bitmask10 = 0x000003ff;
|
||||||
|
global const uint32 bitmask11 = 0x000007ff;
|
||||||
|
global const uint32 bitmask12 = 0x00000fff;
|
||||||
|
global const uint32 bitmask13 = 0x00001fff;
|
||||||
|
global const uint32 bitmask14 = 0x00003fff;
|
||||||
|
global const uint32 bitmask15 = 0x00007fff;
|
||||||
|
global const uint32 bitmask16 = 0x0000ffff;
|
||||||
|
global const uint32 bitmask17 = 0x0001ffff;
|
||||||
|
global const uint32 bitmask18 = 0x0003ffff;
|
||||||
|
global const uint32 bitmask19 = 0x0007ffff;
|
||||||
|
global const uint32 bitmask20 = 0x000fffff;
|
||||||
|
global const uint32 bitmask21 = 0x001fffff;
|
||||||
|
global const uint32 bitmask22 = 0x003fffff;
|
||||||
|
global const uint32 bitmask23 = 0x007fffff;
|
||||||
|
global const uint32 bitmask24 = 0x00ffffff;
|
||||||
|
global const uint32 bitmask25 = 0x01ffffff;
|
||||||
|
global const uint32 bitmask26 = 0x03ffffff;
|
||||||
|
global const uint32 bitmask27 = 0x07ffffff;
|
||||||
|
global const uint32 bitmask28 = 0x0fffffff;
|
||||||
|
global const uint32 bitmask29 = 0x1fffffff;
|
||||||
|
global const uint32 bitmask30 = 0x3fffffff;
|
||||||
|
global const uint32 bitmask31 = 0x7fffffff;
|
||||||
|
global const uint32 bitmask32 = 0xffffffff;
|
||||||
|
|
||||||
|
global const uint32 bit1 = (1 << 0);
|
||||||
|
global const uint32 bit2 = (1 << 1);
|
||||||
|
global const uint32 bit3 = (1 << 2);
|
||||||
|
global const uint32 bit4 = (1 << 3);
|
||||||
|
global const uint32 bit5 = (1 << 4);
|
||||||
|
global const uint32 bit6 = (1 << 5);
|
||||||
|
global const uint32 bit7 = (1 << 6);
|
||||||
|
global const uint32 bit8 = (1 << 7);
|
||||||
|
global const uint32 bit9 = (1 << 8);
|
||||||
|
global const uint32 bit10 = (1 << 9);
|
||||||
|
global const uint32 bit11 = (1 << 10);
|
||||||
|
global const uint32 bit12 = (1 << 11);
|
||||||
|
global const uint32 bit13 = (1 << 12);
|
||||||
|
global const uint32 bit14 = (1 << 13);
|
||||||
|
global const uint32 bit15 = (1 << 14);
|
||||||
|
global const uint32 bit16 = (1 << 15);
|
||||||
|
global const uint32 bit17 = (1 << 16);
|
||||||
|
global const uint32 bit18 = (1 << 17);
|
||||||
|
global const uint32 bit19 = (1 << 18);
|
||||||
|
global const uint32 bit20 = (1 << 19);
|
||||||
|
global const uint32 bit21 = (1 << 20);
|
||||||
|
global const uint32 bit22 = (1 << 21);
|
||||||
|
global const uint32 bit23 = (1 << 22);
|
||||||
|
global const uint32 bit24 = (1 << 23);
|
||||||
|
global const uint32 bit25 = (1 << 24);
|
||||||
|
global const uint32 bit26 = (1 << 25);
|
||||||
|
global const uint32 bit27 = (1 << 26);
|
||||||
|
global const uint32 bit28 = (1 << 27);
|
||||||
|
global const uint32 bit29 = (1 << 28);
|
||||||
|
global const uint32 bit30 = (1 << 29);
|
||||||
|
global const uint32 bit31 = (1 << 30);
|
||||||
|
global const uint32 bit32 = (1 << 31);
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Safe Casts
|
||||||
|
|
||||||
|
internal uint16 safe_cast_uint16(uint32 val);
|
||||||
|
internal uint32 safe_cast_uint32(uint64 val);
|
||||||
|
internal int32 safe_cast_int32(int64 val);
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Time functions
|
||||||
|
|
||||||
|
internal uint64 now_time_us();
|
||||||
|
internal void sleep_ms(uint32 ms);
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// Arena Functions
|
||||||
|
|
||||||
|
#include "core/core.h"
|
||||||
|
internal Arena* arena_alloc_internal(ArenaParams* params)
|
||||||
|
{
|
||||||
|
uint64 committed = params->commit_size;
|
||||||
|
uint64 reserved = params->reserve_size;
|
||||||
|
|
||||||
|
// Initial block
|
||||||
|
void* base = NULL;
|
||||||
|
if (params->flags & ArenaFlag_LargePages)
|
||||||
|
{
|
||||||
|
uint64 page_size = get_system_info()->large_page_size;
|
||||||
|
reserved = AlignPow2(reserved, page_size);
|
||||||
|
committed = AlignPow2(committed, page_size);
|
||||||
|
|
||||||
|
base = reserve_memory_large(reserved);
|
||||||
|
commit_memory_large(base, committed);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
uint64 page_size = get_system_info()->page_size;
|
||||||
|
reserved = AlignPow2(reserved, page_size);
|
||||||
|
committed = AlignPow2(committed, page_size);
|
||||||
|
|
||||||
|
base = reserve_memory(reserved);
|
||||||
|
commit_memory(base, committed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Unlikely(base == 0))
|
||||||
|
{
|
||||||
|
abort_self(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Arena* arena = base;
|
||||||
|
arena->current = arena;
|
||||||
|
arena->commit_size = params->commit_size;
|
||||||
|
arena->reserve_size = params->reserve_size;
|
||||||
|
arena->base_position = 0;
|
||||||
|
arena->position = ARENA_HEADER_SIZE;
|
||||||
|
arena->committed = committed;
|
||||||
|
arena->reserved = reserved;
|
||||||
|
|
||||||
|
#if BUILD_DEBUG
|
||||||
|
arena->alloc_file = params->alloc_file;
|
||||||
|
arena->alloc_line = params->alloc_line;
|
||||||
|
arena->name = params->name;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return arena;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void arena_release(Arena* arena)
|
||||||
|
{
|
||||||
|
for (Arena *p = arena->current, *prev = 0; p != 0; p = prev)
|
||||||
|
{
|
||||||
|
prev = p->previous;
|
||||||
|
release_memory(p, p->reserved);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Arena Push/Pop core Functions
|
||||||
|
|
||||||
|
internal void* arena_push(Arena* arena, uint64 size, uint64 align, bool32 zero)
|
||||||
|
{
|
||||||
|
Arena* current = arena->current;
|
||||||
|
uint64 pos_pre = AlignPow2(current->position, align);
|
||||||
|
uint64 pos_post = pos_pre + size;
|
||||||
|
|
||||||
|
uint64 size_to_zero = 0;
|
||||||
|
if (zero)
|
||||||
|
{
|
||||||
|
size_to_zero = Min(current->committed, pos_post) - pos_pre;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current->reserved < pos_post && !(arena->flags & ArenaFlag_NoChain))
|
||||||
|
{
|
||||||
|
Arena* new_block = 0;
|
||||||
|
if (new_block == 0)
|
||||||
|
{
|
||||||
|
uint64 reserved_size = arena->reserve_size;
|
||||||
|
uint64 committed_size = arena->commit_size;
|
||||||
|
if (size + ARENA_HEADER_SIZE > reserved_size)
|
||||||
|
{
|
||||||
|
reserved_size = AlignPow2(size + ARENA_HEADER_SIZE, align);
|
||||||
|
committed_size = AlignPow2(size + ARENA_HEADER_SIZE, align);
|
||||||
|
}
|
||||||
|
new_block = arena_alloc(.reserve_size = reserved_size, .commit_size = committed_size, .flags = arena->flags,
|
||||||
|
|
||||||
|
#if BUILD_DEBUG
|
||||||
|
.alloc_file = arena->alloc_file, .alloc_line = arena->alloc_line
|
||||||
|
#endif
|
||||||
|
);
|
||||||
|
size_to_zero = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
size_to_zero = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_block->base_position = current->base_position + current->reserved;
|
||||||
|
SLL_stack_push_next(arena->current, new_block, previous);
|
||||||
|
|
||||||
|
current = new_block;
|
||||||
|
pos_pre = AlignPow2(current->position, align);
|
||||||
|
pos_post = pos_pre + size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// commit new pages if needed
|
||||||
|
if (current->committed < pos_post)
|
||||||
|
{
|
||||||
|
uint64 committed_past_aligned = pos_post + current->commit_size - 1;
|
||||||
|
committed_past_aligned -= committed_past_aligned % current->commit_size;
|
||||||
|
uint64 committed_past_clamped = ClampTop(committed_past_aligned, current->reserved);
|
||||||
|
uint64 committed_size = committed_past_clamped - current->committed;
|
||||||
|
uint8* committed_ptr = (uint8*)current + current->committed;
|
||||||
|
if (current->flags & ArenaFlag_LargePages)
|
||||||
|
{
|
||||||
|
commit_memory_large(committed_ptr, committed_size);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
commit_memory(committed_ptr, committed_size);
|
||||||
|
}
|
||||||
|
current->committed = committed_past_clamped;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* result = 0;
|
||||||
|
if (current->committed >= pos_post)
|
||||||
|
{
|
||||||
|
result = (uint8*)current + pos_pre;
|
||||||
|
current->position = pos_post;
|
||||||
|
MemoryZero(result, size_to_zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Unlikely(result == 0))
|
||||||
|
{
|
||||||
|
abort_self(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal uint64 arena_pos(Arena* arena)
|
||||||
|
{
|
||||||
|
Arena* current = arena->current;
|
||||||
|
uint64 position = current->base_position + current->position;
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void arena_pop_to(Arena* arena, uint64 position)
|
||||||
|
{
|
||||||
|
uint64 clamped_pos = ClampBottom(ARENA_HEADER_SIZE, position);
|
||||||
|
Arena* current = arena->current;
|
||||||
|
|
||||||
|
for (Arena* previous = 0; current->base_position >= clamped_pos; current = previous)
|
||||||
|
{
|
||||||
|
previous = current->previous;
|
||||||
|
release_memory(current, current->reserved);
|
||||||
|
}
|
||||||
|
arena->current = current;
|
||||||
|
uint64 new_position = clamped_pos - current->base_position;
|
||||||
|
AssertAlways(new_position <= current->position);
|
||||||
|
current->position = new_position;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void arena_clear(Arena* arena)
|
||||||
|
{
|
||||||
|
arena_pop_to(arena, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void arena_pop(Arena* arena, uint64 amount)
|
||||||
|
{
|
||||||
|
uint64 pos_old = arena_pos(arena);
|
||||||
|
Assert(pos_old > amount);
|
||||||
|
uint64 pos_new = pos_old - amount;
|
||||||
|
arena_pop_to(arena, pos_new);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Temp arena
|
||||||
|
internal TempArena temp_begin(Arena* arena)
|
||||||
|
{
|
||||||
|
uint64 position = arena_pos(arena);
|
||||||
|
TempArena temp = { arena, position };
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void temp_end(TempArena temp)
|
||||||
|
{
|
||||||
|
arena_pop_to(temp.arena, temp.position);
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define ARENA_HEADER_SIZE 128
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Arena Types
|
||||||
|
|
||||||
|
typedef uint64 ArenaFlags;
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
ArenaFlag_NoChain = (1 << 0),
|
||||||
|
ArenaFlag_LargePages = (1 << 1),
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct ArenaParams ArenaParams;
|
||||||
|
struct ArenaParams
|
||||||
|
{
|
||||||
|
ArenaFlags flags;
|
||||||
|
uint64 commit_size;
|
||||||
|
uint64 reserve_size;
|
||||||
|
char* alloc_file;
|
||||||
|
int alloc_line;
|
||||||
|
char* name;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct Arena Arena;
|
||||||
|
struct Arena
|
||||||
|
{
|
||||||
|
Arena* previous;
|
||||||
|
Arena* current;
|
||||||
|
ArenaFlags flags;
|
||||||
|
uint64 base_position;
|
||||||
|
uint64 position;
|
||||||
|
uint64 commit_size;
|
||||||
|
uint64 committed;
|
||||||
|
uint64 reserve_size;
|
||||||
|
uint64 reserved;
|
||||||
|
|
||||||
|
#if BUILD_DEBUG
|
||||||
|
char* alloc_file;
|
||||||
|
int alloc_line;
|
||||||
|
char* name;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
StaticAssert(sizeof(Arena) <= ARENA_HEADER_SIZE, arena_header_size_check);
|
||||||
|
|
||||||
|
typedef struct TempArena TempArena;
|
||||||
|
struct TempArena
|
||||||
|
{
|
||||||
|
Arena* arena;
|
||||||
|
uint64 position;
|
||||||
|
};
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Arena Functions
|
||||||
|
|
||||||
|
global uint64 arena_default_reserve_size = MB(64);
|
||||||
|
global uint64 arena_default_commit_size = KB(64);
|
||||||
|
|
||||||
|
internal Arena* arena_alloc_internal(ArenaParams* params);
|
||||||
|
#define arena_alloc(...) \
|
||||||
|
arena_alloc_internal(&(ArenaParams){ .commit_size = arena_default_commit_size, \
|
||||||
|
.reserve_size = arena_default_reserve_size, \
|
||||||
|
.alloc_file = __FILE__, \
|
||||||
|
.alloc_line = __LINE__, \
|
||||||
|
__VA_ARGS__ })
|
||||||
|
internal void arena_release(Arena* arena);
|
||||||
|
|
||||||
|
// Arena Push/Pop core Functions
|
||||||
|
|
||||||
|
internal void* arena_push(Arena* arena, uint64 size, uint64 align, bool32 zero);
|
||||||
|
internal uint64 arena_pos(Arena* arena);
|
||||||
|
internal void arena_pop_to(Arena* arena, uint64 position);
|
||||||
|
internal void arena_clear(Arena* arena);
|
||||||
|
internal void arena_pop(Arena* arena, uint64 amount);
|
||||||
|
|
||||||
|
// Temp arena
|
||||||
|
internal TempArena temp_begin(Arena* arena);
|
||||||
|
internal void temp_end(TempArena temp);
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Push Helper Macros
|
||||||
|
|
||||||
|
#define push_array_no_zero_aligned(arr, Type, count, align) \
|
||||||
|
(Type*)arena_push((arr), sizeof(Type) * (count), (align), (0))
|
||||||
|
#define push_array_aligned(arr, Type, count, align) (Type*)arena_push((arr), sizeof(Type) * (count), (align), (1))
|
||||||
|
#define push_array_no_zero(arr, Type, count) push_array_no_zero_aligned(arr, Type, count, Max(8, AlignOf(Type)))
|
||||||
|
#define push_array(arr, Type, count) push_array_aligned(arr, Type, count, Max(8, AlignOf(Type)))
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// ========================================================================== //
|
||||||
|
// Compiler defines | Clang
|
||||||
|
|
||||||
|
#if defined(__clang__)
|
||||||
|
|
||||||
|
# define COMPILER_CLANG 1
|
||||||
|
|
||||||
|
# if defined(_WIN32)
|
||||||
|
# define OS_WINDOWS 1
|
||||||
|
# else
|
||||||
|
# error Compiler / OS combo is not supported
|
||||||
|
# endif
|
||||||
|
|
||||||
|
# if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64)
|
||||||
|
# define ARCH_X64 1
|
||||||
|
# else
|
||||||
|
# error Architecture not supported
|
||||||
|
# endif
|
||||||
|
|
||||||
|
#elif defined(_MSC_VER)
|
||||||
|
|
||||||
|
# define COMPILER_MSVC 1
|
||||||
|
|
||||||
|
# if defined(_WIN32)
|
||||||
|
# define OS_WINDOWS 1
|
||||||
|
# else
|
||||||
|
# error Compiler / OS combo is not supported
|
||||||
|
# endif
|
||||||
|
|
||||||
|
# if defined(_M_AMD64)
|
||||||
|
# define ARCH_X64 1
|
||||||
|
# else
|
||||||
|
# error Architecture not supported
|
||||||
|
# endif
|
||||||
|
|
||||||
|
#else
|
||||||
|
# error Compiler not supported.
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ========================================================================== //
|
||||||
|
// Architecture
|
||||||
|
|
||||||
|
#if defined(ARCH_X64)
|
||||||
|
# define ARCH_64BIT 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ========================================================================== //
|
||||||
|
// Language
|
||||||
|
|
||||||
|
#if defined(__cplusplus)
|
||||||
|
# define LANG_CPP 1
|
||||||
|
#else
|
||||||
|
# define LANG_C 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ========================================================================== //
|
||||||
|
// Build Options
|
||||||
|
|
||||||
|
#if !defined(BUILD_DEBUG)
|
||||||
|
# define BUILD_DEBUG 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ========================================================================== //
|
||||||
|
// Zeroing unused Options
|
||||||
|
|
||||||
|
#if !defined(ARCH_X64)
|
||||||
|
# define ARCH_X64 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !defined(ARCH_X86)
|
||||||
|
# define ARCH_X86 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !defined(COMPILER_CLANG)
|
||||||
|
# define COMPILER_CLANG 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !defined(COMPILER_MSVC)
|
||||||
|
# define COMPILER_MSVC 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !defined(OS_WINDOWS)
|
||||||
|
# define OS_WINDOWS 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !defined(LANG_CPP)
|
||||||
|
# define LANG_CPP 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !defined(LANG_C)
|
||||||
|
# define LANG_C 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !ARCH_X64
|
||||||
|
# error Trying to build unsupported architecture. Only x64 is supported.
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
internal void entry_point();
|
||||||
|
|
||||||
|
internal void main_thread_core_entry_point(int argc, char** argv)
|
||||||
|
{
|
||||||
|
ThreadNameF("main_thread");
|
||||||
|
entry_point();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool32 update()
|
||||||
|
{
|
||||||
|
bool32 result = frame();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
internal void main_thread_core_entry_point(int argc, char** argv);
|
||||||
|
internal bool32 update();
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// Hash Functions
|
||||||
|
|
||||||
|
#pragma push_macro("__cpuid")
|
||||||
|
#undef __cpuid
|
||||||
|
#if !defined(XXH_IMPLEMENTATION)
|
||||||
|
# define XXH_INLINE_ALL
|
||||||
|
# define XXH_IMPLEMENTATION
|
||||||
|
# define XXH_STATIC_LINKING_ONLY
|
||||||
|
# include "external/xxHash/xxhash.h"
|
||||||
|
#endif
|
||||||
|
#pragma pop_macro("__cpuid")
|
||||||
|
|
||||||
|
#define HASH_SEED 4259
|
||||||
|
|
||||||
|
internal uint64 uint64_hash_from_string(String8 string)
|
||||||
|
{
|
||||||
|
uint64 result = uint64_hash_from_seed_string(HASH_SEED, string);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal uint64 uint64_hash_from_seed_string(uint64 seed, String8 string)
|
||||||
|
{
|
||||||
|
uint64 result = XXH3_64bits_withSeed(string.str, string.size, seed);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Hash Functions
|
||||||
|
|
||||||
|
internal uint64 uint64_hash_from_string(String8 string);
|
||||||
|
internal uint64 uint64_hash_from_seed_string(uint64 seed, String8 string);
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
// clang-format off
|
||||||
|
#include "core.c"
|
||||||
|
#include "core_memory.c"
|
||||||
|
#include "core_arena.c"
|
||||||
|
#include "core_math.c"
|
||||||
|
#include "core_string.c"
|
||||||
|
#include "core_hash.c"
|
||||||
|
#include "core_system.c"
|
||||||
|
#include "core_threads.c"
|
||||||
|
#include "core_thread_context.c"
|
||||||
|
#include "core_processes.c"
|
||||||
|
#include "core_markup.c"
|
||||||
|
#include "core_entry_point.c"
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
#if OS_WINDOWS
|
||||||
|
# include "win32/core/win32_core.c"
|
||||||
|
#elif
|
||||||
|
# error Unsupported OS
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
#include "core_defines.h"
|
||||||
|
|
||||||
|
#include "core.h"
|
||||||
|
#include "core_memory.h"
|
||||||
|
#include "core_processes.h"
|
||||||
|
#include "core_arena.h"
|
||||||
|
#include "core_math.h"
|
||||||
|
#include "core_string.h"
|
||||||
|
#include "core_hash.h"
|
||||||
|
#include "core_system.h"
|
||||||
|
#include "core_threads.h"
|
||||||
|
#include "core_thread_context.h"
|
||||||
|
#include "core_markup.h"
|
||||||
|
#include "core_entry_point.h"
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
#if OS_WINDOWS
|
||||||
|
# include "win32/core/win32_core.h"
|
||||||
|
#elif
|
||||||
|
# error Unsupported OS
|
||||||
|
#endif
|
||||||
File renamed without changes.
@@ -0,0 +1,3 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define ThreadNameF(...) (set_thread_namef(__VA_ARGS__))
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// Functions
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Vectors
|
||||||
|
|
||||||
|
internal Vector2f vector_2f(float32 x, float32 y)
|
||||||
|
{
|
||||||
|
Vector2f v = { x, y };
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Rectangles
|
||||||
|
|
||||||
|
internal Rect2f rect_2f(Vector2f min, Vector2f max)
|
||||||
|
{
|
||||||
|
Rect2f rect = { min, max };
|
||||||
|
return rect;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool32 contains_2f(Rect2f rect, Vector2f x)
|
||||||
|
{
|
||||||
|
bool32 cond = (rect.min.x <= x.x && x.x < rect.max.x && rect.min.y <= x.y && x.y < rect.max.y);
|
||||||
|
return cond;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Vector2f dimension_2f(Rect2f rect)
|
||||||
|
{
|
||||||
|
Vector2f dim = { ((rect.max.x > rect.min.x) ? (rect.max.x - rect.min.x) : 0),
|
||||||
|
((rect.max.y > rect.min.y) ? (rect.max.y - rect.min.y) : 0) };
|
||||||
|
return dim;
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Vector Types
|
||||||
|
|
||||||
|
typedef union Vector2f Vector2f;
|
||||||
|
union Vector2f
|
||||||
|
{
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
float32 x;
|
||||||
|
float32 y;
|
||||||
|
};
|
||||||
|
float32 v[2];
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef union Vector2i Vector2i;
|
||||||
|
union Vector2i
|
||||||
|
{
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
int32 x;
|
||||||
|
int32 y;
|
||||||
|
};
|
||||||
|
int32 v[2];
|
||||||
|
};
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Rect Types
|
||||||
|
|
||||||
|
typedef union Rect2f Rect2f;
|
||||||
|
union Rect2f
|
||||||
|
{
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
Vector2f min;
|
||||||
|
Vector2f max;
|
||||||
|
};
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
Vector2f p0;
|
||||||
|
Vector2f p1;
|
||||||
|
};
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
float32 x0;
|
||||||
|
float32 y0;
|
||||||
|
float32 x1;
|
||||||
|
float32 y1;
|
||||||
|
};
|
||||||
|
Vector2f v[2];
|
||||||
|
};
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Functions
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Vectors
|
||||||
|
|
||||||
|
#define vector2f(x, y) vector_2f((x), (y))
|
||||||
|
internal Vector2f vector_2f(float32 x, float32 y);
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Rectangles
|
||||||
|
|
||||||
|
#define rect2f(min, max) rect_2f((min), (max))
|
||||||
|
#define rect2fp(x, y, z, w) rect2f(vector2f((x), (y)), vector2f((z), (w)))
|
||||||
|
internal Rect2f rect_2f(Vector2f min, Vector2f max);
|
||||||
|
internal bool32 contains_2f(Rect2f rect, Vector2f x);
|
||||||
|
internal Vector2f dimension_2f(Rect2f rect);
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
// OS DEPEND see os/core/os_core.c
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
internal void* reserve_memory(uint64 size);
|
||||||
|
internal bool32 commit_memory(void* ptr, uint64 size);
|
||||||
|
internal void* reserve_memory_large(uint64 size);
|
||||||
|
internal bool32 commit_memory_large(void* ptr, uint64 size);
|
||||||
|
internal void decommit_memory(void* ptr, uint64 size);
|
||||||
|
internal void release_memory(void* ptr, uint64 size);
|
||||||
File renamed without changes.
@@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Aborting (os dependent see os/core/os_core.c)
|
||||||
|
|
||||||
|
internal void abort_self(uint64 exit_code);
|
||||||
@@ -0,0 +1,395 @@
|
|||||||
|
#ifndef MEM_STATIC
|
||||||
|
# define MEM_STATIC
|
||||||
|
# define MEM_API static inline
|
||||||
|
# include "external/memfun/memfun.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// String conversions
|
||||||
|
|
||||||
|
internal bool32 char_is_upper(uint8 c)
|
||||||
|
{
|
||||||
|
return ('A' <= c && c <= 'Z');
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool32 char_is_lower(uint8 c)
|
||||||
|
{
|
||||||
|
return ('a' <= c && c <= 'z');
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool32 char_is_slash(uint8 c)
|
||||||
|
{
|
||||||
|
return (c == '/' || c == '\\');
|
||||||
|
}
|
||||||
|
|
||||||
|
internal uint8 lower_from_char(uint8 c)
|
||||||
|
{
|
||||||
|
if (char_is_upper(c))
|
||||||
|
{
|
||||||
|
c += ('a' - 'A');
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal uint8 upper_from_char(uint8 c)
|
||||||
|
{
|
||||||
|
if (char_is_lower(c))
|
||||||
|
{
|
||||||
|
c += ('A' - 'a');
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal uint8 correct_slash_from_char(uint8 c)
|
||||||
|
{
|
||||||
|
if (char_is_slash(c))
|
||||||
|
{
|
||||||
|
c = '/';
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================== //
|
||||||
|
// String Length
|
||||||
|
|
||||||
|
internal uint64 cstring8_length(uint8* cstring)
|
||||||
|
{
|
||||||
|
uint64 length = 0;
|
||||||
|
if (cstring)
|
||||||
|
{
|
||||||
|
uint8* p = cstring;
|
||||||
|
for (; *p != '\0'; ++p)
|
||||||
|
;
|
||||||
|
length = (uint64)(p - cstring);
|
||||||
|
}
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal uint64 cstring16_length(uint16* cstring)
|
||||||
|
{
|
||||||
|
uint64 length = 0;
|
||||||
|
if (cstring)
|
||||||
|
{
|
||||||
|
uint16* p = cstring;
|
||||||
|
for (; *p != '\0'; ++p)
|
||||||
|
;
|
||||||
|
length = (uint64)(p - cstring);
|
||||||
|
}
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal uint64 cstring32_length(uint32* cstring)
|
||||||
|
{
|
||||||
|
uint64 length = 0;
|
||||||
|
if (cstring)
|
||||||
|
{
|
||||||
|
uint32* p = cstring;
|
||||||
|
for (; *p != '\0'; ++p)
|
||||||
|
;
|
||||||
|
length = (uint64)(p - cstring);
|
||||||
|
}
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================== //
|
||||||
|
// String Constructor
|
||||||
|
|
||||||
|
internal String8 str8(uint8* str, uint64 size)
|
||||||
|
{
|
||||||
|
String8 result = { str, size };
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal String8 str8_zero()
|
||||||
|
{
|
||||||
|
String8 result = { 0 };
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal String16 str16(uint16* str, uint64 size)
|
||||||
|
{
|
||||||
|
String16 result = { str, size };
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal String16 str16_zero()
|
||||||
|
{
|
||||||
|
String16 result = { 0 };
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal String8 str8_cstring(const char* cstring)
|
||||||
|
{
|
||||||
|
String8 result = { (uint8*)cstring, cstring8_length((uint8*)cstring) };
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal String16 str16_cstring(uint16* cstring)
|
||||||
|
{
|
||||||
|
String16 result = { (uint16*)cstring, cstring16_length((uint16*)cstring) };
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal String32 str32_cstring(uint32* cstring)
|
||||||
|
{
|
||||||
|
String32 result = { (uint32*)cstring, cstring32_length((uint32*)cstring) };
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// String Matching
|
||||||
|
|
||||||
|
internal bool32 str8_match(String8 a, String8 b, StringMatchFlags flags)
|
||||||
|
{
|
||||||
|
bool32 result = 0;
|
||||||
|
if (a.size == b.size && flags == 0)
|
||||||
|
{
|
||||||
|
result = MemIsEqual(a.str, b.str, b.size);
|
||||||
|
}
|
||||||
|
else if (a.size == b.size && flags == StringMatchFlag_CaseInsensitive)
|
||||||
|
{
|
||||||
|
result = MemCompareI(a.str, b.str, a.size) == 0;
|
||||||
|
}
|
||||||
|
else if (a.size == b.size || (flags * StringMatchFlag_RightSideSloppy))
|
||||||
|
{
|
||||||
|
bool32 case_insensitive = (flags & StringMatchFlag_CaseInsensitive);
|
||||||
|
bool32 slash_insensitive = (flags & StringMatchFlag_SlashInsensitive);
|
||||||
|
uint64 size = Min(a.size, b.size);
|
||||||
|
result = 1;
|
||||||
|
for EachIndex(i, size)
|
||||||
|
{
|
||||||
|
uint8 at = a.str[i];
|
||||||
|
uint8 bt = b.str[i];
|
||||||
|
if (case_insensitive)
|
||||||
|
{
|
||||||
|
at = upper_from_char(at);
|
||||||
|
bt = upper_from_char(bt);
|
||||||
|
}
|
||||||
|
if (slash_insensitive)
|
||||||
|
{
|
||||||
|
at = correct_slash_from_char(at);
|
||||||
|
bt = correct_slash_from_char(bt);
|
||||||
|
}
|
||||||
|
if (at != bt)
|
||||||
|
{
|
||||||
|
result = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// String Formatting & Copying
|
||||||
|
|
||||||
|
internal String8 str8_copy(Arena* arena, String8 in)
|
||||||
|
{
|
||||||
|
String8 str;
|
||||||
|
str.size = in.size;
|
||||||
|
str.str = push_array_no_zero(arena, uint8, str.size + 1);
|
||||||
|
MemoryCopy(str.str, in.str, in.size);
|
||||||
|
str.str[str.size] = '\0';
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal String8 str8fv(Arena* arena, char* fmt, va_list args)
|
||||||
|
{
|
||||||
|
va_list args2;
|
||||||
|
va_copy(args2, args);
|
||||||
|
uint32 needed_bytes = juliet_vsnprintf(0, 0, fmt, args) + 1;
|
||||||
|
String8 result = { 0 };
|
||||||
|
result.str = push_array_no_zero(arena, uint8, needed_bytes);
|
||||||
|
result.size = juliet_vsnprintf((char*)result.str, needed_bytes, fmt, args2);
|
||||||
|
result.str[result.size] = '\0';
|
||||||
|
va_end(args2);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Unicode String Encode/Decode
|
||||||
|
|
||||||
|
read_only global uint8 utf8_class[32] = {
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 3, 3, 4, 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
internal UnicodeDecode utf8_decode(uint8* str, uint64 max)
|
||||||
|
{
|
||||||
|
UnicodeDecode result = { 1, max_uint32 };
|
||||||
|
uint8 byte = str[0];
|
||||||
|
uint8 byte_class = utf8_class[byte >> 3];
|
||||||
|
switch (byte_class)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
{
|
||||||
|
result.codepoint = byte;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 2:
|
||||||
|
{
|
||||||
|
if (1 < max)
|
||||||
|
{
|
||||||
|
uint8 cont_byte = str[1];
|
||||||
|
if (utf8_class[cont_byte >> 3] == 0)
|
||||||
|
{
|
||||||
|
result.codepoint = (byte & bitmask5) << 6;
|
||||||
|
result.codepoint |= (cont_byte & bitmask6);
|
||||||
|
result.increment = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 3:
|
||||||
|
{
|
||||||
|
if (2 < max)
|
||||||
|
{
|
||||||
|
uint8 cont_byte[2] = { str[1], str[2] };
|
||||||
|
if (utf8_class[cont_byte[0] >> 3] == 0 && utf8_class[cont_byte[1] >> 3] == 0)
|
||||||
|
{
|
||||||
|
result.codepoint = (byte & bitmask4) << 12;
|
||||||
|
result.codepoint |= ((cont_byte[0] & bitmask6) << 6);
|
||||||
|
result.codepoint |= (cont_byte[1] & bitmask6);
|
||||||
|
result.increment = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 4:
|
||||||
|
{
|
||||||
|
if (3 < max)
|
||||||
|
{
|
||||||
|
uint8 cont_byte[3] = { str[1], str[2], str[3] };
|
||||||
|
if (utf8_class[cont_byte[0] >> 3] == 0 && utf8_class[cont_byte[1] >> 3] == 0 && utf8_class[cont_byte[2] >> 3] == 0)
|
||||||
|
{
|
||||||
|
result.codepoint = (byte & bitmask3) << 18;
|
||||||
|
result.codepoint |= ((cont_byte[0] & bitmask6 << 12));
|
||||||
|
result.codepoint |= ((cont_byte[1] & bitmask6) << 6);
|
||||||
|
result.codepoint |= (cont_byte[2] & bitmask6);
|
||||||
|
result.increment = 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal UnicodeDecode utf16_decode(uint16* str, uint64 max)
|
||||||
|
{
|
||||||
|
UnicodeDecode result = { 1, max_uint32 };
|
||||||
|
result.codepoint = str[0];
|
||||||
|
result.increment = 1;
|
||||||
|
if (max > 1 && 0xD800 <= str[0] && str[0] < 0xDC00 && 0xDC00 <= str[1] && str[1] < 0xE000)
|
||||||
|
{
|
||||||
|
result.codepoint = ((str[0] - 0xD800) << 10) | ((str[1] - 0xDC00) + 0x10000);
|
||||||
|
result.increment = 2;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal uint32 utf8_encode(uint8* str, uint32 codepoint)
|
||||||
|
{
|
||||||
|
uint32 increment = 0;
|
||||||
|
if (codepoint <= 0x7f)
|
||||||
|
{
|
||||||
|
str[0] = (uint8)codepoint;
|
||||||
|
increment = 1;
|
||||||
|
}
|
||||||
|
else if (codepoint <= 0x7FF)
|
||||||
|
{
|
||||||
|
str[0] = (bitmask2 << 6) | ((codepoint >> 6) & bitmask5);
|
||||||
|
str[1] = bit8 | (codepoint & bitmask6);
|
||||||
|
increment = 2;
|
||||||
|
}
|
||||||
|
else if (codepoint <= 0xFFFF)
|
||||||
|
{
|
||||||
|
str[0] = (bitmask3 << 5) | ((codepoint >> 12) & bitmask4);
|
||||||
|
str[1] = bit8 | ((codepoint >> 6) & bitmask6);
|
||||||
|
str[2] = bit8 | (codepoint & bitmask6);
|
||||||
|
increment = 3;
|
||||||
|
}
|
||||||
|
else if (codepoint <= 0x10FFFF)
|
||||||
|
{
|
||||||
|
str[0] = (bitmask4 << 4) | ((codepoint >> 18) & bitmask3);
|
||||||
|
str[1] = bit8 | ((codepoint >> 12) & bitmask6);
|
||||||
|
str[2] = bit8 | (codepoint >> 6 & bitmask6);
|
||||||
|
str[3] = bit8 | (codepoint & bitmask6);
|
||||||
|
increment = 4;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
str[0] = '?';
|
||||||
|
increment = 1;
|
||||||
|
}
|
||||||
|
return increment;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal uint32 utf16_encode(uint16* str, uint32 codepoint)
|
||||||
|
{
|
||||||
|
uint32 increment = 1;
|
||||||
|
if (codepoint == max_uint32)
|
||||||
|
{
|
||||||
|
str[0] = (uint16)'?';
|
||||||
|
}
|
||||||
|
else if (codepoint < 0x10000)
|
||||||
|
{
|
||||||
|
str[0] = (uint16)codepoint;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
uint32 v = codepoint - 0x10000;
|
||||||
|
str[0] = safe_cast_uint16(0xD800 + (v >> 10));
|
||||||
|
str[1] = safe_cast_uint16(0xDC00 + (v & bitmask10));
|
||||||
|
increment = 2;
|
||||||
|
}
|
||||||
|
return increment;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Unicode String Conversions
|
||||||
|
|
||||||
|
internal String8 str8_from_16(Arena* arena, String16 in)
|
||||||
|
{
|
||||||
|
String8 result = str8_zero();
|
||||||
|
if (in.size)
|
||||||
|
{
|
||||||
|
uint64 capacity = in.size * 3;
|
||||||
|
uint8* str = push_array_no_zero(arena, uint8, capacity + 1);
|
||||||
|
uint16* ptr = in.str;
|
||||||
|
uint16* opl = ptr + in.size;
|
||||||
|
uint64 size = 0;
|
||||||
|
UnicodeDecode consume;
|
||||||
|
for (; ptr < opl; ptr += consume.increment)
|
||||||
|
{
|
||||||
|
consume = utf16_decode(ptr, opl - ptr);
|
||||||
|
size += utf8_encode(str + size, consume.codepoint);
|
||||||
|
}
|
||||||
|
str[size] = '\0';
|
||||||
|
arena_pop(arena, (capacity - size));
|
||||||
|
result = str8(str, size);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal String16 str16_from_8(Arena* arena, String8 in)
|
||||||
|
{
|
||||||
|
String16 result = str16_zero();
|
||||||
|
if (in.size)
|
||||||
|
{
|
||||||
|
uint64 capacity = in.size * 2;
|
||||||
|
uint16* str = push_array_no_zero(arena, uint16, capacity + 1);
|
||||||
|
uint8* ptr = in.str;
|
||||||
|
uint8* opl = ptr + in.size;
|
||||||
|
uint64 size = 0;
|
||||||
|
UnicodeDecode consume;
|
||||||
|
for (; ptr < opl; ptr += consume.increment)
|
||||||
|
{
|
||||||
|
consume = utf8_decode(ptr, opl - ptr);
|
||||||
|
size += utf16_encode(str + size, consume.codepoint);
|
||||||
|
}
|
||||||
|
str[size] = '\0';
|
||||||
|
arena_pop(arena, (capacity - size) * 2);
|
||||||
|
result = str16(str, size);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// String Types
|
||||||
|
|
||||||
|
typedef struct String8 String8;
|
||||||
|
struct String8
|
||||||
|
{
|
||||||
|
uint8* str;
|
||||||
|
uint64 size;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct String16 String16;
|
||||||
|
struct String16
|
||||||
|
{
|
||||||
|
uint16* str;
|
||||||
|
uint64 size;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct String32 String32;
|
||||||
|
struct String32
|
||||||
|
{
|
||||||
|
uint32* str;
|
||||||
|
uint64 size;
|
||||||
|
};
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// String Matching Types
|
||||||
|
|
||||||
|
typedef uint32 StringMatchFlags;
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
StringMatchFlag_CaseInsensitive = (1 << 0),
|
||||||
|
StringMatchFlag_RightSideSloppy = (1 << 1),
|
||||||
|
StringMatchFlag_SlashInsensitive = (1 << 2),
|
||||||
|
};
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Unicode decoding Types
|
||||||
|
|
||||||
|
typedef struct UnicodeDecode UnicodeDecode;
|
||||||
|
struct UnicodeDecode
|
||||||
|
{
|
||||||
|
uint32 increment;
|
||||||
|
uint32 codepoint;
|
||||||
|
};
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// String conversions
|
||||||
|
|
||||||
|
internal bool32 char_is_upper(uint8 c);
|
||||||
|
internal bool32 char_is_lower(uint8 c);
|
||||||
|
internal bool32 char_is_slash(uint8 c);
|
||||||
|
internal uint8 lower_from_char(uint8 c);
|
||||||
|
internal uint8 upper_from_char(uint8 c);
|
||||||
|
internal uint8 correct_slash_from_char(uint8 c);
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// String Length
|
||||||
|
|
||||||
|
internal uint64 cstring8_length(uint8* cstring);
|
||||||
|
internal uint64 cstring16_length(uint16* cstring);
|
||||||
|
internal uint64 cstring32_length(uint32* cstring);
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// String Constructor
|
||||||
|
|
||||||
|
#define str8_literal(S) str8((uint8*)(S), sizeof(S) - 1)
|
||||||
|
|
||||||
|
#define str8_struct(S) str8((uint8*)(S), sizeof(*(S)))
|
||||||
|
|
||||||
|
internal String8 str8(uint8* str, uint64 size);
|
||||||
|
internal String8 str8_zero();
|
||||||
|
internal String16 str16(uint16* str, uint64 size);
|
||||||
|
internal String16 str16_zero();
|
||||||
|
internal String8 str8_cstring(const char* cstring);
|
||||||
|
internal String16 str16_cstring(uint16* cstring);
|
||||||
|
internal String32 str32_cstring(uint32* cstring);
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// String Matching
|
||||||
|
|
||||||
|
internal bool32 str8_match(String8 a, String8 b, StringMatchFlags flags);
|
||||||
|
#define str8_matchi(a, b) str8_match(a, b, StringMatchFlag_CaseInsensitive)
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// String Formatting & Copying
|
||||||
|
|
||||||
|
internal String8 str8_copy(Arena* arena, String8 in);
|
||||||
|
internal String8 str8fv(Arena* arena, char* fmt, va_list args);
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Unicode String Encode/Decode
|
||||||
|
|
||||||
|
internal UnicodeDecode utf8_decode(uint8* str, uint64 max);
|
||||||
|
internal UnicodeDecode utf16_decode(uint16* str, uint64 max);
|
||||||
|
internal uint32 utf8_encode(uint8* str, uint32 codepoint);
|
||||||
|
internal uint32 utf16_encode(uint16* str, uint32 codepoint);
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Unicode String Conversions
|
||||||
|
|
||||||
|
internal String8 str8_from_16(Arena* arena, String16 in);
|
||||||
|
internal String16 str16_from_8(Arena* arena, String8 in);
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
// OS DEPEND see os/core/os_core.c
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
typedef struct SystemInfo SystemInfo;
|
||||||
|
struct SystemInfo
|
||||||
|
{
|
||||||
|
uint64 page_size;
|
||||||
|
uint64 large_page_size;
|
||||||
|
String8 machine_name;
|
||||||
|
};
|
||||||
|
|
||||||
|
internal SystemInfo* get_system_info();
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// Globals
|
||||||
|
|
||||||
|
thread_static TCTX* tctx_thread_local;
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Thread Context Functions
|
||||||
|
|
||||||
|
// Thread Context allocation
|
||||||
|
internal TCTX* tctx_alloc()
|
||||||
|
{
|
||||||
|
#if BUILD_DEBUG
|
||||||
|
thread_static static char name[2][1024];
|
||||||
|
juliet_snprintf(name[0], sizeof(name[0]), "Scratch/0[TID:%u]", tid());
|
||||||
|
juliet_snprintf(name[1], sizeof(name[1]), "Scratch/1[TID:%u]", tid());
|
||||||
|
Arena* arena_0 = arena_alloc(.name = name[0]);
|
||||||
|
Arena* arena_1 = arena_alloc(.name = name[1]);
|
||||||
|
#else
|
||||||
|
Arena* arena_0 = arena_alloc();
|
||||||
|
Arena* arena_1 = arena_alloc();
|
||||||
|
#endif
|
||||||
|
TCTX* tctx = push_array(arena_0, TCTX, 1);
|
||||||
|
tctx->arenas[0] = arena_0;
|
||||||
|
tctx->arenas[1] = arena_1;
|
||||||
|
return tctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void tctx_release(TCTX* tctx)
|
||||||
|
{
|
||||||
|
arena_release(tctx->arenas[1]);
|
||||||
|
arena_release(tctx->arenas[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void tctx_select(TCTX* tctx)
|
||||||
|
{
|
||||||
|
tctx_thread_local = tctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal TCTX* tctx_selected()
|
||||||
|
{
|
||||||
|
return tctx_thread_local;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Arena* tctx_get_scratch(Arena** conflicts, uint64 count)
|
||||||
|
{
|
||||||
|
TCTX* tctx = tctx_selected();
|
||||||
|
Arena* result = 0;
|
||||||
|
Arena** arena_ptr = tctx->arenas;
|
||||||
|
for (uint64 i = 0; i < ArrayCount(tctx->arenas); ++i, ++arena_ptr)
|
||||||
|
{
|
||||||
|
Arena** conflict_ptr = conflicts;
|
||||||
|
bool32 has_conflict = 0;
|
||||||
|
for (uint64 j = 0; j < count; ++j, ++conflict_ptr)
|
||||||
|
{
|
||||||
|
if (*arena_ptr == *conflict_ptr)
|
||||||
|
{
|
||||||
|
has_conflict = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!has_conflict)
|
||||||
|
{
|
||||||
|
result = *arena_ptr;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define scratch_begin(conflicts, count) temp_begin(tctx_get_scratch((conflicts), (count)))
|
||||||
|
#define scratch_end(scratch) temp_end(scratch)
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
typedef struct TCTX TCTX;
|
||||||
|
struct TCTX
|
||||||
|
{
|
||||||
|
// Scratch arenas
|
||||||
|
Arena* arenas[2];
|
||||||
|
|
||||||
|
uint8 thread_name[32];
|
||||||
|
uint64 thread_name_size;
|
||||||
|
|
||||||
|
char* file_name;
|
||||||
|
uint64 line_number;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern thread_static TCTX* tctx_thread_local;
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Thread Context Functions
|
||||||
|
|
||||||
|
// Thread Context allocation
|
||||||
|
internal TCTX* tctx_alloc();
|
||||||
|
internal void tctx_release(TCTX* tctx);
|
||||||
|
internal void tctx_select(TCTX* tctx);
|
||||||
|
internal TCTX* tctx_selected();
|
||||||
|
|
||||||
|
internal Arena* tctx_get_scratch(Arena** conflicts, uint64 count);
|
||||||
|
#define scratch_begin(conflicts, count) temp_begin(tctx_get_scratch((conflicts), (count)))
|
||||||
|
#define scratch_end(scratch) temp_end(scratch)
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Thread Info Helpers
|
||||||
|
internal void set_thread_name(String8 name)
|
||||||
|
{
|
||||||
|
set_platform_thread_name(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void set_thread_namef(char* fmt, ...)
|
||||||
|
{
|
||||||
|
TempArena scratch = scratch_begin(0, 0);
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
String8 string = str8fv(scratch.arena, fmt, args);
|
||||||
|
set_thread_name(string);
|
||||||
|
va_end(args);
|
||||||
|
scratch_end(scratch);
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Synchronization primitives types
|
||||||
|
|
||||||
|
typedef struct RWMutex RWMutex;
|
||||||
|
struct RWMutex
|
||||||
|
{
|
||||||
|
uint64 handle[1];
|
||||||
|
};
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Thread Info Helpers
|
||||||
|
internal void set_thread_name(String8 name);
|
||||||
|
internal void set_thread_namef(char* fmt, ...);
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Thread Info
|
||||||
|
|
||||||
|
internal uint32 tid();
|
||||||
|
internal void set_platform_thread_name(String8 name);
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Synchronization primitive functions
|
||||||
|
|
||||||
|
// Read Write Mutexes functions
|
||||||
|
|
||||||
|
internal RWMutex rw_mutex_alloc();
|
||||||
|
internal void rw_mutex_release(RWMutex mutex);
|
||||||
|
internal void rw_mutex_take(RWMutex mutex, bool32 write_mode);
|
||||||
|
internal void rw_mutex_drop(RWMutex mutex, bool32 write_mode);
|
||||||
|
#define rw_mutex_take_r(m) rw_mutex_take((m), (0))
|
||||||
|
#define rw_mutex_take_w(m) rw_mutex_take((m), (1))
|
||||||
|
#define rw_mutex_drop_r(m) rw_mutex_drop((m), (0))
|
||||||
|
#define rw_mutex_drop_w(m) rw_mutex_drop((m), (1))
|
||||||
|
|
||||||
|
// Scope macros
|
||||||
|
#define MutexScopeR(mutex) DeferLoop(rw_mutex_take_r(mutex), rw_mutex_drop_r(mutex))
|
||||||
|
#define MutexScopeW(mutex) DeferLoop(rw_mutex_take_w(mutex), rw_mutex_drop_w(mutex))
|
||||||
Vendored
+4285
File diff suppressed because it is too large.
Load diff
+1
Submodule Juliet/external/stb_sprintf added at 2ffb3379e8.
+1
Submodule Juliet/external/xxHash added at c87183a77d.
@@ -1,13 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <Core/Application/IApplication.h>
|
|
||||||
#include <Core/Common/CoreTypes.h>
|
|
||||||
#include <Juliet.h>
|
|
||||||
|
|
||||||
namespace Juliet
|
|
||||||
{
|
|
||||||
enum class JulietInit_Flags : uint8;
|
|
||||||
|
|
||||||
struct Arena;
|
|
||||||
extern JULIET_API void StartApplication(IApplication& app, JulietInit_Flags flags);
|
|
||||||
} // namespace Juliet
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <Core/Common/NonNullPtr.h>
|
|
||||||
|
|
||||||
namespace Juliet
|
|
||||||
{
|
|
||||||
struct RenderPass;
|
|
||||||
struct CommandList;
|
|
||||||
struct Texture;
|
|
||||||
struct ColorTargetInfo;
|
|
||||||
struct DepthStencilTargetInfo;
|
|
||||||
struct Arena;
|
|
||||||
|
|
||||||
class IApplication
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual ~IApplication() = default;
|
|
||||||
virtual void Init(NonNullPtr<Arena> arena) = 0;
|
|
||||||
virtual void Shutdown() = 0;
|
|
||||||
virtual void Update() = 0;
|
|
||||||
virtual bool IsRunning() = 0;
|
|
||||||
|
|
||||||
// Accessors for Engine Systems
|
|
||||||
virtual struct Window* GetPlatformWindow() = 0;
|
|
||||||
virtual struct GraphicsDevice* GetGraphicsDevice() = 0;
|
|
||||||
|
|
||||||
// Render Lifecycle (Engine-Managed Render Loop)
|
|
||||||
virtual void OnPreRender(CommandList* cmd) = 0;
|
|
||||||
virtual void OnRender(RenderPass* pass, CommandList* cmd) = 0;
|
|
||||||
virtual ColorTargetInfo GetColorTargetInfo(Texture* swapchainTexture) = 0;
|
|
||||||
virtual DepthStencilTargetInfo* GetDepthTargetInfo() = 0;
|
|
||||||
};
|
|
||||||
} // namespace Juliet
|
|
||||||
@@ -1,58 +1,60 @@
|
|||||||
#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
|
// From https://web.mit.edu/freebsd/head/sys/libkern/crc32.c
|
||||||
|
|
||||||
namespace Juliet
|
global const uint32 g_crc32_tab[256] = {
|
||||||
{
|
0x00000000u, 0x77073096u, 0xee0e612cu, 0x990951bau, 0x076dc419u, 0x706af48fu, 0xe963a535u, 0x9e6495a3u, 0x0edb8832u,
|
||||||
namespace details
|
0x79dcb8a4u, 0xe0d5e91eu, 0x97d2d988u, 0x09b64c2bu, 0x7eb17cbdu, 0xe7b82d07u, 0x90bf1d91u, 0x1db71064u, 0x6ab020f2u,
|
||||||
{
|
0xf3b97148u, 0x84be41deu, 0x1adad47du, 0x6ddde4ebu, 0xf4d4b551u, 0x83d385c7u, 0x136c9856u, 0x646ba8c0u, 0xfd62f97au,
|
||||||
constexpr uint32_t crc32_tab[] = {
|
0x8a65c9ecu, 0x14015c4fu, 0x63066cd9u, 0xfa0f3d63u, 0x8d080df5u, 0x3b6e20c8u, 0x4c69105eu, 0xd56041e4u, 0xa2677172u,
|
||||||
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832,
|
0x3c03e4d1u, 0x4b04d447u, 0xd20d85fdu, 0xa50ab56bu, 0x35b5a8fau, 0x42b2986cu, 0xdbbbc9d6u, 0xacbcf940u, 0x32d86ce3u,
|
||||||
0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
|
0x45df5c75u, 0xdcd60dcfu, 0xabd13d59u, 0x26d930acu, 0x51de003au, 0xc8d75180u, 0xbfd06116u, 0x21b4f4b5u, 0x56b3c423u,
|
||||||
0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a,
|
0xcfba9599u, 0xb8bda50fu, 0x2802b89eu, 0x5f058808u, 0xc60cd9b2u, 0xb10be924u, 0x2f6f7c87u, 0x58684c11u, 0xc1611dabu,
|
||||||
0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
|
0xb6662d3du, 0x76dc4190u, 0x01db7106u, 0x98d220bcu, 0xefd5102au, 0x71b18589u, 0x06b6b51fu, 0x9fbfe4a5u, 0xe8b8d433u,
|
||||||
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3,
|
0x7807c9a2u, 0x0f00f934u, 0x9609a88eu, 0xe10e9818u, 0x7f6a0dbbu, 0x086d3d2du, 0x91646c97u, 0xe6635c01u, 0x6b6b51f4u,
|
||||||
0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
|
0x1c6c6162u, 0x856530d8u, 0xf262004eu, 0x6c0695edu, 0x1b01a57bu, 0x8208f4c1u, 0xf50fc457u, 0x65b0d9c6u, 0x12b7e950u,
|
||||||
0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab,
|
0x8bbeb8eau, 0xfcb9887cu, 0x62dd1ddfu, 0x15da2d49u, 0x8cd37cf3u, 0xfbd44c65u, 0x4db26158u, 0x3ab551ceu, 0xa3bc0074u,
|
||||||
0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
|
0xd4bb30e2u, 0x4adfa541u, 0x3dd895d7u, 0xa4d1c46du, 0xd3d6f4fbu, 0x4369e96au, 0x346ed9fcu, 0xad678846u, 0xda60b8d0u,
|
||||||
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4,
|
0x44042d73u, 0x33031de5u, 0xaa0a4c5fu, 0xdd0d7cc9u, 0x5005713cu, 0x270241aau, 0xbe0b1010u, 0xc90c2086u, 0x5768b525u,
|
||||||
0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
|
0x206f85b3u, 0xb966d409u, 0xce61e49fu, 0x5edef90eu, 0x29d9c998u, 0xb0d09822u, 0xc7d7a8b4u, 0x59b33d17u, 0x2eb40d81u,
|
||||||
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074,
|
0xb7bd5c3bu, 0xc0ba6cadu, 0xedb88320u, 0x9abfb3b6u, 0x03b6e20cu, 0x74b1d29au, 0xead54739u, 0x9dd277afu, 0x04db2615u,
|
||||||
0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
|
0x73dc1683u, 0xe3630b12u, 0x94643b84u, 0x0d6d6a3eu, 0x7a6a5aa8u, 0xe40ecf0bu, 0x9309ff9du, 0x0a00ae27u, 0x7d079eb1u,
|
||||||
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525,
|
0xf00f9344u, 0x8708a3d2u, 0x1e01f268u, 0x6906c2feu, 0xf762575du, 0x806567cbu, 0x196c3671u, 0x6e6b06e7u, 0xfed41b76u,
|
||||||
0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
|
0x89d32be0u, 0x10da7a5au, 0x67dd4accu, 0xf9b9df6fu, 0x8ebeeff9u, 0x17b7be43u, 0x60b08ed5u, 0xd6d6a3e8u, 0xa1d1937eu,
|
||||||
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615,
|
0x38d8c2c4u, 0x4fdff252u, 0xd1bb67f1u, 0xa6bc5767u, 0x3fb506ddu, 0x48b2364bu, 0xd80d2bdau, 0xaf0a1b4cu, 0x36034af6u,
|
||||||
0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
|
0x41047a60u, 0xdf60efc3u, 0xa867df55u, 0x316e8eefu, 0x4669be79u, 0xcb61b38cu, 0xbc66831au, 0x256fd2a0u, 0x5268e236u,
|
||||||
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76,
|
0xcc0c7795u, 0xbb0b4703u, 0x220216b9u, 0x5505262fu, 0xc5ba3bbeu, 0xb2bd0b28u, 0x2bb45a92u, 0x5cb36a04u, 0xc2d7ffa7u,
|
||||||
0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
|
0xb5d0cf31u, 0x2cd99e8bu, 0x5bdeae1du, 0x9b64c2b0u, 0xec63f226u, 0x756aa39cu, 0x026d930au, 0x9c0906a9u, 0xeb0e363fu,
|
||||||
0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6,
|
0x72076785u, 0x05005713u, 0x95bf4a82u, 0xe2b87a14u, 0x7bb12baeu, 0x0cb61b38u, 0x92d28e9bu, 0xe5d5be0du, 0x7cdcefb7u,
|
||||||
0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
|
0x0bdbdf21u, 0x86d3d2d4u, 0xf1d4e242u, 0x68ddb3f8u, 0x1fda836eu, 0x81be16cdu, 0xf6b9265bu, 0x6fb077e1u, 0x18b74777u,
|
||||||
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7,
|
0x88085ae6u, 0xff0f6a70u, 0x66063bcau, 0x11010b5cu, 0x8f659effu, 0xf862ae69u, 0x616bffd3u, 0x166ccf45u, 0xa00ae278u,
|
||||||
0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
|
0xd70dd2eeu, 0x4e048354u, 0x3903b3c2u, 0xa7672661u, 0xd06016f7u, 0x4969474du, 0x3e6e77dbu, 0xaed16a4au, 0xd9d65adcu,
|
||||||
0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7,
|
0x40df0b66u, 0x37d83bf0u, 0xa9bcae53u, 0xdebb9ec5u, 0x47b2cf7fu, 0x30b5ffe9u, 0xbdbdf21cu, 0xcabac28au, 0x53b39330u,
|
||||||
0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
|
0x24b4a3a6u, 0xbad03605u, 0xcdd70693u, 0x54de5729u, 0x23d967bfu, 0xb3667a2eu, 0xc4614ab8u, 0x5d681b02u, 0x2a6f2b94u,
|
||||||
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278,
|
0xb40bbe37u, 0xc30c8ea1u, 0x5a05df1bu, 0x2d02ef8du
|
||||||
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)
|
static inline uint32 crc32_bytes(const char* str, size_t length)
|
||||||
{
|
{
|
||||||
|
Assert(str != NULL && length > 0);
|
||||||
const char* p = str;
|
const char* p = str;
|
||||||
uint32_t crc = ~0U;
|
uint32 crc = ~0U;
|
||||||
while (length--)
|
while (length--)
|
||||||
{
|
{
|
||||||
crc = details::crc32_tab[(crc ^ static_cast<uint8>(*p++)) & 0xFF] ^ (crc >> 8);
|
crc = g_crc32_tab[(crc ^ (uint8)(*p++)) & 0xFFu] ^ (crc >> 8);
|
||||||
}
|
}
|
||||||
return crc ^ ~0U;
|
return crc ^ ~0U;
|
||||||
}
|
}
|
||||||
|
|
||||||
consteval uint32 operator""_crc32(const char* str, size_t length)
|
static inline uint32 crc32_str(String str)
|
||||||
{
|
{
|
||||||
return crc32(str, length);
|
return crc32_bytes(str.Str, str.Size);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Juliet
|
static inline uint32 crc32_cstr(const char* str)
|
||||||
|
{
|
||||||
|
return crc32_bytes(str, StringLengthCStr(str));
|
||||||
|
}
|
||||||
@@ -1,45 +1,68 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
using uint8 = uint8_t;
|
#include <limits.h>
|
||||||
using uint16 = uint16_t;
|
#include <stddef.h>
|
||||||
using uint32 = uint32_t;
|
#include <stdint.h>
|
||||||
using uint64 = uint64_t;
|
|
||||||
|
|
||||||
using int8 = int8_t;
|
typedef uint8_t uint8;
|
||||||
using int16 = int16_t;
|
typedef uint16_t uint16;
|
||||||
using int32 = int32_t;
|
typedef uint32_t uint32;
|
||||||
using int64 = int64_t;
|
typedef uint64_t uint64;
|
||||||
|
|
||||||
using Byte = std::byte;
|
typedef int8_t int8;
|
||||||
|
typedef int16_t int16;
|
||||||
|
typedef int32_t int32;
|
||||||
|
typedef int64_t int64;
|
||||||
|
|
||||||
using size_t = std::size_t;
|
typedef uint8 bool8;
|
||||||
using index_t = size_t;
|
typedef uint16 bool16;
|
||||||
|
typedef uint32 bool32;
|
||||||
|
typedef uint64 bool64;
|
||||||
|
|
||||||
|
#ifndef __cplusplus
|
||||||
|
#ifndef true
|
||||||
|
#define true 1
|
||||||
|
#endif
|
||||||
|
#ifndef false
|
||||||
|
#define false 0
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef uint8 Byte;
|
||||||
|
typedef size_t index_t;
|
||||||
|
|
||||||
|
typedef struct ByteBuffer ByteBuffer;
|
||||||
struct ByteBuffer
|
struct ByteBuffer
|
||||||
{
|
{
|
||||||
Byte* Data;
|
Byte* Data;
|
||||||
size_t Size;
|
size_t Size;
|
||||||
};
|
};
|
||||||
|
|
||||||
using ptrdiff_t = decltype(static_cast<int*>(nullptr) - static_cast<int*>(nullptr));
|
typedef void (*FunctionPtr)(void);
|
||||||
|
|
||||||
using FunctionPtr = auto (*)(void) -> void;
|
#define uint8Max ((uint8)0xFFu)
|
||||||
|
#define uint16Max ((uint16)0xFFFFu)
|
||||||
|
#define uint32Max ((uint32)0xFFFFFFFFu)
|
||||||
|
#define uint64Max ((uint64)0xFFFFFFFFFFFFFFFFull)
|
||||||
|
|
||||||
// Limits
|
#define int8Max ((int8)0x7F)
|
||||||
template <typename Type>
|
#define int16Max ((int16)0x7FFF)
|
||||||
consteval Type MaxValueOf()
|
#define int32Max ((int32)0x7FFFFFFF)
|
||||||
{
|
#define int64Max ((int64)0x7FFFFFFFFFFFFFFFll)
|
||||||
return std::numeric_limits<Type>::max();
|
|
||||||
}
|
|
||||||
constexpr uint8 uint8Max = MaxValueOf<uint8>();
|
|
||||||
constexpr uint16 uint16Max = MaxValueOf<uint16>();
|
|
||||||
constexpr uint32 uint32Max = MaxValueOf<uint32>();
|
|
||||||
constexpr uint64 uint64Max = MaxValueOf<uint64>();
|
|
||||||
|
|
||||||
constexpr int8 int8Max = MaxValueOf<int8>();
|
#define indexMax ((index_t)SIZE_MAX)
|
||||||
constexpr int16 int16Max = MaxValueOf<int16>();
|
|
||||||
constexpr int32 int32Max = MaxValueOf<int32>();
|
|
||||||
constexpr int64 int64Max = MaxValueOf<int64>();
|
|
||||||
|
|
||||||
#define Kilobytes(value) value * 1024
|
#define uint8Min ((uint8)0u)
|
||||||
#define Megabytes(value) Kilobytes(value) * 1024
|
#define uint16Min ((uint16)0u)
|
||||||
|
#define uint32Min ((uint32)0u)
|
||||||
|
#define uint64Min ((uint64)0ull)
|
||||||
|
|
||||||
|
#define int8Min ((int8)(-128))
|
||||||
|
#define int16Min ((int16)(-32768))
|
||||||
|
#define int32Min ((int32)(-2147483647 - 1))
|
||||||
|
#define int64Min ((int64)(-9223372036854775807ll - 1ll))
|
||||||
|
|
||||||
|
#define indexMin ((index_t)0u)
|
||||||
|
|
||||||
|
#define Kilobytes(value) ((uint64)(value) * 1024ull)
|
||||||
|
#define Megabytes(value) (Kilobytes(value) * 1024ull)
|
||||||
@@ -1,18 +1,20 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Core/Common/CoreTypes.h>
|
|
||||||
#include <Juliet.h>
|
#include <Juliet.h>
|
||||||
|
#include <Core/Common/CoreTypes.h>
|
||||||
namespace Juliet
|
|
||||||
{
|
|
||||||
|
|
||||||
#define global static
|
#define global static
|
||||||
|
#define internal static
|
||||||
|
|
||||||
// 1. Stringify helpers
|
#define Glue_(A, B) A##B
|
||||||
|
#define Glue(A, B) Glue_(A, B)
|
||||||
|
#define StaticAssert(C, ID) global uint8 Glue(ID, __LINE__)[(C) ? 1 : -1]
|
||||||
|
|
||||||
|
// 1. Stringify helpers
|
||||||
#define JULIET_STR(x) #x
|
#define JULIET_STR(x) #x
|
||||||
#define JULIET_TOSTRING(x) JULIET_STR(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__)
|
#if defined(__clang__) || defined(__GNUC__)
|
||||||
#define JULIET_PRAGMA(x) _Pragma(#x)
|
#define JULIET_PRAGMA(x) _Pragma(#x)
|
||||||
#define JULIET_SUPPRESS_MSVC(id)
|
#define JULIET_SUPPRESS_MSVC(id)
|
||||||
@@ -27,7 +29,7 @@ namespace Juliet
|
|||||||
#define JULIET_SUPPRESS_CLANG(str)
|
#define JULIET_SUPPRESS_CLANG(str)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// 3. The Agnostic "Push/Pop"
|
// 3. The Agnostic "Push/Pop"
|
||||||
#if defined(__clang__)
|
#if defined(__clang__)
|
||||||
#define JULIET_WARNING_PUSH JULIET_PRAGMA(clang diagnostic push)
|
#define JULIET_WARNING_PUSH JULIET_PRAGMA(clang diagnostic push)
|
||||||
#define JULIET_WARNING_POP JULIET_PRAGMA(clang diagnostic pop)
|
#define JULIET_WARNING_POP JULIET_PRAGMA(clang diagnostic pop)
|
||||||
@@ -40,10 +42,8 @@ namespace Juliet
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(_MSC_VER)
|
#if defined(_MSC_VER)
|
||||||
// MSVC specific intrinsic
|
|
||||||
#define JULIET_PLATFORM_BREAK() (__nop(), __debugbreak())
|
#define JULIET_PLATFORM_BREAK() (__nop(), __debugbreak())
|
||||||
#elif defined(__clang__) || defined(__GNUC__)
|
#elif defined(__clang__) || defined(__GNUC__)
|
||||||
// Clang/GCC specific intrinsic
|
|
||||||
#define JULIET_PLATFORM_BREAK() __builtin_trap()
|
#define JULIET_PLATFORM_BREAK() __builtin_trap()
|
||||||
#else
|
#else
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
@@ -52,18 +52,14 @@ namespace Juliet
|
|||||||
|
|
||||||
#if JULIET_DEBUG
|
#if JULIET_DEBUG
|
||||||
#define JULIET_ASSERT_INTERNAL(expression, message) \
|
#define JULIET_ASSERT_INTERNAL(expression, message) \
|
||||||
JULIET_WARNING_PUSH \
|
do \
|
||||||
JULIET_SUPPRESS_CLANG("-Wextra-semi-stmt") \
|
|
||||||
JULIET_SUPPRESS_MSVC(4127) \
|
|
||||||
JULIET_SUPPRESS_MSVC(4548) \
|
|
||||||
{ \
|
{ \
|
||||||
if (!(expression)) [[unlikely]] \
|
if (!(expression)) \
|
||||||
{ \
|
{ \
|
||||||
Juliet::JulietAssert(#expression, message); \
|
JulietAssert(#expression, message, __FILE__, __func__, (uint32)__LINE__, 0); \
|
||||||
} \
|
} \
|
||||||
} \
|
} \
|
||||||
JULIET_WARNING_POP \
|
while (0)
|
||||||
static_assert(true, "")
|
|
||||||
|
|
||||||
#define AssertHR(hr_expression, message) \
|
#define AssertHR(hr_expression, message) \
|
||||||
do \
|
do \
|
||||||
@@ -71,7 +67,7 @@ namespace Juliet
|
|||||||
long hr_val = (hr_expression); \
|
long hr_val = (hr_expression); \
|
||||||
if (hr_val < 0) \
|
if (hr_val < 0) \
|
||||||
{ \
|
{ \
|
||||||
Juliet::JulietAssert(#hr_expression, message, std::source_location::current(), hr_val); \
|
JulietAssert(#hr_expression, message, __FILE__, __func__, (uint32)__LINE__, hr_val); \
|
||||||
} \
|
} \
|
||||||
} \
|
} \
|
||||||
while (0)
|
while (0)
|
||||||
@@ -83,7 +79,7 @@ namespace Juliet
|
|||||||
#define Unimplemented() \
|
#define Unimplemented() \
|
||||||
do \
|
do \
|
||||||
{ \
|
{ \
|
||||||
Juliet::JulietAssert("UNIMPLEMENTED", "This code path is not yet functional."); \
|
JulietAssert("UNIMPLEMENTED", "This code path is not yet functional.", __FILE__, __func__, (uint32)__LINE__, 0); \
|
||||||
} \
|
} \
|
||||||
while (0)
|
while (0)
|
||||||
|
|
||||||
@@ -93,85 +89,60 @@ namespace Juliet
|
|||||||
#define Unimplemented() ((void)0)
|
#define Unimplemented() ((void)0)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
JULIET_API extern void JulietAssert(const char* expression, const char* message,
|
JULIET_API extern void JulietAssert(const char* expression, const char* message, const char* file, const char* function,
|
||||||
std::source_location location = std::source_location::current(), long handleResult = 0);
|
uint32 line, long handle_result);
|
||||||
|
|
||||||
#define ZeroStruct(structInstance) ZeroSize(sizeof(structInstance), &(structInstance))
|
#define ZeroStruct(structInstance) ZeroSize(sizeof(structInstance), &(structInstance))
|
||||||
#define ZeroArray(array) ZeroSize(sizeof((array)), (array))
|
#define ZeroArray(array) ZeroSize(sizeof((array)), (array))
|
||||||
#define ZeroDynArray(Count, Pointer) ZeroSize((Count) * sizeof((Pointer)[0]), Pointer)
|
#define ZeroDynArray(Count, Pointer) ZeroSize((Count) * sizeof((Pointer)[0]), (Pointer))
|
||||||
inline void ZeroSize(size_t size, void* ptr)
|
|
||||||
{
|
static inline void ZeroSize(size_t size, void* ptr)
|
||||||
auto Byte = (uint8*)ptr;
|
{
|
||||||
|
uint8* byte_ptr = (uint8*)ptr;
|
||||||
while (size--)
|
while (size--)
|
||||||
{
|
{
|
||||||
*Byte++ = 0;
|
*byte_ptr++ = 0;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#define Restrict __restrict
|
#define Restrict __restrict
|
||||||
|
|
||||||
template <class Function>
|
static inline bool8 IsValidByteBuffer(ByteBuffer buffer)
|
||||||
class DeferredFunction
|
{
|
||||||
{
|
return (buffer.Size > 0 && buffer.Data != NULL);
|
||||||
public:
|
}
|
||||||
explicit DeferredFunction(const Function& otherFct) noexcept
|
|
||||||
: Callback(otherFct)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
explicit DeferredFunction(Function&& otherFct) noexcept
|
|
||||||
: Callback(std::move(otherFct))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
~DeferredFunction() noexcept { Callback(); }
|
extern JULIET_API void FreeByteBuffer(ByteBuffer* buffer);
|
||||||
|
|
||||||
DeferredFunction(const DeferredFunction&) = delete;
|
static inline bool8 IsPow2(uint64 x)
|
||||||
DeferredFunction(const DeferredFunction&&) = delete;
|
{
|
||||||
void operator=(const DeferredFunction&) = delete;
|
return (x != 0) && ((x & (x - 1)) == 0);
|
||||||
void operator=(DeferredFunction&&) = delete;
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
Function Callback;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <class Function>
|
|
||||||
auto Defer(Function&& fct) noexcept
|
|
||||||
{
|
|
||||||
return DeferredFunction<std::decay_t<Function>>{ std::forward<Function>(fct) };
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool IsValid(ByteBuffer buffer)
|
|
||||||
{
|
|
||||||
return buffer.Size > 0 && buffer.Data;
|
|
||||||
}
|
|
||||||
|
|
||||||
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)));
|
|
||||||
|
|
||||||
|
static inline uint64 AlignPow2(uint64 x, uint64 alignment)
|
||||||
|
{
|
||||||
|
Assert(IsPow2(alignment));
|
||||||
return (x + alignment - 1) & ~(alignment - 1);
|
return (x + alignment - 1) & ~(alignment - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
static inline void SwapMemory(void* Restrict a, void* Restrict b, size_t elem_size)
|
||||||
inline void Swap(T* Restrict a, T* Restrict b)
|
{
|
||||||
|
uint8* pa = (uint8*)a;
|
||||||
|
uint8* pb = (uint8*)b;
|
||||||
|
for (size_t i = 0; i < elem_size; ++i)
|
||||||
{
|
{
|
||||||
T temp = std::move(*a);
|
uint8 tmp = pa[i];
|
||||||
*a = std::move(*b);
|
pa[i] = pb[i];
|
||||||
*b = std::move(temp);
|
pb[i] = tmp;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Move to another file dedicated to those
|
|
||||||
#if defined(__clang__)
|
#if defined(__clang__)
|
||||||
#define COMPILER_CLANG 1
|
#define COMPILER_CLANG 1
|
||||||
#elif defined(_MSC_VER)
|
#elif defined(_MSC_VER)
|
||||||
#define COMPILER_MSVC 1
|
#define COMPILER_MSVC 1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Undef anything not defined
|
|
||||||
#if !defined(COMPILER_CLANG)
|
#if !defined(COMPILER_CLANG)
|
||||||
#define COMPILER_CLANG 0
|
#define COMPILER_CLANG 0
|
||||||
#endif
|
#endif
|
||||||
@@ -189,17 +160,33 @@ namespace Juliet
|
|||||||
#error AlignOf not defined for this compiler.
|
#error AlignOf not defined for this compiler.
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
template <typename T>
|
#if COMPILER_MSVC || (COMPILER_CLANG && defined(_MSC_VER))
|
||||||
[[nodiscard]] constexpr const char* GetTypeName()
|
#define ThreadLocal __declspec(thread)
|
||||||
{
|
|
||||||
#if COMPILER_CLANG
|
|
||||||
return __PRETTY_FUNCTION__;
|
|
||||||
#elif COMPILER_MSVC
|
|
||||||
return __FUNCSIG__;
|
|
||||||
#elif COMPILER_GCC
|
|
||||||
return __PRETTY_FUNCTION__;
|
|
||||||
#else
|
#else
|
||||||
return "UnknownType";
|
#define ThreadLocal __thread
|
||||||
#endif
|
#endif
|
||||||
}
|
|
||||||
} // namespace Juliet
|
static inline uint16 safe_cast_uint16(uint32 value)
|
||||||
|
{
|
||||||
|
Assert(value <= uint16Max);
|
||||||
|
uint16 result = (uint16)value;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define bitmask1 0x00000001u
|
||||||
|
#define bitmask2 0x00000003u
|
||||||
|
#define bitmask3 0x00000007u
|
||||||
|
#define bitmask4 0x0000000fu
|
||||||
|
#define bitmask5 0x0000001fu
|
||||||
|
#define bitmask6 0x0000003fu
|
||||||
|
#define bitmask7 0x0000007fu
|
||||||
|
#define bitmask8 0x000000ffu
|
||||||
|
#define bitmask9 0x000001ffu
|
||||||
|
#define bitmask10 0x000003ffu
|
||||||
|
#define bitmask11 0x000007ffu
|
||||||
|
#define bitmask12 0x00000fffu
|
||||||
|
#define bitmask13 0x00001fffu
|
||||||
|
#define bitmask14 0x00003fffu
|
||||||
|
#define bitmask15 0x00007fffu
|
||||||
|
#define bitmask16 0x0000ffffu
|
||||||
|
#define bitmask32 0xffffffffu
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
namespace Juliet
|
|
||||||
{
|
|
||||||
template <typename T>
|
|
||||||
concept IsEnum = std::is_enum_v<T>;
|
|
||||||
|
|
||||||
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 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 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 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 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 E ToEnum(std::underlying_type_t<E> value) noexcept
|
|
||||||
{
|
|
||||||
return static_cast<E>(value);
|
|
||||||
}
|
|
||||||
} // namespace Juliet
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
#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
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
constexpr NonNullPtr(Type* ptr)
|
|
||||||
: InternalPtr(ptr)
|
|
||||||
{
|
|
||||||
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.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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& 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 explicit operator bool() const { return InternalPtr != nullptr; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
Type* InternalPtr;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
NonNullPtr(T*) -> NonNullPtr<T>;
|
|
||||||
} // namespace Juliet
|
|
||||||
+119
-114
@@ -1,67 +1,84 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Core/Common/NonNullPtr.h>
|
#include <Core/Common/CoreTypes.h>
|
||||||
|
#include <Core/Common/CoreUtils.h>
|
||||||
#include <Core/Math/MathUtils.h>
|
#include <Core/Math/MathUtils.h>
|
||||||
#include <Core/Memory/Utils.h>
|
#include <Core/Memory/Utils.h>
|
||||||
|
|
||||||
#ifdef global
|
typedef struct Arena Arena;
|
||||||
#undef global
|
|
||||||
#define RESTORE_GLOBAL
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef RESTORE_GLOBAL
|
#define ConstString(str) ((String){ (char*)(str), sizeof(str) - 1 })
|
||||||
#define global static
|
#define ConstStringInit(str) { (char*)(str), sizeof(str) - 1 }
|
||||||
#undef RESTORE_GLOBAL
|
#define CStr(str) ((str).Str)
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace Juliet
|
|
||||||
{
|
|
||||||
struct Arena;
|
|
||||||
|
|
||||||
#define ConstString(str) { const_cast<char*>((str)), sizeof(str) - 1 }
|
|
||||||
#define CStr(str) ((str).Data)
|
|
||||||
#define InplaceString(name, size) \
|
#define InplaceString(name, size) \
|
||||||
char name##_[size]; \
|
char name##_[size]; \
|
||||||
MemSet(name##_, 0, sizeof(uint32)); \
|
MemSet(name##_, 0, sizeof(uint32)); \
|
||||||
String name = { name##_, 0 }
|
String name = { name##_, 0 }
|
||||||
|
|
||||||
// Everything is Little Endian
|
// Everything is Little Endian
|
||||||
enum class StringEncoding : uint8
|
typedef uint8 StringEncoding;
|
||||||
{
|
enum
|
||||||
Unknown = 0,
|
{
|
||||||
ASCII,
|
StringEncoding_Unknown = 0,
|
||||||
LATIN1,
|
StringEncoding_ASCII,
|
||||||
UTF8,
|
StringEncoding_LATIN1,
|
||||||
UTF16,
|
StringEncoding_UTF8,
|
||||||
UTF32,
|
StringEncoding_UTF16,
|
||||||
UCS2,
|
StringEncoding_UTF32,
|
||||||
UCS4,
|
StringEncoding_UCS2,
|
||||||
};
|
StringEncoding_UCS4,
|
||||||
|
};
|
||||||
|
|
||||||
// Represents a UTF-8 String.
|
// Represents a UTF-8 String.
|
||||||
// Not null terminated.
|
// Not null terminated.
|
||||||
struct String
|
typedef struct String8 String8;
|
||||||
|
struct String8
|
||||||
|
{
|
||||||
|
char* Str;
|
||||||
|
size_t Size;
|
||||||
|
};
|
||||||
|
typedef String8 String;
|
||||||
|
|
||||||
|
typedef struct String16 String16;
|
||||||
|
struct String16
|
||||||
|
{
|
||||||
|
uint16* Str;
|
||||||
|
size_t Size;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct StringBuffer StringBuffer;
|
||||||
|
struct StringBuffer
|
||||||
|
{
|
||||||
|
union
|
||||||
{
|
{
|
||||||
char* Data;
|
String8 str8;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
char* Str;
|
||||||
size_t Size;
|
size_t Size;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct StringBuffer : String
|
|
||||||
{
|
|
||||||
size_t Capacity;
|
|
||||||
};
|
};
|
||||||
|
size_t Capacity;
|
||||||
|
};
|
||||||
|
|
||||||
constexpr uint32 kInvalidUTF8 = 0xFFFD;
|
typedef struct UnicodeDecode UnicodeDecode;
|
||||||
|
struct UnicodeDecode
|
||||||
|
{
|
||||||
|
uint32 Increment;
|
||||||
|
uint32 Codepoint;
|
||||||
|
};
|
||||||
|
|
||||||
inline size_t StringLength(String str)
|
#define kInvalidUTF8 ((uint32)0xFFFDu)
|
||||||
{
|
|
||||||
|
static inline size_t StringLength(String str)
|
||||||
|
{
|
||||||
return str.Size;
|
return str.Size;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline size_t StringLength(const char* str)
|
static inline size_t StringLengthCStr(const char* str)
|
||||||
{
|
{
|
||||||
size_t length = 0;
|
size_t length = 0;
|
||||||
if (str)
|
if (str != NULL)
|
||||||
{
|
{
|
||||||
while (*str)
|
while (*str)
|
||||||
{
|
{
|
||||||
@@ -71,50 +88,49 @@ namespace Juliet
|
|||||||
}
|
}
|
||||||
|
|
||||||
return length;
|
return length;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool IsValid(String str)
|
static inline bool8 IsValidString(String str)
|
||||||
{
|
{
|
||||||
return str.Size > 0 && str.Data != nullptr && *str.Data;
|
return (str.Size > 0 && str.Str != NULL && *str.Str != '\0');
|
||||||
}
|
}
|
||||||
|
|
||||||
inline String WrapString(const char* str)
|
static inline String WrapString(const char* str)
|
||||||
{
|
{
|
||||||
String result = {};
|
String result = { 0 };
|
||||||
result.Data = const_cast<char*>(str);
|
result.Str = (char*)str;
|
||||||
result.Size = str ? strlen(str) : 0;
|
result.Size = (str != NULL) ? strlen(str) : 0;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline String FindChar(String str, char c)
|
static inline String FindChar(String str, char c)
|
||||||
|
{
|
||||||
|
String result = { 0 };
|
||||||
|
String cursor = str;
|
||||||
|
while (cursor.Size > 0)
|
||||||
{
|
{
|
||||||
String result = str;
|
if (*cursor.Str == c)
|
||||||
while (result.Size)
|
|
||||||
{
|
{
|
||||||
if (*result.Data != c)
|
result = cursor;
|
||||||
{
|
break;
|
||||||
++result.Data;
|
}
|
||||||
--result.Size;
|
++cursor.Str;
|
||||||
|
--cursor.Size;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool ContainsChar(String str, char c)
|
static inline bool8 ContainsChar(String str, char c)
|
||||||
{
|
{
|
||||||
return IsValid(FindChar(str, c));
|
return IsValidString(FindChar(str, c));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return:
|
// Return:
|
||||||
// - < 0 if str1 < str2
|
// - < 0 if str1 < str2
|
||||||
// - = 0 : Both strings are equals
|
// - = 0 : Both strings are equals
|
||||||
// - > 0 if str1 > str2
|
// - > 0 if str1 > str2
|
||||||
inline int32 StringCompare(String str1, String str2)
|
static inline int32 StringCompare(String str1, String str2)
|
||||||
{
|
{
|
||||||
size_t len1 = StringLength(str1);
|
size_t len1 = StringLength(str1);
|
||||||
size_t len2 = StringLength(str2);
|
size_t len2 = StringLength(str2);
|
||||||
size_t minLen = Min(len1, len2);
|
size_t minLen = Min(len1, len2);
|
||||||
@@ -123,58 +139,47 @@ namespace Juliet
|
|||||||
{
|
{
|
||||||
if (len1 > len2)
|
if (len1 > len2)
|
||||||
{
|
{
|
||||||
return 1;
|
result = 1;
|
||||||
}
|
}
|
||||||
if (len1 < len2)
|
else if (len1 < len2)
|
||||||
{
|
{
|
||||||
return -1;
|
result = -1;
|
||||||
}
|
}
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
JULIET_API uint32 StepUTF8(String& inStr);
|
JULIET_API uint32 StepUTF8(String* inStr);
|
||||||
JULIET_API String FindString(String strLeft, String strRight);
|
JULIET_API String FindString(String strLeft, String strRight);
|
||||||
|
|
||||||
// Case insensitive compare. Supports ASCII only
|
// Case insensitive compare. Supports ASCII only
|
||||||
// TODO: Support UNICODE
|
extern JULIET_API int8 StringCompareCaseInsensitive(String str1, String str2);
|
||||||
extern JULIET_API int8 StringCompareCaseInsensitive(String str1, String str2);
|
|
||||||
|
|
||||||
// Do not allocate anything, you must allocate your out buffer yourself
|
extern JULIET_API bool8 ConvertString(StringEncoding from, StringEncoding to, String src, StringBuffer* dst, bool8 nullTerminate);
|
||||||
// TODO: Version taking arena that can allocate
|
extern JULIET_API bool8 ConvertStringByName(String from, String to, String src, StringBuffer* dst, bool8 nullTerminate);
|
||||||
// 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 String StringCopy(Arena* arena, String str);
|
||||||
|
JULIET_API String16 str16_from_8(Arena* arena, String8 str);
|
||||||
|
|
||||||
template <typename... Args>
|
String trim_whitespace(String str);
|
||||||
String Format(NonNullPtr<Arena> arena, const char* formatStr, Args&&... args)
|
|
||||||
{
|
JULIET_API String StringFormatV(Arena* arena, const char* formatStr, va_list args);
|
||||||
std::string result = std::vformat(formatStr, std::make_format_args(args...));
|
JULIET_API String StringFormat(Arena* arena, const char* formatStr, ...);
|
||||||
return StringCopy(arena, WrapString(result.c_str()));
|
|
||||||
}
|
#define juliet_snprintf snprintf
|
||||||
} // namespace Juliet
|
|
||||||
|
|
||||||
#ifdef UNIT_TEST
|
#ifdef UNIT_TEST
|
||||||
namespace Juliet::UnitTest
|
static inline void UnitTest_TestFindChar(void)
|
||||||
{
|
{
|
||||||
inline void TestFindChar()
|
|
||||||
{
|
|
||||||
String s1 = ConstString("");
|
String s1 = ConstString("");
|
||||||
String s2 = ConstString("abcdefabcdef");
|
String s2 = ConstString("abcdefabcdef");
|
||||||
String s3 = ConstString("11111111111111111111");
|
String s3 = ConstString("11111111111111111111");
|
||||||
|
|
||||||
Assert(FindChar(s1, 'x').Data == nullptr);
|
Assert(FindChar(s1, 'x').Str == NULL);
|
||||||
Assert(FindChar(s2, 'y').Data == nullptr);
|
Assert(FindChar(s2, 'y').Str == NULL);
|
||||||
Assert(FindChar(s2, 'a').Data - s2.Data == 0);
|
Assert(FindChar(s2, 'a').Str - s2.Str == 0);
|
||||||
Assert(FindChar(s2, 'd').Data - s2.Data == 3);
|
Assert(FindChar(s2, 'd').Str - s2.Str == 3);
|
||||||
Assert(FindChar(s2, 'f').Data - s2.Data == 5);
|
Assert(FindChar(s2, 'f').Str - s2.Str == 5);
|
||||||
Assert(FindChar(s3, '1').Data - s3.Data == 0);
|
Assert(FindChar(s3, '1').Str - s3.Str == 0);
|
||||||
}
|
}
|
||||||
} // namespace Juliet::UnitTest
|
|
||||||
#endif
|
#endif
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Juliet.h>
|
||||||
|
|
||||||
|
#include <Core/Common/CRC32.h>
|
||||||
|
#include <Core/Common/String.h>
|
||||||
|
#include <Core/HAL/IO/IOStream.h>
|
||||||
|
#include <Core/Math/Vector.h>
|
||||||
|
|
||||||
|
typedef struct IOStream IOStream;
|
||||||
|
typedef struct Arena Arena;
|
||||||
|
typedef struct ArchivePropertyNode ArchivePropertyNode;
|
||||||
|
typedef struct ParsedArchive ParsedArchive;
|
||||||
|
typedef struct Archive Archive;
|
||||||
|
|
||||||
|
struct ArchivePropertyNode
|
||||||
|
{
|
||||||
|
String key;
|
||||||
|
String value;
|
||||||
|
uint32 key_crc;
|
||||||
|
bool8 consumed;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ParsedArchive
|
||||||
|
{
|
||||||
|
ArchivePropertyNode* nodes;
|
||||||
|
uint32 property_count;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Archive
|
||||||
|
{
|
||||||
|
Arena* arena;
|
||||||
|
bool8 loading;
|
||||||
|
ParsedArchive base;
|
||||||
|
IOStream* stream;
|
||||||
|
|
||||||
|
void* base_ptr;
|
||||||
|
index_t offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
JULIET_API void serialize_bytes(Archive* ar, void* data, size_t size);
|
||||||
|
|
||||||
|
#define serialize_elem(ar, val) serialize_bytes((ar), &(val), sizeof(val))
|
||||||
|
|
||||||
|
JULIET_API ParsedArchive tokenize_archive(Arena* arena, ByteBuffer file_buffer);
|
||||||
|
JULIET_API ArchivePropertyNode* find_property(ParsedArchive* archive, uint32 property_crc);
|
||||||
|
JULIET_API void write_property_header(Archive* archive, String property_name);
|
||||||
|
|
||||||
|
JULIET_API bool8 read_prop_str(Archive* ar, String value_raw, String* value);
|
||||||
|
JULIET_API void write_str(IOStream* stream, String value);
|
||||||
|
JULIET_API bool8 read_f32(const char* buffer, float* value, const char** end);
|
||||||
|
JULIET_API void write_f32(IOStream* stream, float value);
|
||||||
|
JULIET_API bool8 read_i8(const char* buffer, int8* value);
|
||||||
|
JULIET_API void write_i8(IOStream* stream, int8 value);
|
||||||
|
JULIET_API bool8 read_i16(const char* buffer, int16* value);
|
||||||
|
JULIET_API void write_i16(IOStream* stream, int16 value);
|
||||||
|
JULIET_API bool8 read_i32(const char* buffer, int32* value);
|
||||||
|
JULIET_API void write_i32(IOStream* stream, int32 value);
|
||||||
|
JULIET_API bool8 read_i64(const char* buffer, int64* value);
|
||||||
|
JULIET_API void write_i64(IOStream* stream, int64 value);
|
||||||
|
JULIET_API bool8 read_u8(const char* buffer, uint8* value);
|
||||||
|
JULIET_API void write_u8(IOStream* stream, uint8 value);
|
||||||
|
JULIET_API bool8 read_u16(const char* buffer, uint16* value);
|
||||||
|
JULIET_API void write_u16(IOStream* stream, uint16 value);
|
||||||
|
JULIET_API bool8 read_u32(const char* buffer, uint32* value);
|
||||||
|
JULIET_API void write_u32(IOStream* stream, uint32 value);
|
||||||
|
JULIET_API bool8 read_u64(const char* buffer, uint64* value);
|
||||||
|
JULIET_API void write_u64(IOStream* stream, uint64 value);
|
||||||
|
JULIET_API bool8 read_b8(const char* buffer, bool8* value);
|
||||||
|
JULIET_API void write_b8(IOStream* stream, bool8 value);
|
||||||
|
JULIET_API bool8 read_vec4(const char* buffer, Vector4* value);
|
||||||
|
JULIET_API void write_vec4(IOStream* stream, Vector4 value);
|
||||||
|
|
||||||
|
JULIET_API bool8 serialize_str(Archive* ar, String property_name, String* value);
|
||||||
|
JULIET_API bool8 serialize_f32(Archive* ar, String property_name, float* value);
|
||||||
|
JULIET_API bool8 serialize_i8(Archive* ar, String property_name, int8* value);
|
||||||
|
JULIET_API bool8 serialize_i16(Archive* ar, String property_name, int16* value);
|
||||||
|
JULIET_API bool8 serialize_i32(Archive* ar, String property_name, int32* value);
|
||||||
|
JULIET_API bool8 serialize_i64(Archive* ar, String property_name, int64* value);
|
||||||
|
JULIET_API bool8 serialize_u8(Archive* ar, String property_name, uint8* value);
|
||||||
|
JULIET_API bool8 serialize_u16(Archive* ar, String property_name, uint16* value);
|
||||||
|
JULIET_API bool8 serialize_u32(Archive* ar, String property_name, uint32* value);
|
||||||
|
JULIET_API bool8 serialize_u64(Archive* ar, String property_name, uint64* value);
|
||||||
|
JULIET_API bool8 serialize_b8(Archive* ar, String property_name, bool8* value);
|
||||||
|
JULIET_API bool8 serialize_vec4(Archive* ar, String property_name, Vector4* value);
|
||||||
|
|
||||||
|
#define SERIALIZE_STR(ar, name, var_ptr) serialize_str((ar), ConstString(#name), (var_ptr))
|
||||||
|
#define SERIALIZE_F32(ar, name, var_ptr) serialize_f32((ar), ConstString(#name), (var_ptr))
|
||||||
|
#define SERIALIZE_I8(ar, name, var_ptr) serialize_i8((ar), ConstString(#name), (var_ptr))
|
||||||
|
#define SERIALIZE_I16(ar, name, var_ptr) serialize_i16((ar), ConstString(#name), (var_ptr))
|
||||||
|
#define SERIALIZE_I32(ar, name, var_ptr) serialize_i32((ar), ConstString(#name), (var_ptr))
|
||||||
|
#define SERIALIZE_I64(ar, name, var_ptr) serialize_i64((ar), ConstString(#name), (var_ptr))
|
||||||
|
#define SERIALIZE_U8(ar, name, var_ptr) serialize_u8((ar), ConstString(#name), (var_ptr))
|
||||||
|
#define SERIALIZE_U16(ar, name, var_ptr) serialize_u16((ar), ConstString(#name), (var_ptr))
|
||||||
|
#define SERIALIZE_U32(ar, name, var_ptr) serialize_u32((ar), ConstString(#name), (var_ptr))
|
||||||
|
#define SERIALIZE_U64(ar, name, var_ptr) serialize_u64((ar), ConstString(#name), (var_ptr))
|
||||||
|
#define SERIALIZE_B8(ar, name, var_ptr) serialize_b8((ar), ConstString(#name), (var_ptr))
|
||||||
|
#define SERIALIZE_BOOL(ar, name, var_ptr) serialize_b8((ar), ConstString(#name), (var_ptr))
|
||||||
|
#define SERIALIZE_VEC4(ar, name, var_ptr) serialize_vec4((ar), ConstString(#name), (var_ptr))
|
||||||
|
|
||||||
|
#if JULIET_DEBUG
|
||||||
|
JULIET_API void audit_unconsumed_properties(ParsedArchive* archive, String context_name);
|
||||||
|
#endif
|
||||||
@@ -1,214 +1,232 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Core/Common/NonNullPtr.h>
|
#include <Core/Common/CoreUtils.h>
|
||||||
#include <Core/Memory/MemoryArena.h>
|
#include <Core/Memory/MemoryArena.h>
|
||||||
|
#include <Core/Memory/Utils.h>
|
||||||
|
|
||||||
namespace Juliet
|
typedef struct VectorArena VectorArena;
|
||||||
|
struct VectorArena
|
||||||
{
|
{
|
||||||
template <typename Type, size_t ReserveSize = 16>
|
Arena* Arena;
|
||||||
struct VectorArena
|
void* DataFirst;
|
||||||
|
void* DataLast;
|
||||||
|
void* Data;
|
||||||
|
union
|
||||||
{
|
{
|
||||||
void Create(NonNullPtr<Arena> arena JULIET_DEBUG_PARAM(const char* name = nullptr))
|
size_t Count;
|
||||||
|
size_t Size;
|
||||||
|
};
|
||||||
|
size_t Capacity;
|
||||||
|
size_t ElemSize;
|
||||||
|
size_t ElemAlign;
|
||||||
|
size_t ReserveSize;
|
||||||
|
JULIET_DEBUG_ONLY(const char* Name;)
|
||||||
|
};
|
||||||
|
|
||||||
|
#define kDefaultVectorReserveSize ((size_t)16)
|
||||||
|
#define kVectorArenaDefaultReserveSize ((size_t)16)
|
||||||
|
|
||||||
|
static inline void vector_arena_reserve(VectorArena* vec, size_t new_capacity)
|
||||||
|
{
|
||||||
|
Assert(vec != NULL);
|
||||||
|
Assert(vec->Arena != NULL);
|
||||||
|
Assert(new_capacity <= vec->ReserveSize && "VectorArena capacity should be <= ReserveSize.");
|
||||||
|
|
||||||
|
if (vec->Data == NULL)
|
||||||
{
|
{
|
||||||
Assert(!Arena);
|
vec->Data = ArenaPush(vec->Arena, new_capacity * vec->ElemSize, Max(8ull, (uint64)vec->ElemAlign),
|
||||||
|
true JULIET_DEBUG_PARAM(vec->Name));
|
||||||
JULIET_DEBUG_ONLY(Name = name ? name : Name;)
|
vec->Capacity = new_capacity;
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
Data = ArenaPushArray<Type>(Arena, newCapacity JULIET_DEBUG_PARAM(Name));
|
|
||||||
Capacity = newCapacity;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Unimplemented();
|
Unimplemented();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void vector_arena_create(VectorArena* vec, Arena* arena, size_t elem_size, size_t elem_align,
|
||||||
|
size_t reserve_size JULIET_DEBUG_PARAM(const char* name))
|
||||||
|
{
|
||||||
|
Assert(vec != NULL);
|
||||||
|
Assert(arena != NULL);
|
||||||
|
Assert(vec->Arena == NULL);
|
||||||
|
Assert(elem_size > 0);
|
||||||
|
|
||||||
|
JULIET_DEBUG_ONLY(vec->Name = (name != NULL) ? name : "VectorArena";)
|
||||||
|
|
||||||
|
vec->DataFirst = NULL;
|
||||||
|
vec->DataLast = NULL;
|
||||||
|
vec->Data = NULL;
|
||||||
|
vec->Count = 0;
|
||||||
|
vec->Capacity = 0;
|
||||||
|
vec->ElemSize = elem_size;
|
||||||
|
vec->ElemAlign = elem_align;
|
||||||
|
vec->ReserveSize = (reserve_size > 0) ? reserve_size : 16;
|
||||||
|
vec->Arena = arena;
|
||||||
|
|
||||||
|
vector_arena_reserve(vec, vec->ReserveSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void vector_arena_destroy(VectorArena* vec)
|
||||||
|
{
|
||||||
|
Assert(vec != NULL);
|
||||||
|
vec->DataFirst = NULL;
|
||||||
|
vec->DataLast = NULL;
|
||||||
|
vec->Data = NULL;
|
||||||
|
vec->Count = 0;
|
||||||
|
vec->Capacity = 0;
|
||||||
|
vec->Arena = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void* vector_arena_begin(VectorArena* vec)
|
||||||
|
{
|
||||||
|
Assert(vec != NULL);
|
||||||
|
return vec->DataFirst;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline const void* vector_arena_begin_const(const VectorArena* vec)
|
||||||
|
{
|
||||||
|
Assert(vec != NULL);
|
||||||
|
return vec->DataFirst;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void* vector_arena_end(VectorArena* vec)
|
||||||
|
{
|
||||||
|
Assert(vec != NULL);
|
||||||
|
void* result = NULL;
|
||||||
|
if (vec->DataFirst != NULL)
|
||||||
|
{
|
||||||
|
result = (Byte*)vec->DataFirst + (vec->Count * vec->ElemSize);
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
void Resize(size_t newCount)
|
static inline const void* vector_arena_end_const(const VectorArena* vec)
|
||||||
|
{
|
||||||
|
Assert(vec != NULL);
|
||||||
|
const void* result = NULL;
|
||||||
|
if (vec->DataFirst != NULL)
|
||||||
{
|
{
|
||||||
Assert(Arena);
|
result = (const Byte*)vec->DataFirst + (vec->Count * vec->ElemSize);
|
||||||
if (newCount == Count)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
if (Data == nullptr)
|
static inline void vector_arena_resize(VectorArena* vec, size_t new_size)
|
||||||
|
{
|
||||||
|
Assert(vec != NULL);
|
||||||
|
Assert(new_size <= vec->Capacity);
|
||||||
|
vec->Count = new_size;
|
||||||
|
if (vec->Count > 0)
|
||||||
{
|
{
|
||||||
size_t initialCapacity = newCount > ReserveSize ? newCount : ReserveSize;
|
vec->DataFirst = vec->Data;
|
||||||
Reserve(initialCapacity);
|
vec->DataLast = (Byte*)vec->Data + ((vec->Count - 1) * vec->ElemSize);
|
||||||
}
|
|
||||||
|
|
||||||
Assert(newCount <= Capacity && "VectorArena capacity exceeded!");
|
|
||||||
Count = newCount;
|
|
||||||
|
|
||||||
if (Count > 0)
|
|
||||||
{
|
|
||||||
DataFirst = Data;
|
|
||||||
DataLast = Data + Count - 1;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DataFirst = DataLast = nullptr;
|
vec->DataFirst = NULL;
|
||||||
}
|
vec->DataLast = NULL;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void PushBack(const Type* buffer, size_t amount)
|
static inline void vector_arena_push_buffer(VectorArena* vec, const void* buffer, size_t amount)
|
||||||
|
{
|
||||||
|
Assert(vec != NULL);
|
||||||
|
Assert(buffer != NULL);
|
||||||
|
Assert(amount > 0);
|
||||||
|
Assert(vec->Count + amount <= vec->Capacity && "VectorArena capacity exceeded!");
|
||||||
|
|
||||||
|
Byte* dest = (Byte*)vec->Data + (vec->Count * vec->ElemSize);
|
||||||
|
MemCopy(dest, buffer, amount * vec->ElemSize);
|
||||||
|
|
||||||
|
if (vec->DataFirst == NULL)
|
||||||
{
|
{
|
||||||
Assert(Arena);
|
vec->DataFirst = vec->Data;
|
||||||
Assert(buffer || amount == 0);
|
}
|
||||||
|
|
||||||
if (amount == 0)
|
vec->Count += amount;
|
||||||
|
vec->DataLast = (Byte*)vec->Data + ((vec->Count - 1) * vec->ElemSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define vector_arena_push_array vector_arena_push_buffer
|
||||||
|
|
||||||
|
static inline void vector_arena_push_elem(VectorArena* vec, const void* value_ptr)
|
||||||
|
{
|
||||||
|
Assert(vec != NULL);
|
||||||
|
Assert(value_ptr != NULL);
|
||||||
|
Assert(vec->Count + 1 <= vec->Capacity && "VectorArena capacity exceeded!");
|
||||||
|
|
||||||
|
Byte* dest = (Byte*)vec->Data + (vec->Count * vec->ElemSize);
|
||||||
|
MemCopy(dest, value_ptr, vec->ElemSize);
|
||||||
|
|
||||||
|
if (vec->DataFirst == NULL)
|
||||||
{
|
{
|
||||||
return;
|
vec->DataFirst = vec->Data;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Data == nullptr)
|
vec->Count += 1;
|
||||||
|
vec->DataLast = dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void vector_arena_remove_at_fast(VectorArena* vec, index_t index)
|
||||||
|
{
|
||||||
|
Assert(vec != NULL);
|
||||||
|
Assert(index < vec->Count);
|
||||||
|
|
||||||
|
size_t last_idx = vec->Count - 1;
|
||||||
|
if (index != last_idx)
|
||||||
{
|
{
|
||||||
size_t initialCapacity = amount > ReserveSize ? amount : ReserveSize;
|
Byte* dst = (Byte*)vec->DataFirst + (index * vec->ElemSize);
|
||||||
Reserve(initialCapacity);
|
Byte* src = (Byte*)vec->DataFirst + (last_idx * vec->ElemSize);
|
||||||
|
MemCopy(dst, src, vec->ElemSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert(Count + amount <= Capacity && "VectorArena capacity exceeded!");
|
vec->Count -= 1;
|
||||||
|
if (vec->Count == 0)
|
||||||
Type* dst = Data + Count;
|
|
||||||
MemCopy(dst, buffer, amount * sizeof(Type));
|
|
||||||
|
|
||||||
if (Count == 0)
|
|
||||||
{
|
{
|
||||||
DataFirst = dst;
|
vec->DataFirst = NULL;
|
||||||
|
vec->DataLast = NULL;
|
||||||
}
|
}
|
||||||
DataLast = dst + amount - 1;
|
else
|
||||||
Count += amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PushBack(const Type& value)
|
|
||||||
{
|
{
|
||||||
Assert(Arena);
|
vec->DataLast = (Byte*)vec->DataFirst + ((vec->Count - 1) * vec->ElemSize);
|
||||||
|
|
||||||
if (Data == nullptr)
|
|
||||||
{
|
|
||||||
Reserve(ReserveSize);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Assert(Count + 1 <= Capacity && "VectorArena capacity exceeded!");
|
static inline void vector_arena_clear(VectorArena* vec)
|
||||||
|
{
|
||||||
|
Assert(vec != NULL);
|
||||||
|
vec->DataFirst = NULL;
|
||||||
|
vec->DataLast = NULL;
|
||||||
|
vec->Count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
Type* entry = Data + Count;
|
static inline bool8 vector_arena_is_empty(const VectorArena* vec)
|
||||||
*entry = value;
|
{
|
||||||
|
Assert(vec != NULL);
|
||||||
|
return vec->Count == 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (Count == 0)
|
static inline size_t vector_arena_size(const VectorArena* vec)
|
||||||
{
|
{
|
||||||
DataFirst = entry;
|
Assert(vec != NULL);
|
||||||
}
|
return vec->Count;
|
||||||
DataLast = entry;
|
}
|
||||||
++Count;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PushBack(Type&& value)
|
#define VectorArenaCreate(vec, Type, arena, ...) \
|
||||||
{
|
vector_arena_create((vec), (arena), sizeof(Type), AlignOf(Type), __VA_ARGS__)
|
||||||
Assert(Arena);
|
|
||||||
|
|
||||||
if (Data == nullptr)
|
#define VectorArenaAt(Type, vec, idx) (((Type*)((vec)->DataFirst))[idx])
|
||||||
{
|
#define VectorArenaData(Type, vec) ((Type*)((vec)->Data))
|
||||||
Reserve(ReserveSize);
|
#define VectorArenaBegin(Type, vec) ((Type*)((vec)->DataFirst))
|
||||||
}
|
#define VectorArenaEnd(Type, vec) (((vec)->DataFirst != NULL) ? (((Type*)((vec)->DataFirst)) + (vec)->Count) : NULL)
|
||||||
|
#define VectorArenaLast(Type, vec) ((Type*)((vec)->DataLast))
|
||||||
Assert(Count + 1 <= Capacity && "VectorArena capacity exceeded!");
|
#define VectorArenaBack(Type, vec) ((Type*)((vec)->DataLast))
|
||||||
|
#define VectorArenaPushPtr(Type, vec, ptr) vector_arena_push_elem((vec), (ptr))
|
||||||
Type* entry = Data + Count;
|
#define VectorArenaPushVal(Type, vec, val) \
|
||||||
*entry = std::move(value);
|
do \
|
||||||
|
{ \
|
||||||
if (Count == 0)
|
Type _vec_tmp_val = (val); \
|
||||||
{
|
vector_arena_push_elem((vec), &_vec_tmp_val); \
|
||||||
DataFirst = entry;
|
} \
|
||||||
}
|
while (0)
|
||||||
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; }
|
|
||||||
|
|
||||||
// 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* begin() { return DataFirst; }
|
|
||||||
[[nodiscard]] Type* end() { return DataFirst + Count; }
|
|
||||||
|
|
||||||
[[nodiscard]] const Type* begin() const { return DataFirst; }
|
|
||||||
[[nodiscard]] const Type* end() const { return DataFirst + Count; }
|
|
||||||
|
|
||||||
[[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).");
|
|
||||||
} // namespace Juliet
|
|
||||||
@@ -1,21 +1,16 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Core/Common/CoreTypes.h>
|
|
||||||
#include <Core/Common/NonNullPtr.h>
|
|
||||||
#include <Core/Common/String.h>
|
#include <Core/Common/String.h>
|
||||||
#include <Juliet.h>
|
|
||||||
|
|
||||||
namespace Juliet
|
typedef struct Window Window;
|
||||||
{
|
|
||||||
struct Window;
|
|
||||||
|
|
||||||
using WindowID = uint8;
|
typedef uint8 WindowID;
|
||||||
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 Window* CreatePlatformWindow(const char* title, uint16 width, uint16 height, int flags);
|
||||||
extern JULIET_API void HideWindow(NonNullPtr<Window> window);
|
extern JULIET_API void DestroyPlatformWindow(Window* window);
|
||||||
|
|
||||||
extern JULIET_API WindowID GetWindowID(NonNullPtr<Window> window);
|
extern JULIET_API void ShowPlatformWindow(Window* window);
|
||||||
extern JULIET_API void SetWindowTitle(NonNullPtr<Window> window, String title);
|
extern JULIET_API void HidePlatformWindow(Window* window);
|
||||||
} // namespace Juliet
|
|
||||||
|
extern JULIET_API WindowID GetWindowID(Window* window);
|
||||||
|
extern JULIET_API void SetWindowTitle(Window* window, String title);
|
||||||
@@ -1,12 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Core/Common/NonNullPtr.h>
|
#include <Core/Common/CoreTypes.h>
|
||||||
|
#include <Juliet.h>
|
||||||
|
|
||||||
namespace Juliet
|
typedef struct DynamicLibrary DynamicLibrary;
|
||||||
{
|
|
||||||
struct DynamicLibrary;
|
|
||||||
|
|
||||||
extern JULIET_API DynamicLibrary* LoadDynamicLibrary(const char* filename);
|
extern JULIET_API DynamicLibrary* LoadDynamicLibrary(const char* filename);
|
||||||
extern JULIET_API FunctionPtr LoadFunction(NonNullPtr<DynamicLibrary> lib, const char* functionName);
|
extern JULIET_API FunctionPtr LoadFunction(DynamicLibrary* lib, const char* functionName);
|
||||||
extern JULIET_API void UnloadDynamicLibrary(NonNullPtr<DynamicLibrary> lib);
|
extern JULIET_API void UnloadDynamicLibrary(DynamicLibrary* lib);
|
||||||
} // namespace Juliet
|
|
||||||
@@ -1,71 +1,70 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <Core/Common/CoreUtils.h>
|
||||||
#include <Core/HAL/Display/Display.h>
|
#include <Core/HAL/Display/Display.h>
|
||||||
#include <Core/HAL/Keyboard/Keyboard.h>
|
#include <Core/HAL/Keyboard/Keyboard.h>
|
||||||
#include <Core/HAL/Keyboard/KeyCode.h>
|
#include <Core/HAL/Keyboard/KeyCode.h>
|
||||||
#include <Core/HAL/Mouse/Mouse.h>
|
#include <Core/HAL/Mouse/Mouse.h>
|
||||||
#include <Juliet.h>
|
#include <Juliet.h>
|
||||||
|
|
||||||
// Handles all events from systems handling the Hardware
|
typedef uint32 EventType;
|
||||||
// Very inspired by SDL3
|
enum
|
||||||
|
|
||||||
namespace Juliet
|
|
||||||
{
|
{
|
||||||
enum class EventType : uint32
|
EventType_None = 0,
|
||||||
{
|
EventType_First = EventType_None,
|
||||||
None = 0,
|
|
||||||
First = None,
|
|
||||||
|
|
||||||
// Application Events
|
// Application Events
|
||||||
// User querying an exit
|
EventType_Application_Exit = 100,
|
||||||
Application_Exit = 100,
|
EventType_Application_OS_Terminate = 101,
|
||||||
// OS terminating the application
|
EventType_Application_Begin = EventType_Application_Exit,
|
||||||
Application_OS_Terminate,
|
EventType_Application_End = EventType_Application_OS_Terminate,
|
||||||
Application_Begin = Application_Exit,
|
|
||||||
Application_End = Application_OS_Terminate,
|
|
||||||
|
|
||||||
// Window Events
|
// Window Events
|
||||||
Window_Close_Request = 200,
|
EventType_Window_Close_Request = 200,
|
||||||
Window_Begin = Window_Close_Request,
|
EventType_Window_Begin = EventType_Window_Close_Request,
|
||||||
Window_End = Window_Close_Request,
|
EventType_Window_End = EventType_Window_Close_Request,
|
||||||
|
|
||||||
// Keyboard Event
|
// Keyboard Event
|
||||||
Key_Down = 300,
|
EventType_Key_Down = 300,
|
||||||
Key_Up,
|
EventType_Key_Up = 301,
|
||||||
Keyboard_Begin = Key_Down,
|
EventType_Keyboard_Begin = EventType_Key_Down,
|
||||||
Keyboard_End = Key_Up,
|
EventType_Keyboard_End = EventType_Key_Up,
|
||||||
|
|
||||||
// Mouse Event
|
// Mouse Event
|
||||||
Mouse_Move = 400,
|
EventType_Mouse_Move = 400,
|
||||||
Mouse_ButtonPressed,
|
EventType_Mouse_ButtonPressed = 401,
|
||||||
Mouse_ButtonReleased,
|
EventType_Mouse_ButtonReleased = 402,
|
||||||
|
|
||||||
Mouse_Begin = Mouse_Move,
|
EventType_Mouse_Begin = EventType_Mouse_Move,
|
||||||
Mouse_End = Mouse_ButtonReleased,
|
EventType_Mouse_End = EventType_Mouse_ButtonReleased,
|
||||||
|
|
||||||
Last // Get value from the previous one
|
EventType_Last
|
||||||
};
|
};
|
||||||
|
|
||||||
struct WindowEvent
|
typedef struct WindowEvent WindowEvent;
|
||||||
{
|
typedef struct KeyboardEvent KeyboardEvent;
|
||||||
|
typedef struct MouseMovementEvent MouseMovementEvent;
|
||||||
|
typedef struct MouseButtonEvent MouseButtonEvent;
|
||||||
|
typedef union AllSystemEventUnion AllSystemEventUnion;
|
||||||
|
typedef struct SystemEvent SystemEvent;
|
||||||
|
|
||||||
|
struct WindowEvent
|
||||||
|
{
|
||||||
WindowID AssociatedWindowID;
|
WindowID AssociatedWindowID;
|
||||||
uint32 DataPadding[2]; // TODO : define how much data param we need
|
uint32 DataPadding[2];
|
||||||
};
|
};
|
||||||
|
|
||||||
struct KeyboardEvent
|
struct KeyboardEvent
|
||||||
{
|
{
|
||||||
KeyboardID AssociatedKeyboardID;
|
KeyboardID AssociatedKeyboardID;
|
||||||
WindowID WindowID;
|
WindowID WindowID;
|
||||||
Key Key;
|
Key Key;
|
||||||
KeyState KeyState;
|
KeyState KeyState;
|
||||||
KeyMod KeyModeState;
|
KeyMod KeyModeState;
|
||||||
};
|
};
|
||||||
|
|
||||||
// =====================================================
|
struct MouseMovementEvent
|
||||||
// Mouse Events
|
{
|
||||||
// =====================================================
|
|
||||||
struct MouseMovementEvent
|
|
||||||
{
|
|
||||||
MouseID AssociatedMouseID;
|
MouseID AssociatedMouseID;
|
||||||
WindowID WindowID;
|
WindowID WindowID;
|
||||||
float X;
|
float X;
|
||||||
@@ -73,49 +72,36 @@ namespace Juliet
|
|||||||
float X_Displacement;
|
float X_Displacement;
|
||||||
float Y_Displacement;
|
float Y_Displacement;
|
||||||
MouseButton ButtonState;
|
MouseButton ButtonState;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct MouseButtonEvent
|
struct MouseButtonEvent
|
||||||
{
|
{
|
||||||
MouseID AssociatedMouseID;
|
MouseID AssociatedMouseID;
|
||||||
WindowID WindowID;
|
WindowID WindowID;
|
||||||
float X;
|
float X;
|
||||||
float Y;
|
float Y;
|
||||||
MouseButton ButtonState;
|
MouseButton ButtonState;
|
||||||
bool IsPressed : 1;
|
bool8 IsPressed;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Tagged union representing ALL possible system events + a bit of data for custom event if needed
|
union AllSystemEventUnion
|
||||||
union AllSystemEventUnion
|
{
|
||||||
{
|
|
||||||
WindowEvent Window;
|
WindowEvent Window;
|
||||||
KeyboardEvent Keyboard;
|
KeyboardEvent Keyboard;
|
||||||
MouseMovementEvent MouseMovement;
|
MouseMovementEvent MouseMovement;
|
||||||
MouseButtonEvent MouseButton;
|
MouseButtonEvent MouseButton;
|
||||||
uint8 Padding[128]; // Make sure that the union is fixed in size and big enough on all platforms.
|
uint8 Padding[128];
|
||||||
};
|
};
|
||||||
// Make sure we do not bust the union size
|
StaticAssert(sizeof(AllSystemEventUnion) == 128, SystemEventUnionSizeCheck);
|
||||||
static_assert(sizeof(AllSystemEventUnion) == sizeof(((AllSystemEventUnion*)nullptr)->Padding));
|
|
||||||
|
|
||||||
struct SystemEvent
|
struct SystemEvent
|
||||||
{
|
{
|
||||||
EventType Type;
|
EventType Type;
|
||||||
uint64 Timestamp;
|
uint64 Timestamp;
|
||||||
AllSystemEventUnion Data;
|
AllSystemEventUnion Data;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Poll for any event, return false if no event is available.
|
extern JULIET_API bool8 GetEvent(SystemEvent* event);
|
||||||
// Equivalent to WaitEvent(event, 0);
|
extern JULIET_API bool8 WaitEvent(SystemEvent* event, int32 timeoutInNS);
|
||||||
// Will not block
|
extern JULIET_API bool8 AddEvent(SystemEvent* event);
|
||||||
extern JULIET_API bool GetEvent(SystemEvent& event);
|
extern void Events_NewFrame(float deltaTime);
|
||||||
|
|
||||||
// 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);
|
|
||||||
} // namespace Juliet
|
|
||||||
@@ -2,19 +2,43 @@
|
|||||||
|
|
||||||
#include <Core/Common/String.h>
|
#include <Core/Common/String.h>
|
||||||
|
|
||||||
namespace Juliet
|
typedef uint32 AssetType;
|
||||||
|
enum
|
||||||
{
|
{
|
||||||
// Returns the path to the application directory
|
AssetType_JAsset = 0,
|
||||||
[[nodiscard]] extern JULIET_API String GetBasePath();
|
AssetType_Shader = 1,
|
||||||
|
AssetType_Config = 2,
|
||||||
|
};
|
||||||
|
|
||||||
// Returns the resolved base path to the compiled shaders directory.
|
typedef struct Path Path;
|
||||||
// In dev, this resolves to ../../Assets/compiled/ relative to the exe.
|
struct Path
|
||||||
// In shipping, this resolves to Assets/Shaders/ next to the exe.
|
{
|
||||||
[[nodiscard]] extern JULIET_API String GetAssetBasePath();
|
String Directories;
|
||||||
|
String FullPath;
|
||||||
|
};
|
||||||
|
|
||||||
// Builds a full path to an asset file given its filename (e.g. "Triangle.vert.dxil").
|
JULIET_API bool8 IsValidPath(Path path);
|
||||||
// The caller owns the returned buffer and must free it.
|
|
||||||
[[nodiscard]] extern JULIET_API String GetAssetPath(String filename);
|
|
||||||
|
|
||||||
[[nodiscard]]extern JULIET_API bool IsAbsolutePath(String path);
|
// Returns the path to the application directory
|
||||||
} // namespace Juliet
|
JULIET_API Path GetBasePath(void);
|
||||||
|
|
||||||
|
// Path to the /Assets/ folder
|
||||||
|
JULIET_API Path GetAssetBasePath(void);
|
||||||
|
|
||||||
|
// Builds a full path to an asset file given its filename (e.g. "Triangle.vert.dxil").
|
||||||
|
JULIET_API Path GetAssetPath(Arena* arena, AssetType type, String filename);
|
||||||
|
|
||||||
|
JULIET_API bool8 IsAbsolutePath(String path);
|
||||||
|
|
||||||
|
JULIET_API Path fs_create_clean_path(Arena* arena, String path);
|
||||||
|
|
||||||
|
JULIET_API bool8 fs_create_directory(String path, bool8 recursive);
|
||||||
|
|
||||||
|
JULIET_API bool8 fs_path_exists(String path);
|
||||||
|
|
||||||
|
JULIET_API bool8 fs_move_file(Path old_path, Path new_path);
|
||||||
|
|
||||||
|
JULIET_API bool8 fs_delete_file(String path);
|
||||||
|
|
||||||
|
typedef void (*fs_iterate_director_callback)(Path path, bool8 is_director, void* user_payload);
|
||||||
|
JULIET_API void fs_iterate_directory(Path directory_path, String extension, void* user_payload, fs_iterate_director_callback callback);
|
||||||
@@ -1,69 +1,69 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Core/Common/NonNullPtr.h>
|
|
||||||
#include <Core/Common/String.h>
|
#include <Core/Common/String.h>
|
||||||
|
#include <Core/HAL/Filesystem/Filesystem.h>
|
||||||
#include <Juliet.h>
|
#include <Juliet.h>
|
||||||
|
|
||||||
namespace Juliet
|
// Opaque type
|
||||||
|
typedef struct IOStream IOStream;
|
||||||
|
typedef struct IOStreamDataPayload IOStreamDataPayload;
|
||||||
|
|
||||||
|
struct IOStreamDataPayload
|
||||||
{
|
{
|
||||||
// Opaque type
|
uint8 unused;
|
||||||
struct IOStream;
|
};
|
||||||
|
|
||||||
struct IOStreamDataPayload
|
typedef uint8 IOStreamStatus;
|
||||||
{
|
enum
|
||||||
};
|
{
|
||||||
|
IOStreamStatus_Ready = 0,
|
||||||
|
IOStreamStatus_Error,
|
||||||
|
IOStreamStatus_EndOfFile,
|
||||||
|
IOStreamStatus_NotReady,
|
||||||
|
IOStreamStatus_ReadOnly,
|
||||||
|
IOStreamStatus_WriteOnly
|
||||||
|
};
|
||||||
|
|
||||||
enum class IOStreamStatus : uint8
|
typedef uint8 IOStreamSeekPivot;
|
||||||
{
|
enum
|
||||||
Ready,
|
{
|
||||||
Error,
|
IOStreamSeekPivot_Begin = 0,
|
||||||
EndOfFile,
|
IOStreamSeekPivot_Current,
|
||||||
NotReady,
|
IOStreamSeekPivot_End,
|
||||||
ReadOnly,
|
IOStreamSeekPivot_Count
|
||||||
WriteOnly
|
};
|
||||||
};
|
|
||||||
|
|
||||||
enum class IOStreamSeekPivot : uint8
|
typedef struct IOStreamInterface IOStreamInterface;
|
||||||
{
|
struct IOStreamInterface
|
||||||
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;
|
uint32 Version;
|
||||||
|
|
||||||
int64 (*Size)(NonNullPtr<IOStreamDataPayload> data);
|
int64 (*Size)(IOStreamDataPayload* data);
|
||||||
|
|
||||||
int64 (*Seek)(NonNullPtr<IOStreamDataPayload> data, int64 offset, IOStreamSeekPivot pivot);
|
int64 (*Seek)(IOStreamDataPayload* data, int64 offset, IOStreamSeekPivot pivot);
|
||||||
size_t (*Read)(NonNullPtr<IOStreamDataPayload> data, void* outBuffer, size_t size, NonNullPtr<IOStreamStatus> status);
|
size_t (*Read)(IOStreamDataPayload* data, void* outBuffer, size_t size, IOStreamStatus* status);
|
||||||
size_t (*Write)(NonNullPtr<IOStreamDataPayload> data, ByteBuffer inBuffer, NonNullPtr<IOStreamStatus> status);
|
size_t (*Write)(IOStreamDataPayload* data, ByteBuffer inBuffer, IOStreamStatus* status);
|
||||||
bool (*Flush)(NonNullPtr<IOStreamDataPayload> data, NonNullPtr<IOStreamStatus> status);
|
bool8 (*Flush)(IOStreamDataPayload* data, IOStreamStatus* status);
|
||||||
|
|
||||||
bool (*Close)(NonNullPtr<IOStreamDataPayload> data);
|
bool8 (*Close)(IOStreamDataPayload* data);
|
||||||
};
|
};
|
||||||
|
|
||||||
extern JULIET_API IOStream* IOFromFile(String filename, String mode);
|
extern JULIET_API IOStream* IOFromFile(Arena* arena, String filename, String mode);
|
||||||
|
|
||||||
// Let you use an interface to open any io. Is used internally by IOFromFile
|
extern JULIET_API IOStream* IOFromInterface(Arena* arena, const IOStreamInterface* streamInterface,
|
||||||
extern JULIET_API IOStream* IOFromInterface(NonNullPtr<const IOStreamInterface> streamInterface, NonNullPtr<IOStreamDataPayload> payload);
|
IOStreamDataPayload* payload);
|
||||||
|
|
||||||
// Write formatted string into the stream.
|
JULIET_API size_t IOPrintf(IOStream* stream, const char* format, ...);
|
||||||
extern JULIET_API size_t IOPrintf(NonNullPtr<IOStream> stream, _Printf_format_string_ const char* format, ...);
|
JULIET_API size_t IOWrite(IOStream* stream, ByteBuffer inBuffer);
|
||||||
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);
|
JULIET_API size_t IORead(IOStream* stream, void* ptr, size_t size);
|
||||||
extern JULIET_API int64 IOSeek(NonNullPtr<IOStream> stream, int64 offset, IOStreamSeekPivot pivot);
|
JULIET_API int64 IOSeek(IOStream* stream, int64 offset, IOStreamSeekPivot pivot);
|
||||||
|
|
||||||
extern JULIET_API int64 IOSize(NonNullPtr<IOStream> stream);
|
JULIET_API int64 IOSize(IOStream* stream);
|
||||||
|
|
||||||
// TODO : Use memory arena because that Allocates
|
JULIET_API ByteBuffer LoadFile(Arena* arena, String filename);
|
||||||
extern JULIET_API ByteBuffer LoadFile(String filename);
|
JULIET_API ByteBuffer LoadFileFromStream(Arena* arena, IOStream* stream, bool8 closeStreamWhenDone);
|
||||||
extern JULIET_API ByteBuffer LoadFile(NonNullPtr<IOStream> stream, bool closeStreamWhenDone);
|
|
||||||
|
|
||||||
extern JULIET_API bool IOClose(NonNullPtr<IOStream> stream);
|
JULIET_API size_t IO_atomic_file_write(Path filepath, ByteBuffer file_content);
|
||||||
} // namespace Juliet
|
|
||||||
|
JULIET_API bool8 IOClose(IOStream* stream);
|
||||||
@@ -2,192 +2,182 @@
|
|||||||
|
|
||||||
#include <Core/Common/CoreTypes.h>
|
#include <Core/Common/CoreTypes.h>
|
||||||
|
|
||||||
namespace Juliet
|
typedef uint32 KeyCode;
|
||||||
|
enum
|
||||||
{
|
{
|
||||||
// Represents a Virtual Key corresponding to the Physical key, but localized using the keyboard layout
|
KeyCode_Unknown = 0x0u,
|
||||||
// ScanCode reprensent US ASCII Keyboard
|
KeyCode_Unsupported = 0x0u,
|
||||||
// WASD Scan codes are ZQSD in KeyCode for French keyboard
|
KeyCode_Return = 0x0Du,
|
||||||
// We use the ASCII value of the generated character as value, when possible.
|
KeyCode_Escape = 0x1Bu,
|
||||||
// Keys that do not produce a character are converted to an abritrary value high enough to not conflict
|
KeyCode_Backspace = 0x08u,
|
||||||
// Reference: https://www.asciitable.com/
|
KeyCode_Tab = 0x09u,
|
||||||
// Reference: https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-6.0/aa299374(v=vs.60)
|
KeyCode_Space = 0x20u,
|
||||||
enum class KeyCode : uint32
|
KeyCode_ExclamationPoint = 0x21u,
|
||||||
{
|
KeyCode_DoubleApostrophe = 0x22u,
|
||||||
Unknown = 0x0, // 0
|
KeyCode_Hash = 0x23u,
|
||||||
Unsupported = 0x0, // 0
|
KeyCode_Dollar = 0x24u,
|
||||||
Return = 0X0Du, // '\r'
|
KeyCode_Percent = 0x25u,
|
||||||
Escape = 0X1Bu, // '\X1B'
|
KeyCode_Ampersand = 0x26u,
|
||||||
Backspace = 0X08u, // '\b'
|
KeyCode_Apostrophe = 0x27u,
|
||||||
Tab = 0X09u, // '\t'
|
KeyCode_LeftParenthesis = 0x28u,
|
||||||
Space = 0X20u, // ' '
|
KeyCode_RightParenthesis = 0x29u,
|
||||||
ExclamationPoint = 0X21u, // '!'
|
KeyCode_Asterisk = 0x2Au,
|
||||||
DoubleApostrophe = 0X22u, // '"'
|
KeyCode_Plus = 0x2Bu,
|
||||||
Hash = 0X23u, // '#'
|
KeyCode_Comma = 0x2Cu,
|
||||||
Dollar = 0X24u, // '$'
|
KeyCode_Minus = 0x2Du,
|
||||||
Percent = 0X25u, // '%'
|
KeyCode_Period = 0x2Eu,
|
||||||
Ampersand = 0X26u, // '&'
|
KeyCode_Slash = 0x2Fu,
|
||||||
Apostrophe = 0X27u, // '\''
|
KeyCode_Num0 = 0x30u,
|
||||||
LeftParenthesis = 0X28u, // '('
|
KeyCode_Num1 = 0x31u,
|
||||||
RightParenthesis = 0X29u, // ')'
|
KeyCode_Num2 = 0x32u,
|
||||||
Asterisk = 0X2Au, // '*'
|
KeyCode_Num3 = 0x33u,
|
||||||
Plus = 0X2Bu, // '+'
|
KeyCode_Num4 = 0x34u,
|
||||||
Comma = 0X2Cu, // ','
|
KeyCode_Num5 = 0x35u,
|
||||||
Minus = 0X2Du, // '-'
|
KeyCode_Num6 = 0x36u,
|
||||||
Period = 0X2Eu, // '.'
|
KeyCode_Num7 = 0x37u,
|
||||||
Slash = 0X2Fu, // '/'
|
KeyCode_Num8 = 0x38u,
|
||||||
Num0 = 0X30u, // '0'
|
KeyCode_Num9 = 0x39u,
|
||||||
Num1 = 0X31u, // '1'
|
KeyCode_Colon = 0x3Au,
|
||||||
Num2 = 0X32u, // '2'
|
KeyCode_Semicolon = 0x3Bu,
|
||||||
Num3 = 0X33u, // '3'
|
KeyCode_LessThan = 0x3Cu,
|
||||||
Num4 = 0X34u, // '4'
|
KeyCode_Equals = 0x3Du,
|
||||||
Num5 = 0X35u, // '5'
|
KeyCode_GreaterThan = 0x3Eu,
|
||||||
Num6 = 0X36u, // '6'
|
KeyCode_QuestionMark = 0x3Fu,
|
||||||
Num7 = 0X37u, // '7'
|
KeyCode_CommercialAt = 0x40u,
|
||||||
Num8 = 0X38u, // '8'
|
KeyCode_LeftBracket = 0x5Bu,
|
||||||
Num9 = 0X39u, // '9'
|
KeyCode_Backslash = 0x5Cu,
|
||||||
Colon = 0X3Au, // ':'
|
KeyCode_RightBracket = 0x5Du,
|
||||||
Semicolon = 0X3Bu, // ';'
|
KeyCode_Caret = 0x5Eu,
|
||||||
LessThan = 0X3Cu, // '<'
|
KeyCode_Underscore = 0x5Fu,
|
||||||
Equals = 0X3Du, // '='
|
KeyCode_GraveAccent = 0x60u,
|
||||||
GreaterThan = 0X3Eu, // '>'
|
KeyCode_A = 0x61u,
|
||||||
QuestionMark = 0X3Fu, // '?'
|
KeyCode_B = 0x62u,
|
||||||
CommercialAt = 0x40u, // '@'
|
KeyCode_C = 0x63u,
|
||||||
LeftBracket = 0X5Bu, // '['
|
KeyCode_D = 0x64u,
|
||||||
Backslash = 0X5Cu, // '\\'
|
KeyCode_E = 0x65u,
|
||||||
RightBracket = 0X5DU, // ']'
|
KeyCode_F = 0x66u,
|
||||||
Caret = 0X5Eu, // '^'
|
KeyCode_G = 0x67u,
|
||||||
Underscore = 0X5Fu, // '_'
|
KeyCode_H = 0x68u,
|
||||||
GraveAccent = 0X60u, // '`'
|
KeyCode_I = 0x69u,
|
||||||
A = 0x61u, // 'a'
|
KeyCode_J = 0x6Au,
|
||||||
B = 0x62u, // 'b'
|
KeyCode_K = 0x6Bu,
|
||||||
C = 0x63u, // 'c'
|
KeyCode_L = 0x6Cu,
|
||||||
D = 0x64u, // 'd'
|
KeyCode_M = 0x6Du,
|
||||||
E = 0x65u, // 'e'
|
KeyCode_N = 0x6Eu,
|
||||||
F = 0x66u, // 'f'
|
KeyCode_O = 0x6Fu,
|
||||||
G = 0x67u, // 'g'
|
KeyCode_P = 0x70u,
|
||||||
H = 0x68u, // 'h'
|
KeyCode_Q = 0x71u,
|
||||||
I = 0x69u, // 'i'
|
KeyCode_R = 0x72u,
|
||||||
J = 0x6Au, // 'j'
|
KeyCode_S = 0x73u,
|
||||||
K = 0x6Bu, // 'k'
|
KeyCode_T = 0x74u,
|
||||||
L = 0x6CU, // 'l'
|
KeyCode_U = 0x75u,
|
||||||
M = 0x6DU, // 'm'
|
KeyCode_V = 0x76u,
|
||||||
N = 0x6Eu, // 'n'
|
KeyCode_W = 0x77u,
|
||||||
O = 0x6Fu, // 'o'
|
KeyCode_X = 0x78u,
|
||||||
P = 0x70u, // 'p'
|
KeyCode_Y = 0x79u,
|
||||||
Q = 0x71u, // 'q'
|
KeyCode_Z = 0x7Au,
|
||||||
R = 0x72u, // 'r'
|
KeyCode_LeftBrace = 0x7Bu,
|
||||||
S = 0x73u, // 's'
|
KeyCode_Pipe = 0x7Cu,
|
||||||
T = 0x74u, // 't'
|
KeyCode_RightBrace = 0x7Du,
|
||||||
U = 0x75u, // 'y'
|
KeyCode_Tilde = 0x7Eu,
|
||||||
V = 0x76u, // 'v'
|
KeyCode_Delete = 0x7Fu,
|
||||||
W = 0x77u, // 'w'
|
KeyCode_PlusMinus = 0xb1u,
|
||||||
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
|
KeyCode_CapsLock = 0x40000039u,
|
||||||
// Based on SDL Algo: ScanCode | 0x40000000
|
KeyCode_F1 = 0x4000003Au,
|
||||||
CapsLock = 0x40000039u,
|
KeyCode_F2 = 0x4000003Bu,
|
||||||
F1 = 0x4000003Au,
|
KeyCode_F3 = 0x4000003Cu,
|
||||||
F2 = 0x4000003Bu,
|
KeyCode_F4 = 0x4000003Du,
|
||||||
F3 = 0x4000003CU,
|
KeyCode_F5 = 0x4000003Eu,
|
||||||
F4 = 0x4000003DU,
|
KeyCode_F6 = 0x4000003Fu,
|
||||||
F5 = 0x4000003Eu,
|
KeyCode_F7 = 0x40000040u,
|
||||||
F6 = 0x4000003Fu,
|
KeyCode_F8 = 0x40000041u,
|
||||||
F7 = 0x40000040u,
|
KeyCode_F9 = 0x40000042u,
|
||||||
F8 = 0x40000041u,
|
KeyCode_F10 = 0x40000043u,
|
||||||
F9 = 0x40000042u,
|
KeyCode_F11 = 0x40000044u,
|
||||||
F10 = 0x40000043u,
|
KeyCode_F12 = 0x40000045u,
|
||||||
F11 = 0x40000044u,
|
KeyCode_PrintScreen = 0x40000046u,
|
||||||
F12 = 0x40000045u,
|
KeyCode_ScrollLock = 0x40000047u,
|
||||||
PrintScreen = 0x40000046u,
|
KeyCode_Pause = 0x40000048u,
|
||||||
ScrollLock = 0x40000047u,
|
KeyCode_Insert = 0x40000049u,
|
||||||
Pause = 0x40000048u,
|
KeyCode_Home = 0x4000004Au,
|
||||||
Insert = 0x40000049u,
|
KeyCode_PageUp = 0x4000004Bu,
|
||||||
Home = 0x4000004Au,
|
KeyCode_End = 0x4000004Du,
|
||||||
PageUp = 0x4000004Bu,
|
KeyCode_PageDown = 0x4000004Eu,
|
||||||
End = 0x4000004DU,
|
KeyCode_RightArrow = 0x4000004Fu,
|
||||||
PageDown = 0x4000004Eu,
|
KeyCode_LeftArrow = 0x40000050u,
|
||||||
RightArrow = 0x4000004Fu,
|
KeyCode_DownArrow = 0x40000051u,
|
||||||
LeftArrow = 0x40000050u,
|
KeyCode_UpArrow = 0x40000052u,
|
||||||
DownArrow = 0x40000051u,
|
KeyCode_NumlockClear = 0x40000053u,
|
||||||
UpArrow = 0x40000052u,
|
KeyCode_KeyPad_Divide = 0x40000054u,
|
||||||
NumlockClear = 0x40000053u,
|
KeyCode_KeyPad_Multiply = 0x40000055u,
|
||||||
KeyPad_Divide = 0x40000054u,
|
KeyCode_KeyPad_Minus = 0x40000056u,
|
||||||
KeyPad_Multiply = 0x40000055u,
|
KeyCode_KeyPad_Plus = 0x40000057u,
|
||||||
KeyPad_Minus = 0x40000056u,
|
KeyCode_KeyPad_Enter = 0x40000058u,
|
||||||
KeyPad_Plus = 0x40000057u,
|
KeyCode_KeyPad_Num1 = 0x40000059u,
|
||||||
KeyPad_Enter = 0x40000058u,
|
KeyCode_KeyPad_Num2 = 0x4000005Au,
|
||||||
KeyPad_Num1 = 0x40000059u,
|
KeyCode_KeyPad_Num3 = 0x4000005Bu,
|
||||||
KeyPad_Num2 = 0x4000005Au,
|
KeyCode_KeyPad_Num4 = 0x4000005Cu,
|
||||||
KeyPad_Num3 = 0x4000005Bu,
|
KeyCode_KeyPad_Num5 = 0x4000005Du,
|
||||||
KeyPad_Num4 = 0x4000005Cu,
|
KeyCode_KeyPad_Num6 = 0x4000005Eu,
|
||||||
KeyPad_Num5 = 0x4000005Du,
|
KeyCode_KeyPad_Num7 = 0x4000005Fu,
|
||||||
KeyPad_Num6 = 0x4000005Eu,
|
KeyCode_KeyPad_Num8 = 0x40000060u,
|
||||||
KeyPad_Num7 = 0x4000005Fu,
|
KeyCode_KeyPad_Num9 = 0x40000061u,
|
||||||
KeyPad_Num8 = 0x40000060u,
|
KeyCode_KeyPad_Num0 = 0x40000062u,
|
||||||
KeyPad_Num9 = 0x40000061u,
|
KeyCode_KeyPad_Period = 0x40000063u,
|
||||||
KeyPad_Num0 = 0x40000062u,
|
KeyCode_Power = 0x40000066u,
|
||||||
KeyPad_Period = 0x40000063u,
|
KeyCode_KeyPad_Equals = 0x40000067u,
|
||||||
Power = 0x40000066u,
|
KeyCode_F13 = 0x40000068u,
|
||||||
KeyPad_Equals = 0x40000067u,
|
KeyCode_F14 = 0x40000069u,
|
||||||
F13 = 0x40000068u,
|
KeyCode_F15 = 0x4000006Au,
|
||||||
F14 = 0x40000069u,
|
KeyCode_F16 = 0x4000006Bu,
|
||||||
F15 = 0x4000006Au,
|
KeyCode_F17 = 0x4000006Cu,
|
||||||
F16 = 0x4000006Bu,
|
KeyCode_F18 = 0x4000006Du,
|
||||||
F17 = 0x4000006Cu,
|
KeyCode_F19 = 0x4000006Eu,
|
||||||
F18 = 0x4000006Du,
|
KeyCode_F20 = 0x4000006Fu,
|
||||||
F19 = 0x4000006Eu,
|
KeyCode_F21 = 0x40000070u,
|
||||||
F20 = 0x4000006Fu,
|
KeyCode_F22 = 0x40000071u,
|
||||||
F21 = 0x40000070u,
|
KeyCode_F23 = 0x40000072u,
|
||||||
F22 = 0x40000071u,
|
KeyCode_F24 = 0x40000073u,
|
||||||
F23 = 0x40000072u,
|
KeyCode_Mute = 0x4000007Fu,
|
||||||
F24 = 0x40000073u,
|
KeyCode_VolumeUp = 0x40000080u,
|
||||||
Mute = 0x4000007Fu,
|
KeyCode_VolumeDown = 0x40000081u,
|
||||||
VolumeUp = 0x40000080u,
|
KeyCode_KeyPad_Comma = 0x40000085u,
|
||||||
VolumeDown = 0x40000081u,
|
KeyCode_LeftControl = 0x400000E0u,
|
||||||
KeyPad_Comma = 0x40000085u,
|
KeyCode_LeftShift = 0x400000E1u,
|
||||||
LeftControl = 0x400000E0u,
|
KeyCode_LeftAlt = 0x400000E2u,
|
||||||
LeftShift = 0x400000E1u,
|
KeyCode_LeftOSCommand = 0x400000E3u,
|
||||||
LeftAlt = 0x400000E2u,
|
KeyCode_RightControl = 0x400000E4u,
|
||||||
LeftOSCommand = 0x400000E3u,
|
KeyCode_RightShift = 0x400000E5u,
|
||||||
RightControl = 0x400000E4u,
|
KeyCode_RightAlt = 0x400000E6u,
|
||||||
RightShift = 0x400000E5u,
|
KeyCode_RightOSCommand = 0x400000E7u,
|
||||||
RightAlt = 0x400000E6u,
|
KeyCode_Sleep = 0x40000102u,
|
||||||
RightOSCommand = 0x400000E7u,
|
KeyCode_WakeUp = 0x40000103u,
|
||||||
Sleep = 0x40000102u,
|
KeyCode_Media_NextTrack = 0x4000010Bu,
|
||||||
WakeUp = 0x40000103u,
|
KeyCode_Media_PreviousTrack = 0x4000010Cu,
|
||||||
Media_NextTrack = 0x4000010Bu,
|
KeyCode_Media_Stop = 0x4000010Du,
|
||||||
Media_PreviousTrack = 0x4000010Cu,
|
KeyCode_Media_Eject = 0x4000010Eu,
|
||||||
Media_Stop = 0x4000010Du,
|
KeyCode_Media_PlayPause = 0x4000010Fu,
|
||||||
Media_Eject = 0x4000010Eu,
|
KeyCode_Media_Select = 0x40000110u,
|
||||||
Media_PlayPause = 0x4000010Fu,
|
};
|
||||||
Media_Select = 0x40000110u,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class KeyMod : uint16
|
typedef uint16 KeyMod;
|
||||||
{
|
enum
|
||||||
None = 0b0,
|
{
|
||||||
LeftShift = 0b0000'0000'0001u,
|
KeyMod_None = 0x0000u,
|
||||||
RightShift = 0b0000'0000'0010u,
|
KeyMod_LeftShift = 0x0001u,
|
||||||
LeftControl = 0b0000'0000'0100u,
|
KeyMod_RightShift = 0x0002u,
|
||||||
RightControl = 0b0000'0000'1000u,
|
KeyMod_LeftControl = 0x0004u,
|
||||||
LeftAlt = 0b0000'0001'000u,
|
KeyMod_RightControl = 0x0008u,
|
||||||
RightAlt = 0b0000'0010'0000u,
|
KeyMod_LeftAlt = 0x0010u,
|
||||||
LeftOSCommand = 0b0000'0100'0000u,
|
KeyMod_RightAlt = 0x0020u,
|
||||||
RightOSCommand = 0b0000'1000'0000u,
|
KeyMod_LeftOSCommand = 0x0040u,
|
||||||
NumLock = 0b0001'0000'0000u,
|
KeyMod_RightOSCommand = 0x0080u,
|
||||||
CapsLock = 0b0010'0000'0000u,
|
KeyMod_NumLock = 0x0100u,
|
||||||
ScrollLock = 0b0100'0000'0000u,
|
KeyMod_CapsLock = 0x0200u,
|
||||||
Control = LeftControl | RightControl,
|
KeyMod_ScrollLock = 0x0400u,
|
||||||
Shift = LeftShift | RightShift,
|
KeyMod_Control = KeyMod_LeftControl | KeyMod_RightControl,
|
||||||
Alt = LeftAlt | RightAlt,
|
KeyMod_Shift = KeyMod_LeftShift | KeyMod_RightShift,
|
||||||
OSCommand = LeftOSCommand | RightOSCommand,
|
KeyMod_Alt = KeyMod_LeftAlt | KeyMod_RightAlt,
|
||||||
};
|
KeyMod_OSCommand = KeyMod_LeftOSCommand | KeyMod_RightOSCommand,
|
||||||
} // namespace Juliet
|
};
|
||||||
@@ -4,25 +4,32 @@
|
|||||||
#include <Core/HAL/Keyboard/ScanCode.h>
|
#include <Core/HAL/Keyboard/ScanCode.h>
|
||||||
#include <Juliet.h>
|
#include <Juliet.h>
|
||||||
|
|
||||||
namespace Juliet
|
typedef uint8 KeyboardID;
|
||||||
|
|
||||||
|
typedef bool8 KeyPosition;
|
||||||
|
enum
|
||||||
{
|
{
|
||||||
using KeyboardID = uint8;
|
KeyPosition_Up = false,
|
||||||
|
KeyPosition_Down = true
|
||||||
|
};
|
||||||
|
|
||||||
enum class KeyState : bool
|
typedef struct KeyState KeyState;
|
||||||
{
|
struct KeyState
|
||||||
Up = false,
|
{
|
||||||
Down = true
|
KeyPosition Position;
|
||||||
};
|
float Time;
|
||||||
|
};
|
||||||
|
|
||||||
struct Key
|
typedef struct Key Key;
|
||||||
{
|
struct Key
|
||||||
|
{
|
||||||
ScanCode ScanCode;
|
ScanCode ScanCode;
|
||||||
KeyCode KeyCode;
|
KeyCode KeyCode;
|
||||||
uint16 Raw;
|
uint16 Raw;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern JULIET_API bool IsKeyDown(ScanCode scanCode);
|
extern JULIET_API bool8 IsKeyDown(ScanCode scanCode);
|
||||||
|
extern JULIET_API bool8 IsKeyPressed(ScanCode scanCode);
|
||||||
|
|
||||||
extern JULIET_API KeyMod GetKeyModState();
|
extern JULIET_API KeyMod GetKeyModState(void);
|
||||||
extern JULIET_API KeyCode GetKeyCodeFromScanCode(ScanCode scanCode, KeyMod keyModState);
|
extern JULIET_API KeyCode GetKeyCodeFromScanCode(ScanCode scanCode, KeyMod keyModState);
|
||||||
} // namespace Juliet
|
|
||||||
@@ -1,186 +1,180 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
namespace Juliet
|
#include <Core/Common/CoreTypes.h>
|
||||||
|
|
||||||
|
typedef uint16 ScanCode;
|
||||||
|
enum
|
||||||
{
|
{
|
||||||
// Follow the HID Usage page for USB
|
ScanCode_Unknown = 0,
|
||||||
// https://usb.org/sites/default/files/hut1_5.pdf
|
ScanCode_Unsupported = 0,
|
||||||
// 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,
|
|
||||||
|
|
||||||
A = 4,
|
ScanCode_A = 4,
|
||||||
B = 5,
|
ScanCode_B = 5,
|
||||||
C = 6,
|
ScanCode_C = 6,
|
||||||
D = 7,
|
ScanCode_D = 7,
|
||||||
E = 8,
|
ScanCode_E = 8,
|
||||||
F = 9,
|
ScanCode_F = 9,
|
||||||
G = 10,
|
ScanCode_G = 10,
|
||||||
H = 11,
|
ScanCode_H = 11,
|
||||||
I = 12,
|
ScanCode_I = 12,
|
||||||
J = 13,
|
ScanCode_J = 13,
|
||||||
K = 14,
|
ScanCode_K = 14,
|
||||||
L = 15,
|
ScanCode_L = 15,
|
||||||
M = 16,
|
ScanCode_M = 16,
|
||||||
N = 17,
|
ScanCode_N = 17,
|
||||||
O = 18,
|
ScanCode_O = 18,
|
||||||
P = 19,
|
ScanCode_P = 19,
|
||||||
Q = 20,
|
ScanCode_Q = 20,
|
||||||
R = 21,
|
ScanCode_R = 21,
|
||||||
S = 22,
|
ScanCode_S = 22,
|
||||||
T = 23,
|
ScanCode_T = 23,
|
||||||
U = 24,
|
ScanCode_U = 24,
|
||||||
V = 25,
|
ScanCode_V = 25,
|
||||||
W = 26,
|
ScanCode_W = 26,
|
||||||
X = 27,
|
ScanCode_X = 27,
|
||||||
Y = 28,
|
ScanCode_Y = 28,
|
||||||
Z = 29,
|
ScanCode_Z = 29,
|
||||||
|
|
||||||
Num1 = 30,
|
ScanCode_Num1 = 30,
|
||||||
Num2 = 31,
|
ScanCode_Num2 = 31,
|
||||||
Num3 = 32,
|
ScanCode_Num3 = 32,
|
||||||
Num4 = 33,
|
ScanCode_Num4 = 33,
|
||||||
Num5 = 34,
|
ScanCode_Num5 = 34,
|
||||||
Num6 = 35,
|
ScanCode_Num6 = 35,
|
||||||
Num7 = 36,
|
ScanCode_Num7 = 36,
|
||||||
Num8 = 37,
|
ScanCode_Num8 = 37,
|
||||||
Num9 = 38,
|
ScanCode_Num9 = 38,
|
||||||
Num0 = 39,
|
ScanCode_Num0 = 39,
|
||||||
|
|
||||||
Return = 40,
|
ScanCode_Return = 40,
|
||||||
Escape = 41,
|
ScanCode_Escape = 41,
|
||||||
Backspace = 42,
|
ScanCode_Backspace = 42,
|
||||||
Tab = 43,
|
ScanCode_Tab = 43,
|
||||||
Space = 44,
|
ScanCode_Space = 44,
|
||||||
|
|
||||||
Minus = 45,
|
ScanCode_Minus = 45,
|
||||||
Equals = 46,
|
ScanCode_Equals = 46,
|
||||||
LeftBracket = 47,
|
ScanCode_LeftBracket = 47,
|
||||||
RightBracket = 48,
|
ScanCode_RightBracket = 48,
|
||||||
Backslash = 49,
|
ScanCode_Backslash = 49,
|
||||||
NonUSHash = 50, // Same as 49 but for ISO keyboards
|
ScanCode_NonUSHash = 50,
|
||||||
Semicolon = 51,
|
ScanCode_Semicolon = 51,
|
||||||
Apostrophe = 52,
|
ScanCode_Apostrophe = 52,
|
||||||
GraveAccent = 53,
|
ScanCode_GraveAccent = 53,
|
||||||
Comma = 54,
|
ScanCode_Comma = 54,
|
||||||
Period = 55,
|
ScanCode_Period = 55,
|
||||||
Slash = 56,
|
ScanCode_Slash = 56,
|
||||||
CapsLock = 57,
|
ScanCode_CapsLock = 57,
|
||||||
|
|
||||||
F1 = 58,
|
ScanCode_F1 = 58,
|
||||||
F2 = 59,
|
ScanCode_F2 = 59,
|
||||||
F3 = 60,
|
ScanCode_F3 = 60,
|
||||||
F4 = 61,
|
ScanCode_F4 = 61,
|
||||||
F5 = 62,
|
ScanCode_F5 = 62,
|
||||||
F6 = 63,
|
ScanCode_F6 = 63,
|
||||||
F7 = 64,
|
ScanCode_F7 = 64,
|
||||||
F8 = 65,
|
ScanCode_F8 = 65,
|
||||||
F9 = 66,
|
ScanCode_F9 = 66,
|
||||||
F10 = 67,
|
ScanCode_F10 = 67,
|
||||||
F11 = 68,
|
ScanCode_F11 = 68,
|
||||||
F12 = 69,
|
ScanCode_F12 = 69,
|
||||||
|
|
||||||
PrintScreen = 70,
|
ScanCode_PrintScreen = 70,
|
||||||
ScrollLock = 71,
|
ScanCode_ScrollLock = 71,
|
||||||
Pause = 72,
|
ScanCode_Pause = 72,
|
||||||
Insert = 73,
|
ScanCode_Insert = 73,
|
||||||
|
|
||||||
Home = 74,
|
ScanCode_Home = 74,
|
||||||
PageUp = 75,
|
ScanCode_PageUp = 75,
|
||||||
Delete = 76,
|
ScanCode_Delete = 76,
|
||||||
End = 77,
|
ScanCode_End = 77,
|
||||||
PageDown = 78,
|
ScanCode_PageDown = 78,
|
||||||
RightArrow = 79,
|
ScanCode_RightArrow = 79,
|
||||||
LeftArrow = 80,
|
ScanCode_LeftArrow = 80,
|
||||||
DownArrow = 81,
|
ScanCode_DownArrow = 81,
|
||||||
UpArrow = 82,
|
ScanCode_UpArrow = 82,
|
||||||
|
|
||||||
NumlockClear = 83, // Pc = Numlock / Mac = Clear
|
ScanCode_NumlockClear = 83,
|
||||||
|
|
||||||
KeyPad_Divide = 84,
|
ScanCode_KeyPad_Divide = 84,
|
||||||
KeyPad_Multiply = 85,
|
ScanCode_KeyPad_Multiply = 85,
|
||||||
KeyPad_Minus = 86,
|
ScanCode_KeyPad_Minus = 86,
|
||||||
KeyPad_Plus = 87,
|
ScanCode_KeyPad_Plus = 87,
|
||||||
KeyPad_Enter = 88,
|
ScanCode_KeyPad_Enter = 88,
|
||||||
KeyPad_Num1 = 89,
|
ScanCode_KeyPad_Num1 = 89,
|
||||||
KeyPad_Num2 = 90,
|
ScanCode_KeyPad_Num2 = 90,
|
||||||
KeyPad_Num3 = 91,
|
ScanCode_KeyPad_Num3 = 91,
|
||||||
KeyPad_Num4 = 92,
|
ScanCode_KeyPad_Num4 = 92,
|
||||||
KeyPad_Num5 = 93,
|
ScanCode_KeyPad_Num5 = 93,
|
||||||
KeyPad_Num6 = 94,
|
ScanCode_KeyPad_Num6 = 94,
|
||||||
KeyPad_Num7 = 95,
|
ScanCode_KeyPad_Num7 = 95,
|
||||||
KeyPad_Num8 = 96,
|
ScanCode_KeyPad_Num8 = 96,
|
||||||
KeyPad_Num9 = 97,
|
ScanCode_KeyPad_Num9 = 97,
|
||||||
KeyPad_Num0 = 98,
|
ScanCode_KeyPad_Num0 = 98,
|
||||||
KeyPad_Period = 99,
|
ScanCode_KeyPad_Period = 99,
|
||||||
|
|
||||||
NonUSBackslash = 100, // ISO keyboards only
|
ScanCode_NonUSBackslash = 100,
|
||||||
Power = 102, // Some mac have a Power key
|
ScanCode_Power = 102,
|
||||||
|
|
||||||
KeyPad_Equals = 103,
|
ScanCode_KeyPad_Equals = 103,
|
||||||
F13 = 104,
|
ScanCode_F13 = 104,
|
||||||
F14 = 105,
|
ScanCode_F14 = 105,
|
||||||
F15 = 106,
|
ScanCode_F15 = 106,
|
||||||
F16 = 107,
|
ScanCode_F16 = 107,
|
||||||
F17 = 108,
|
ScanCode_F17 = 108,
|
||||||
F18 = 109,
|
ScanCode_F18 = 109,
|
||||||
F19 = 110,
|
ScanCode_F19 = 110,
|
||||||
F20 = 111,
|
ScanCode_F20 = 111,
|
||||||
F21 = 112,
|
ScanCode_F21 = 112,
|
||||||
F22 = 113,
|
ScanCode_F22 = 113,
|
||||||
F23 = 114,
|
ScanCode_F23 = 114,
|
||||||
F24 = 115,
|
ScanCode_F24 = 115,
|
||||||
|
|
||||||
Mute = 127,
|
ScanCode_Mute = 127,
|
||||||
VolumeUp = 128,
|
ScanCode_VolumeUp = 128,
|
||||||
VolumeDown = 129,
|
ScanCode_VolumeDown = 129,
|
||||||
|
|
||||||
KeyPad_Comma = 133,
|
ScanCode_KeyPad_Comma = 133,
|
||||||
|
|
||||||
International1 = 135, // Mostly used on Asian keyboards
|
ScanCode_International1 = 135,
|
||||||
International2 = 136,
|
ScanCode_International2 = 136,
|
||||||
International3 = 137, // Yen Symbol
|
ScanCode_International3 = 137,
|
||||||
International4 = 138,
|
ScanCode_International4 = 138,
|
||||||
International5 = 139,
|
ScanCode_International5 = 139,
|
||||||
International6 = 140,
|
ScanCode_International6 = 140,
|
||||||
International7 = 141,
|
ScanCode_International7 = 141,
|
||||||
International8 = 142,
|
ScanCode_International8 = 142,
|
||||||
International9 = 143,
|
ScanCode_International9 = 143,
|
||||||
Lang1 = 144, // Hangul (Korean)
|
ScanCode_Lang1 = 144,
|
||||||
Lang2 = 145, // Hanja (Korean)
|
ScanCode_Lang2 = 145,
|
||||||
Lang3 = 146, // Katakana (Japanese)
|
ScanCode_Lang3 = 146,
|
||||||
Lang4 = 147, // Hiragana (Japanese)
|
ScanCode_Lang4 = 147,
|
||||||
Lang5 = 148, // Zenkaku/Hankaku (Japanese)
|
ScanCode_Lang5 = 148,
|
||||||
Lang6 = 149, // Unused
|
ScanCode_Lang6 = 149,
|
||||||
Lang7 = 150, // Unused
|
ScanCode_Lang7 = 150,
|
||||||
Lang8 = 151, // Unused
|
ScanCode_Lang8 = 151,
|
||||||
Lang9 = 152, // Unused
|
ScanCode_Lang9 = 152,
|
||||||
|
|
||||||
LeftControl = 224,
|
ScanCode_LeftControl = 224,
|
||||||
LeftShift = 225,
|
ScanCode_LeftShift = 225,
|
||||||
LeftAlt = 226, // Alt for PC, Option for Mac
|
ScanCode_LeftAlt = 226,
|
||||||
LeftOSCommand = 227, // Window key for PC, Command for Mac
|
ScanCode_LeftOSCommand = 227,
|
||||||
RightControl = 228,
|
ScanCode_RightControl = 228,
|
||||||
RightShift = 229,
|
ScanCode_RightShift = 229,
|
||||||
RightAlt = 230, // Alt Gr for PC, Option for Mac
|
ScanCode_RightAlt = 230,
|
||||||
RightOSCommand = 231, // Window key for PC, Command for Mac
|
ScanCode_RightOSCommand = 231,
|
||||||
|
|
||||||
Sleep = 258,
|
ScanCode_Sleep = 258,
|
||||||
WakeUp = 259,
|
ScanCode_WakeUp = 259,
|
||||||
|
|
||||||
Media_NextTrack = 267,
|
ScanCode_Media_NextTrack = 267,
|
||||||
Media_PreviousTrack = 268,
|
ScanCode_Media_PreviousTrack = 268,
|
||||||
Media_Stop = 269,
|
ScanCode_Media_Stop = 269,
|
||||||
Media_Eject = 270,
|
ScanCode_Media_Eject = 270,
|
||||||
Media_PlayPause = 271,
|
ScanCode_Media_PlayPause = 271,
|
||||||
Media_Select = 272,
|
ScanCode_Media_Select = 272,
|
||||||
|
|
||||||
Reserved = 287,
|
ScanCode_Reserved = 287,
|
||||||
|
|
||||||
Count = 512
|
ScanCode_Count = 512
|
||||||
};
|
};
|
||||||
} // namespace Juliet
|
|
||||||
@@ -1,27 +1,29 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
namespace Juliet
|
#include <Core/Common/CoreTypes.h>
|
||||||
|
#include <Juliet.h>
|
||||||
|
|
||||||
|
typedef uint8 MouseID;
|
||||||
|
|
||||||
|
typedef uint8 MouseButton;
|
||||||
|
enum
|
||||||
{
|
{
|
||||||
using MouseID = uint8;
|
MouseButton_None = 0,
|
||||||
|
MouseButton_Left = 1 << 0,
|
||||||
|
MouseButton_Right = 1 << 1,
|
||||||
|
MouseButton_Middle = 1 << 2,
|
||||||
|
MouseButton_Button1 = 1 << 3,
|
||||||
|
MouseButton_Button2 = 1 << 4,
|
||||||
|
};
|
||||||
|
|
||||||
enum class MouseButton : uint8
|
typedef struct MousePosition MousePosition;
|
||||||
{
|
struct MousePosition
|
||||||
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 X;
|
||||||
float Y;
|
float Y;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern bool IsMouseButtonDown(MouseButton button);
|
JULIET_API extern bool8 IsMouseButtonDown(MouseButton button);
|
||||||
extern MousePosition GetMousePosition();
|
JULIET_API extern MousePosition GetMousePosition(void);
|
||||||
extern MouseButton GetMouseButtonState();
|
JULIET_API extern MousePosition GetMouseDelta(void);
|
||||||
} // namespace Juliet
|
JULIET_API extern MouseButton GetMouseButtonState(void);
|
||||||
@@ -1,41 +1,23 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Core/Common/CoreTypes.h>
|
#include <Core/Common/String.h>
|
||||||
#include <Juliet.h>
|
#include <Core/Memory/MemoryArena.h>
|
||||||
|
|
||||||
namespace Juliet
|
Byte* OS_Reserve(size_t size);
|
||||||
{
|
bool8 OS_Commit(void* ptr, size_t size);
|
||||||
namespace Memory
|
void OS_Release(void* ptr, 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);
|
|
||||||
|
|
||||||
template <typename Type>
|
uint64 Time_Timestamp(void);
|
||||||
Type* OS_Reserve(size_t size)
|
void Time_ComputeDeltaTime(void);
|
||||||
{
|
float Time_GetDeltaTime(void);
|
||||||
return reinterpret_cast<Type*>(OS_Reserve(size));
|
uint64 Time_GetFrameNumber(void);
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Type>
|
JULIET_API bool8 Debug_IsDebuggerPresent(void);
|
||||||
bool OS_Commit(Type* ptr, size_t size)
|
|
||||||
{
|
|
||||||
return OS_Commit(reinterpret_cast<Byte*>(ptr), size);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Type>
|
String os_get_env_var(Arena* arena, String name);
|
||||||
void OS_Release(Type* ptr, size_t size)
|
void os_set_env_var(String name, String value);
|
||||||
{
|
|
||||||
OS_Release(reinterpret_cast<Byte*>(ptr), size);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Memory
|
String os_get_username(Arena* arena);
|
||||||
|
|
||||||
namespace Debug
|
typedef int (*EntryPointFunc)(int, wchar_t**);
|
||||||
{
|
JULIET_API int Bootstrap(EntryPointFunc entryPointFunc, int argc, wchar_t** argv);
|
||||||
JULIET_API bool IsDebuggerPresent();
|
|
||||||
} // namespace Debug
|
|
||||||
|
|
||||||
using EntryPointFunc = int (*)(int, wchar_t**);
|
|
||||||
JULIET_API int Bootstrap(EntryPointFunc entryPointFunc, int argc, wchar_t** argv);
|
|
||||||
} // namespace Juliet
|
|
||||||
@@ -2,15 +2,11 @@
|
|||||||
|
|
||||||
#include <Core/Common/String.h>
|
#include <Core/Common/String.h>
|
||||||
|
|
||||||
namespace Juliet
|
typedef struct DynamicLibrary DynamicLibrary;
|
||||||
|
typedef struct HotReloadCode HotReloadCode;
|
||||||
|
|
||||||
|
struct HotReloadCode
|
||||||
{
|
{
|
||||||
// Fwd Declare
|
|
||||||
struct DynamicLibrary;
|
|
||||||
|
|
||||||
struct HotReloadCode
|
|
||||||
{
|
|
||||||
Arena* Arena;
|
|
||||||
|
|
||||||
String DLLFullPath;
|
String DLLFullPath;
|
||||||
String LockFullPath;
|
String LockFullPath;
|
||||||
String TransientDLLName;
|
String TransientDLLName;
|
||||||
@@ -25,15 +21,15 @@ namespace Juliet
|
|||||||
|
|
||||||
uint32 UniqueID;
|
uint32 UniqueID;
|
||||||
|
|
||||||
bool IsValid : 1;
|
bool8 IsValid;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern JULIET_API void InitHotReloadCode(HotReloadCode& code, String dllName, String transientDllName, String lockFilename);
|
extern JULIET_API void InitHotReloadCode(Arena* arena, HotReloadCode* code, String dllName,
|
||||||
extern JULIET_API void ShutdownHotReloadCode(HotReloadCode& code);
|
String transientDllName, String lockFilename);
|
||||||
|
extern JULIET_API void ShutdownHotReloadCode(HotReloadCode* code);
|
||||||
|
|
||||||
extern JULIET_API void LoadCode(HotReloadCode& code);
|
extern JULIET_API void LoadCode(HotReloadCode* code);
|
||||||
extern JULIET_API void UnloadCode(HotReloadCode& code);
|
extern JULIET_API void UnloadCode(HotReloadCode* code);
|
||||||
|
|
||||||
extern JULIET_API void ReloadCode(HotReloadCode& code);
|
extern JULIET_API void ReloadCode(HotReloadCode* code);
|
||||||
extern JULIET_API bool ShouldReloadCode(const HotReloadCode& code);
|
extern JULIET_API bool8 ShouldReloadCode(const HotReloadCode* code);
|
||||||
} // namespace Juliet
|
|
||||||
@@ -1,31 +1,24 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Core/Common/CoreTypes.h>
|
#include <Core/Common/CoreTypes.h>
|
||||||
#include <Core/Common/NonNullPtr.h>
|
#include <Juliet.h>
|
||||||
|
|
||||||
#ifdef JULIET_ENABLE_IMGUI
|
#ifdef JULIET_ENABLE_IMGUI
|
||||||
|
|
||||||
struct ImGuiContext;
|
typedef struct ImGuiContext ImGuiContext;
|
||||||
|
typedef struct Window Window;
|
||||||
|
typedef struct GraphicsDevice GraphicsDevice;
|
||||||
|
|
||||||
namespace Juliet
|
JULIET_API void ImGuiService_Initialize(Window* window);
|
||||||
{
|
JULIET_API void ImGuiService_Shutdown(void);
|
||||||
struct Window;
|
|
||||||
struct GraphicsDevice;
|
|
||||||
|
|
||||||
namespace ImGuiService
|
JULIET_API void ImGuiService_NewFrame(void);
|
||||||
{
|
JULIET_API void ImGuiService_Render(void);
|
||||||
JULIET_API void Initialize(NonNullPtr<Window> window);
|
|
||||||
JULIET_API void Shutdown();
|
|
||||||
|
|
||||||
JULIET_API void NewFrame();
|
JULIET_API bool8 ImGuiService_IsInitialized(void);
|
||||||
JULIET_API void Render();
|
JULIET_API ImGuiContext* ImGuiService_GetContext(void);
|
||||||
|
|
||||||
JULIET_API bool IsInitialized();
|
// Run internal unit tests
|
||||||
JULIET_API ImGuiContext* GetContext();
|
JULIET_API void ImGuiService_RunTests(void);
|
||||||
|
|
||||||
// Run internal unit tests
|
|
||||||
JULIET_API void RunTests();
|
|
||||||
} // namespace ImGuiService
|
|
||||||
} // namespace Juliet
|
|
||||||
|
|
||||||
#endif // JULIET_ENABLE_IMGUI
|
#endif // JULIET_ENABLE_IMGUI
|
||||||
@@ -1,12 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Core/Common/NonNullPtr.h>
|
|
||||||
#include <Core/HAL/Display/Window.h>
|
|
||||||
#include <Graphics/GraphicsDevice.h>
|
|
||||||
|
|
||||||
#include <Juliet.h>
|
#include <Juliet.h>
|
||||||
|
|
||||||
namespace Juliet::UnitTest
|
void UnitTest_TestImGui(void);
|
||||||
{
|
|
||||||
void TestImGui();
|
|
||||||
}
|
|
||||||
Loaded 100 of 452 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user