Compare commits
23 commits
main
...
ci/build_m
| Author | SHA1 | Date | |
|---|---|---|---|
| 7d02876d12 | |||
| a74e23051c | |||
| 9909f5c430 | |||
| 6ecb268196 | |||
| 144ca1aa4f | |||
| 3520ae36a6 | |||
| 57031ee44e | |||
| 90a1a06bbe | |||
| 9662f97095 | |||
| 8f12d76401 | |||
| cb5b97dddb | |||
| 253a8216a2 | |||
| bf2bf1e1a1 | |||
| f21e6450cd | |||
| d1caf1df5b | |||
| bcfbc5c1c1 | |||
| 48bf9eb033 | |||
| 1bdfa1fac0 | |||
| 8df6e7967f | |||
| 034a6c7537 | |||
| b1bca72f6f | |||
| 306b65df94 | |||
| e59b3b8d3a |
349 changed files with 53184 additions and 13244 deletions
5
.clangd
5
.clangd
|
|
@ -1,5 +0,0 @@
|
|||
CompileFlags:
|
||||
DriverMode: cl
|
||||
Add:
|
||||
- /EHsc
|
||||
- /std:c++latest
|
||||
103
.drone.yml
103
.drone.yml
|
|
@ -13,7 +13,7 @@ steps:
|
|||
- name: unit tests
|
||||
shell: powershell
|
||||
commands:
|
||||
- ./tools/ci/amd64/msvc/unit_tests.ps1
|
||||
- ./tools/ci/steps/amd64/msvc/unit-tests.ps1
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
|
|
@ -25,16 +25,16 @@ trigger:
|
|||
|
||||
steps:
|
||||
- name: unit tests
|
||||
image: ci:latest
|
||||
image: unit_tests:latest
|
||||
pull: if-not-exists
|
||||
commands:
|
||||
- ./tools/ci/amd64/gcc/unit_tests.sh
|
||||
- ./tools/ci/steps/amd64/gcc/unit-tests.sh
|
||||
|
||||
- name: valgrind
|
||||
image: ci:latest
|
||||
image: valgrind:latest
|
||||
pull: if-not-exists
|
||||
commands:
|
||||
- ./tools/ci/amd64/gcc/valgrind.sh
|
||||
- ./tools/ci/steps/amd64/gcc/valgrind.sh
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
|
|
@ -45,26 +45,11 @@ trigger:
|
|||
- main
|
||||
|
||||
steps:
|
||||
- name: code coverage
|
||||
image: ci:latest
|
||||
pull: if-not-exists
|
||||
environment:
|
||||
CODECOV_TOKEN:
|
||||
from_secret: CODECOV_TOKEN
|
||||
commands:
|
||||
- ./tools/ci/amd64/clang/coverage.sh
|
||||
|
||||
- name: leak sanitizer
|
||||
image: ci:latest
|
||||
image: leak_sanitizer:latest
|
||||
pull: if-not-exists
|
||||
commands:
|
||||
- ./tools/ci/amd64/clang/lsan.sh
|
||||
|
||||
- name: memory sanitizer
|
||||
image: ci:latest
|
||||
pull: if-not-exists
|
||||
commands:
|
||||
- ./tools/ci/amd64/clang/msan.sh
|
||||
- ./tools/ci/steps/amd64/clang/lsan.sh
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
|
|
@ -75,84 +60,24 @@ trigger:
|
|||
- main
|
||||
|
||||
steps:
|
||||
- name: clang tidy
|
||||
image: ci:latest
|
||||
- name: static_analysis
|
||||
image: static_analysis:latest
|
||||
pull: if-not-exists
|
||||
privileged: true
|
||||
commands:
|
||||
- ./tools/ci/static_analysis/clang_tidy.sh
|
||||
|
||||
- name: shell check
|
||||
image: ci:latest
|
||||
pull: if-not-exists
|
||||
commands:
|
||||
- ./tools/ci/static_analysis/shell_check.sh
|
||||
|
||||
- name: clang format
|
||||
image: ci:latest
|
||||
pull: if-not-exists
|
||||
commands:
|
||||
- ./tools/ci/static_analysis/clang_format.sh
|
||||
|
||||
- name: cmake format
|
||||
image: ci:latest
|
||||
pull: if-not-exists
|
||||
commands:
|
||||
- ./tools/ci/static_analysis/cmake_format.sh
|
||||
|
||||
- name: shell format
|
||||
image: ci:latest
|
||||
pull: if-not-exists
|
||||
commands:
|
||||
- ./tools/ci/static_analysis/shell_format.sh
|
||||
- ./tools/ci/steps/static_analysis.sh
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: documentation — development
|
||||
node:
|
||||
environment: ryali
|
||||
name: style
|
||||
trigger:
|
||||
branch:
|
||||
- main
|
||||
|
||||
steps:
|
||||
- name: build and deploy
|
||||
image: documentation:latest
|
||||
- name: clang format
|
||||
image: clang_format:latest
|
||||
pull: if-not-exists
|
||||
commands:
|
||||
- pwd
|
||||
- cd docs
|
||||
- mkdir generated
|
||||
- touch generated/changelogs.rst
|
||||
- touch generated/api.rst
|
||||
- sphinx-build -M html . .
|
||||
|
||||
- rm -rf /light_docs_dev/*
|
||||
- mv ./html/* /light_docs_dev/
|
||||
|
||||
---
|
||||
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: documentation — production
|
||||
node:
|
||||
environment: ryali
|
||||
trigger:
|
||||
event:
|
||||
- tag
|
||||
|
||||
steps:
|
||||
- name: build and deploy
|
||||
image: documentation:latest
|
||||
pull: if-not-exists
|
||||
commands:
|
||||
- cd docs
|
||||
- mkdir generated
|
||||
- touch generated/changelogs.rst
|
||||
- touch generated/api.rst
|
||||
- sphinx-build -M html . .
|
||||
|
||||
- rm -rf /light_docs/*
|
||||
- mv ./html/* /light_docs/
|
||||
|
||||
- ./tools/ci/steps/style.sh
|
||||
|
|
|
|||
|
|
@ -1,8 +1,16 @@
|
|||
cmake_minimum_required(VERSION 3.14)
|
||||
project(Light)
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake/functions.cmake)
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake/definitions.cmake)
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake/options.cmake)
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake/dependencies.cmake)
|
||||
|
||||
add_option(ENABLE_STATIC_ANALYSIS "Performs static analysis via clang-tidy and fails build on failing checks")
|
||||
if (ENABLE_STATIC_ANALYSIS)
|
||||
set(CMAKE_CXX_CLANG_TIDY "clang-tidy;--warnings-as-errors=*;--allow-no-checks")
|
||||
endif ()
|
||||
|
||||
add_option(ENABLE_TESTS "Enables the building of the test modules")
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/modules)
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/external/glad)
|
||||
|
|
|
|||
128
CODE_OF_CONDUCT.md
Normal file
128
CODE_OF_CONDUCT.md
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
Discord: Light7734#4652.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
|
|
@ -1,4 +1,2 @@
|
|||
# Light
|
||||
See docs.light7734.com for a comprehensive project documentation
|
||||
|
||||
###### “No great thing comes into being all at once, any more than a cluster of grapes or a fig. If you tell me, 'I want a fig,' I will answer that it needs time. Let it flower first, then put forth its fruit and then ripen. I say then, if the fig tree's fruit is not brought to perfection suddenly in a single hour, would you expect to gather the fruit of a person’s mind so soon and so easily? I tell you, you must not expect it.” —Epictetus, Discourses 1.15.7-8
|
||||
|
|
|
|||
58
conanfile.py
Normal file
58
conanfile.py
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
from conan import ConanFile
|
||||
from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout
|
||||
import shutil
|
||||
import os
|
||||
import git
|
||||
|
||||
class LightRecipe(ConanFile):
|
||||
name = "Light Engine"
|
||||
|
||||
settings = "os", "compiler", "build_type", "arch"
|
||||
generators = "CMakeDeps"
|
||||
|
||||
options = {
|
||||
"enable_tests": [True, False],
|
||||
"enable_static_analysis": [True, False],
|
||||
"use_mold": [True, False],
|
||||
"export_compile_commands": [True, False],
|
||||
}
|
||||
|
||||
default_options = {
|
||||
"enable_tests": True,
|
||||
"enable_static_analysis": False,
|
||||
"use_mold": False,
|
||||
"export_compile_commands": True,
|
||||
}
|
||||
|
||||
def requirements(self):
|
||||
self.requires("imgui/1.92.0-docking")
|
||||
self.requires("entt/3.15.0")
|
||||
self.requires("glfw/3.4")
|
||||
self.requires("stb/cci.20240531")
|
||||
self.requires("yaml-cpp/0.8.0")
|
||||
self.requires("lz4/1.10.0")
|
||||
|
||||
def layout(self):
|
||||
cmake_layout(self)
|
||||
|
||||
def generate(self):
|
||||
tc = CMakeToolchain(self)
|
||||
|
||||
tc.variables["CMAKE_BUILD_TYPE"] = self.settings.build_type
|
||||
|
||||
if self.options.use_mold:
|
||||
tc.cache_variables["CMAKE_LINKER_TYPE"] = "MOLD"
|
||||
|
||||
tc.cache_variables["CMAKE_EXPORT_COMPILE_COMMANDS"] = self.options.export_compile_commands
|
||||
tc.cache_variables["ENABLE_STATIC_ANALYSIS"] = self.options.enable_static_analysis
|
||||
tc.cache_variables["ENABLE_TESTS"] = self.options.enable_tests
|
||||
|
||||
repo = git.Repo(search_parent_directories=True)
|
||||
tc.cache_variables["GIT_HASH"] = repo.head.object.hexsha
|
||||
|
||||
tc.generate()
|
||||
|
||||
def build(self):
|
||||
cmake = CMake(self)
|
||||
cmake.configure()
|
||||
cmake.build()
|
||||
BIN
data/assets/shaders/quads/ps.asset
Normal file
BIN
data/assets/shaders/quads/ps.asset
Normal file
Binary file not shown.
10
data/assets/shaders/quads/ps.glsl
Normal file
10
data/assets/shaders/quads/ps.glsl
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#version 440 core
|
||||
|
||||
in vec4 vso_FragmentColor;
|
||||
|
||||
out vec4 fso_FragmentColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
fso_FragmentColor = vso_FragmentColor;
|
||||
}
|
||||
BIN
data/assets/shaders/quads/vs.asset
Normal file
BIN
data/assets/shaders/quads/vs.asset
Normal file
Binary file not shown.
17
data/assets/shaders/quads/vs.glsl
Normal file
17
data/assets/shaders/quads/vs.glsl
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#version 440 core
|
||||
|
||||
layout(location = 0) in vec4 a_Position;
|
||||
layout(location = 1) in vec4 a_Color;
|
||||
|
||||
layout(std140, binding = 0) uniform ub_ViewProjection
|
||||
{
|
||||
mat4 viewProjection;
|
||||
};
|
||||
|
||||
layout(location = 0) out vec4 vso_FragmentColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = viewProjection * a_Position;
|
||||
vso_FragmentColor = a_Color;
|
||||
}
|
||||
BIN
data/assets/shaders/texture/ps.asset
Normal file
BIN
data/assets/shaders/texture/ps.asset
Normal file
Binary file not shown.
12
data/assets/shaders/texture/ps.glsl
Normal file
12
data/assets/shaders/texture/ps.glsl
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#version 450 core
|
||||
|
||||
in vec2 vso_TexCoord;
|
||||
|
||||
uniform sampler2D u_Texture;
|
||||
|
||||
out vec4 fso_FragmentColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
fso_FragmentColor = texture(u_Texture, vso_TexCoord);
|
||||
}
|
||||
BIN
data/assets/shaders/texture/vs.asset
Normal file
BIN
data/assets/shaders/texture/vs.asset
Normal file
Binary file not shown.
19
data/assets/shaders/texture/vs.glsl
Normal file
19
data/assets/shaders/texture/vs.glsl
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#version 450 core
|
||||
|
||||
layout(location = 0) in vec4 a_Position;
|
||||
layout(location = 1) in vec2 a_TexCoord;
|
||||
|
||||
layout(std140, binding = 0) uniform ub_ViewProjection
|
||||
{
|
||||
mat4 u_ViewProjection;
|
||||
};
|
||||
|
||||
layout(location = 0) out vec2 vso_TexCoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = u_ViewProjection * a_Position;
|
||||
|
||||
vso_TexCoord = a_TexCoord;
|
||||
}
|
||||
|
||||
BIN
data/assets/shaders/tinted_texture/ps.asset
Normal file
BIN
data/assets/shaders/tinted_texture/ps.asset
Normal file
Binary file not shown.
14
data/assets/shaders/tinted_texture/ps.glsl
Normal file
14
data/assets/shaders/tinted_texture/ps.glsl
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#version 450 core
|
||||
|
||||
in vec4 vso_Tint;
|
||||
in vec2 vso_TexCoord;
|
||||
|
||||
uniform sampler2D u_Texture;
|
||||
|
||||
out vec4 fso_FragmentColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
fso_FragmentColor = texture(u_Texture, vso_TexCoord) * vso_Tint;
|
||||
}
|
||||
|
||||
BIN
data/assets/shaders/tinted_texture/vs.asset
Normal file
BIN
data/assets/shaders/tinted_texture/vs.asset
Normal file
Binary file not shown.
21
data/assets/shaders/tinted_texture/vs.glsl
Normal file
21
data/assets/shaders/tinted_texture/vs.glsl
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#version 450 core
|
||||
|
||||
layout(location = 0) in vec4 a_Position;
|
||||
layout(location = 1) in vec4 a_Tint;
|
||||
layout(location = 2) in vec2 a_TexCoord;
|
||||
|
||||
layout(std140, binding = 0) uniform ub_ViewProjection
|
||||
{
|
||||
mat4 u_ViewProjection;
|
||||
};
|
||||
|
||||
out vec4 vso_Tint;
|
||||
out vec2 vso_TexCoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = u_ViewProjection * a_Position;
|
||||
|
||||
vso_Tint = a_Tint;
|
||||
vso_TexCoord = a_TexCoord;
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
The quick brown fox jumps over the lazy dog
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
#version 450 core
|
||||
|
||||
layout(location = 0) in vec3 in_frag_color;
|
||||
|
||||
layout(location = 0) out vec4 out_frag_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
out_frag_color = vec4(in_frag_color, 1.0);
|
||||
}
|
||||
Binary file not shown.
|
|
@ -1,26 +0,0 @@
|
|||
#version 450 core
|
||||
|
||||
layout(push_constant ) uniform pc {
|
||||
mat4 view_projection;
|
||||
};
|
||||
|
||||
vec3 positions[3] = vec3[](
|
||||
vec3(0.0, -0.5, 0.5),
|
||||
vec3(0.5, 0.5, 0.5),
|
||||
vec3(-0.5, 0.5, 0.5)
|
||||
);
|
||||
|
||||
vec3 colors[3] = vec3[](
|
||||
vec3(0.0, 0.0, 0.0),
|
||||
vec3(0.0, 0.0, 0.0),
|
||||
vec3(0.0, 0.0, 0.0)
|
||||
);
|
||||
|
||||
layout(location = 0) out vec3 out_frag_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = view_projection * vec4(positions[gl_VertexIndex], 1.0);
|
||||
out_frag_color = colors[gl_VertexIndex];
|
||||
}
|
||||
|
||||
Binary file not shown.
48
default_gui_layout.ini
Normal file
48
default_gui_layout.ini
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
[Window][Dockspace]
|
||||
Pos=0,0
|
||||
Size=1595,720
|
||||
Collapsed=0
|
||||
|
||||
[Window][Debug##Default]
|
||||
ViewportPos=2078,721
|
||||
ViewportId=0x9F5F46A1
|
||||
Size=848,1408
|
||||
Collapsed=0
|
||||
|
||||
[Window][Dear ImGui Demo]
|
||||
Pos=836,24
|
||||
Size=759,696
|
||||
Collapsed=0
|
||||
DockId=0x00000003,1
|
||||
|
||||
[Window][Hierarchy]
|
||||
Pos=0,24
|
||||
Size=184,696
|
||||
Collapsed=0
|
||||
DockId=0x00000001,0
|
||||
|
||||
[Window][Properties]
|
||||
Pos=836,24
|
||||
Size=759,696
|
||||
Collapsed=0
|
||||
DockId=0x00000003,0
|
||||
|
||||
[Window][Game]
|
||||
Pos=186,24
|
||||
Size=648,696
|
||||
Collapsed=0
|
||||
DockId=0x00000002,0
|
||||
|
||||
[Window][Content Browser]
|
||||
ViewportPos=1359,621
|
||||
ViewportId=0x371352B7
|
||||
Size=1274,1296
|
||||
Collapsed=0
|
||||
|
||||
[Docking][Data]
|
||||
DockSpace ID=0x1ED03EE2 Window=0x5B816B74 Pos=516,375 Size=1595,696 Split=X
|
||||
DockNode ID=0x00000006 Parent=0x1ED03EE2 SizeRef=834,696 Split=X
|
||||
DockNode ID=0x00000001 Parent=0x00000006 SizeRef=184,696 Selected=0x29EABFBD
|
||||
DockNode ID=0x00000002 Parent=0x00000006 SizeRef=648,696 CentralNode=1 Selected=0x26816F31
|
||||
DockNode ID=0x00000003 Parent=0x1ED03EE2 SizeRef=759,696 Selected=0x199AB496
|
||||
|
||||
3
docs/.gitignore
vendored
3
docs/.gitignore
vendored
|
|
@ -1,3 +0,0 @@
|
|||
_build/
|
||||
generated/
|
||||
|
||||
|
|
@ -1,72 +1,62 @@
|
|||
Asset Management
|
||||
===================================================================================================
|
||||
|
||||
On Disk (file) Layout
|
||||
Layout
|
||||
---------------------------------------------------------------------------------------------------
|
||||
|
||||
.. code-block:: md
|
||||
|
||||
{version} | 4 bytes, ie. uint32_t
|
||||
{general metadata} | sizeof(AssetMetadata)
|
||||
{specialized metadata} | sizeof(XXXAssetMetadata), eg. TextureAssetMetadata
|
||||
{n} | 4 bytes, ie. uint32_t
|
||||
{blob_0...n metadata} | n * sizeof(BlobMetadata)
|
||||
{blob_0...n data} | variable size based on actual data
|
||||
{end marker} | 8 byte, ie size_t for marking the END
|
||||
{version} | 4 bytes, ie. uint32_t
|
||||
{general metadata} | sizeof(AssetMetadata)
|
||||
{specialized metadata} | sizeof(XXXAssetMetadata), eg. TextureAssetMetadata
|
||||
{n} | 4 bytes, ie. uint32_t
|
||||
{blob_0...n metadata} | n * sizeof(BlobMetadata)
|
||||
{blob_0...n data} | variable size based on actual data
|
||||
{end marker} | 8 byte, ie size_t for marking the END
|
||||
|
||||
Sections
|
||||
---------------------------------------------------------------------------------------------------
|
||||
|
||||
.. code-block:: md
|
||||
|
||||
version -> The version of the asset for forward compatibility
|
||||
general metadata -> Common asset metadata such as file-path, asset-type, creator, etc.
|
||||
specialized metadata -> Metadata specific to the asset, eg. texture dimensions for Textures.
|
||||
n -> The number of blobs.
|
||||
blob_0...n metadata -> Metadata specifying how the actual data is packed, required for unpacking.
|
||||
blob_0...n data -> The actual data, packed and compressed to be reacdy for direct engine consumption.
|
||||
version -> The version of the asset for forward compatibility
|
||||
general metadata -> Common asset metadata such as file-path, asset-type, creator, etc.
|
||||
specialized metadata -> Metadata specific to the asset, eg. texture dimensions for Textures.
|
||||
n -> The number of blobs.
|
||||
blob_0...n metadata -> Metadata specifying how the actual data is packed, required for unpacking.
|
||||
blob_0...n data -> The actual data, packed and compressed to be reacdy for direct engine consumption.
|
||||
|
||||
Loading
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Loading pre-baked asset files (like .png files) for baking:
|
||||
Each `Loader` has ONE OR MORE supported input file types (detected via the file extension): eg. StbLoader -> Can read in .jpg, .png, .bmp, etc.... files
|
||||
|
||||
|
||||
Each **Loader** has ONE OR MORE supported input file types (detected via the file extension): eg. StbLoader -> Can read in .jpg, .png, .bmp, etc.... files
|
||||
|
||||
Each **Loader** has ONLY ONE supported output asset type:
|
||||
Each `Loader` has ONLY ONE supported output asset type:
|
||||
eg. StbLoader -> outputs TextureAsset
|
||||
|
||||
Multiple **Loader**\s MAY have as output the same asset type:
|
||||
Multiple `Loader`s MAY have as output the same asset type:
|
||||
eg. StbLoader -> outputs TextureAsset
|
||||
eg. SomeOtherImgLoader -> outputs TextureAsset
|
||||
|
||||
Multiple **Loader**\s SHOULD NOT have as input same extension types
|
||||
eg. .jpg, .png -> if supported, should only be supported by 1 **Loader** class
|
||||
Multiple `Loader`s SHOULD NOT have as input same extension types
|
||||
eg. .jpg, .png -> if supported, should only be supported by 1 `Loader` class
|
||||
|
||||
Each **Loader** SHOULD read and turn the data from a file (eg. .png for textures) into something
|
||||
understandable by a **Packer** (not the engine itself).
|
||||
Each `Loader` SHOULD read and turn the data from a file (eg. .png for textures) into something
|
||||
understandable by a `Packer` (not the engine itself).
|
||||
|
||||
A **Loader** SHOULD NOT be responsible for packing the parsed file data into asset data,
|
||||
A `Loader` SHOULD NOT be responsible for packing the parsed file data into asset data,
|
||||
as that implies direct understanding of the layout required by the engine.
|
||||
|
||||
And if that layout changes, ALL **Loader**s should change accordingly;
|
||||
And if that layout changes, ALL `Loader`s should change accordingly;
|
||||
which makes a class that's responsible for reading files dependant on the engine's (potentially frequent) internal changes.
|
||||
The logic is to reduce many-to-one dependency into a one-to-one dependency by redirecting the packing process to **Packer** classes
|
||||
The logic is to reduce many-to-one dependency into a one-to-one dependency by redirecting the packing process to `Packer` classes
|
||||
|
||||
Packing
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Each **Packer** is responsible for packing ONLY ONE asset type:
|
||||
Each `Packer` is responsible for packing ONLY ONE asset type:
|
||||
eg. TexturePacker for packing texture assets from parsed image files.
|
||||
eg. ModelPacker for packing model assets from parsed model files.
|
||||
|
||||
Each **Packer** will output ONE OR MORE blobs of data,
|
||||
Each `Packer` will output ONE OR MORE blobs of data,
|
||||
and for EACH blob of data, it'll write a BlobMetadata, AFTER the specialized metadata (eg. TextureAssetMetadata)
|
||||
|
||||
A **Packer** will make use of the **Compressor** classes to compress the data,
|
||||
A `Packer` will make use of the `Compressor` classes to compress the data,
|
||||
and lay it out in a way that is suitable for the engine's consumption.
|
||||
|
||||
Unpacking
|
||||
---------------------------------------------------------------------------------------------------
|
||||
A **Parser** is responsible for parsing ONLY ONE asset type:
|
||||
A `Parser` is responsible for parsing ONLY ONE asset type:
|
||||
eg. TextureParser for parsing texture assets for direct engine consumption.
|
||||
eg. ModelParser for parsing model assets for direct engine consumption.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
.. architecture/resources
|
||||
|
||||
Resource Management
|
||||
|
||||
===================================================================================================
|
||||
|
||||
|
|
|
|||
27
docs/conf.py
27
docs/conf.py
|
|
@ -1,27 +0,0 @@
|
|||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# For the full list of built-in configuration values, see the documentation:
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
||||
|
||||
project = 'light'
|
||||
copyright = '2025, light7734'
|
||||
author = 'light7734'
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||
|
||||
extensions = []
|
||||
|
||||
templates_path = ['_templates']
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||
|
||||
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
||||
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
html_static_path = ['_static']
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
from git import Repo
|
||||
import re
|
||||
|
||||
repo = Repo(search_parent_directories=True)
|
||||
assert not repo.bare
|
||||
|
||||
file_path = "generated/changelog.rst"
|
||||
|
||||
messages = []
|
||||
short_shas = []
|
||||
hex_shas = []
|
||||
logs = []
|
||||
|
||||
remote_url = "https://git.light7734.com/light7734/light/commit"
|
||||
def format_log(commit_type, message, major, minor, patch, short_sha, hex_sha):
|
||||
href = f"{remote_url}/{hex_sha}"
|
||||
version = f"{major}.{minor}.{patch}-kitten+{short_sha}";
|
||||
link = f"`{version} <{remote_url}/{hex_sha}>`__"
|
||||
return f"| **{message}** ({link})"
|
||||
|
||||
for commit in repo.iter_commits():
|
||||
messages.append(commit.summary)
|
||||
short_shas.append(repo.git.rev_parse(commit.hexsha, short=5))
|
||||
hex_shas.append(commit.hexsha)
|
||||
|
||||
ver_major = 0
|
||||
ver_minor = 0
|
||||
ver_patch = 0
|
||||
|
||||
idx = len(messages)
|
||||
for message in reversed(messages):
|
||||
idx = idx - 1;
|
||||
|
||||
commit_type = re.match("^(feat|fix|refactor|perf|build|asset|test|chore|ci|docs)", message)
|
||||
if not commit_type:
|
||||
continue
|
||||
|
||||
match commit_type.group(0):
|
||||
case "feat":
|
||||
ver_minor = ver_minor + 1
|
||||
ver_patch = 0
|
||||
|
||||
case "fix":
|
||||
ver_patch = ver_patch + 1
|
||||
|
||||
case "refactor":
|
||||
ver_patch = ver_patch + 1
|
||||
|
||||
case "perf":
|
||||
ver_patch = ver_patch + 1
|
||||
|
||||
case "build":
|
||||
ver_patch = ver_patch + 1
|
||||
|
||||
case "asset":
|
||||
ver_patch = ver_patch + 1
|
||||
|
||||
logs.append(format_log(commit_type, message, ver_major, ver_minor, ver_patch, short_shas[idx], hex_shas[idx]))
|
||||
|
||||
with open(file_path, "w") as f:
|
||||
f.write(".. changelogs\n\n\n")
|
||||
f.write("Changelogs\n")
|
||||
f.write("==================================================\n\n")
|
||||
|
||||
f.write("KITTEN\n")
|
||||
f.write("--------------------------------------------------\n\n")
|
||||
for log in reversed(logs):
|
||||
f.write(log + '\n')
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
.. guidelines/conventions
|
||||
|
||||
Coding Conventions
|
||||
===================================================================================================
|
||||
Any line of code added to the engine, must abide by following conventions.
|
||||
They may seem arbitrary, and sometimes they are. But to achieve **consistency**, which is not an arbitrary goal, is to
|
||||
follow these guidelines.
|
||||
|
||||
AAA
|
||||
--------------------
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
.. guidelines/development
|
||||
|
||||
Development
|
||||
===================================================================================================
|
||||
As a solo-project, I am not only the **developer**, but also the **manager**.
|
||||
Therefore there is a need, if this project is to succeed, to have a development plan.
|
||||
|
||||
Such a plan should:
|
||||
|
||||
- Define a way to **distribute work** (across time, since there's only 1 developer).
|
||||
- Define what is a **unit of work** (cycles).
|
||||
- Provide a way to **track productivity**, which helps projecting the future and **detecting patterns** early on.
|
||||
- Provide a **pipeline** for the work to go through and **minimize ambiguity**.
|
||||
|
||||
These are the **management** aspects of the project, which help the development goals to be more **pragmatic**
|
||||
---by pulling my mind out of its **engineering dreamland**, and make it focus on the **broader picture**.
|
||||
|
||||
Cycle
|
||||
---------------------------------------------------------------------------------------------------
|
||||
A cycle is one **step** in development, one cycle = one ticket, and it consists of 4 stages:
|
||||
|
||||
1 - Make it known
|
||||
- Write the commit message.
|
||||
- This limits the **scope of changes** and gives you a very specific **goal** to work towards.
|
||||
- If something outside of this scope really bothers you, fix and stash for a future cycle.
|
||||
- Make a ticket if stash-fix is implausible ---**DO NOT** write **todo** comments.
|
||||
- The message should follow the project's **commit message specifications**.
|
||||
|
||||
- Make a ticket.
|
||||
- Version control (git) is a **development-tool**, not a **management-tool**.
|
||||
- Provide a very brief description ---This may be used in the commit message's body.
|
||||
|
||||
2 - Make it work
|
||||
- Write high-level tests that confirms the cycle's requirements are met.
|
||||
- That is, specify requirements in a programming language instead of English.
|
||||
- You're done when all the tests pass.
|
||||
- Preferably write the tests first, but it's okay to start with the interface.
|
||||
- Tests may not be necessary depending on the requirements and commit type.
|
||||
|
||||
- "Make it work" doesn't mean liberally producing shit code, you should:
|
||||
- Follow project's **conventions**.
|
||||
- Follow **best practices** and **proven swe principles**.
|
||||
- Enable **warnings as errors**.
|
||||
- Enable **static analysis**.
|
||||
- Don't break any pre-existing-tests.
|
||||
- Have the over-all picture in mind.
|
||||
|
||||
3 - Make it right
|
||||
- Test driven refactoring
|
||||
- Now you have a better picture of how things relate and work.
|
||||
- Switch to a TDD-style development to do the refactoring while following swe best-practices and proven-principles.
|
||||
|
||||
4 - Make it fast
|
||||
- This is an engine, at the end of the day, **performance** is king.
|
||||
- Get a performance and/or memory profile and try to alleviate the bottlenecks.
|
||||
- Avoid premature optimizations, be certain what you change has performance benefits.
|
||||
|
||||
|
||||
Sprint
|
||||
---------------------------------------------------------------------------------------------------
|
||||
A sprint is the collection of all the finished cycles in one week.
|
||||
It's meant to provide insight on development speed and help projecting the future.
|
||||
|
||||
|
||||
Commit Message Specification
|
||||
---------------------------------------------------------------------------------------------------
|
||||
The project follows the `Conventional Commits Specification <https://www.conventionalcommits.org/en/v1.0.0-beta.4>`_.
|
||||
|
||||
.. code-block:: md
|
||||
|
||||
<type>[optional scope]: <description>
|
||||
|
||||
[optional body]
|
||||
|
||||
[optional footer]
|
||||
|
||||
With the following commit types:
|
||||
|
||||
- feat
|
||||
- For adding a new feature.
|
||||
- Causes a **minor** bump in version.
|
||||
|
||||
- fix
|
||||
- For changes that fix one or more bug.
|
||||
- Causes a **patch** bump in version.
|
||||
|
||||
- refactor
|
||||
- For non feat/fix changes that improve the implementation and/or the interface.
|
||||
- Causes a **patch** bump in version.
|
||||
|
||||
- perf
|
||||
- For changes that (hopefully) improve the performance.
|
||||
- Causes a **patch** bump in version.
|
||||
|
||||
- build
|
||||
- For changes that affect the build system or external dependencies.
|
||||
- Causes a **patch** bump in version.
|
||||
|
||||
- asset
|
||||
- For changes to the files under the ``/data`` directory.
|
||||
- Causes a **patch** bump in version.
|
||||
|
||||
- test
|
||||
- For adding missing tests or correcting the existing tests.
|
||||
- Does not affect the version.
|
||||
|
||||
- chore
|
||||
- For releases, .gitignore changes, deleting unused files, etc.
|
||||
- Does not affect the version.
|
||||
|
||||
- ci
|
||||
- For changes to our CI configuration files and scripts, including files under ``/tools/ci``.
|
||||
- Does not affect the version.
|
||||
|
||||
- docs
|
||||
- For changes to the documentations.
|
||||
- Does not affect the version.
|
||||
|
||||
Semantic Versioning
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Coupled with conventional commit style messages, we can automajically version the project following
|
||||
the **Semantic Versioning 2.0.0** specifications.
|
||||
|
||||
The full version identifier consits of a version core (major.minor.patch) + label + hexsha of the commit.
|
||||
Using the following format:
|
||||
|
||||
|
||||
.. code-block:: md
|
||||
|
||||
<major>.<minor>.<patch>-<label>+<short_hexsha>
|
||||
|
||||
eg.
|
||||
0.8.1-kitten+ea898
|
||||
0.5.0-kitten+01d85
|
||||
1.5.0-akasha+7de53
|
||||
|
||||
kitten refers to all pre-release (1.0.0) versions
|
||||
|
||||
|
||||
The shortened hexsha of a commit is obtained by:
|
||||
``git rev-parse --short=5 <commit_hexsha>``
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
.. guidelines/philosophy
|
||||
|
||||
Philosophy
|
||||
===================================================================================================
|
||||
|
||||
| **A theory or attitude that acts as a guiding principle for behaviour.**
|
||||
| --- Oxford Languages
|
||||
|
|
@ -1,32 +1,6 @@
|
|||
.. light documentation
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Light Engine
|
||||
|
||||
light/showcase.rst
|
||||
light/features.rst
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Software Architecture
|
||||
|
||||
architecture/assets.rst
|
||||
architecture/resource.rst
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Development Guidelines
|
||||
|
||||
guidelines/philosophy.rst
|
||||
guidelines/development.rst
|
||||
guidelines/conventions.rst
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Generated Docs
|
||||
|
||||
generated/api.rst
|
||||
generated/changelog.rst
|
||||
A bleeding-edge, cross-platform, cross-graphics-api, minimal-dependencies modern game-engine.
|
||||
|
||||
Supported Platforms: Windows, Mac, Linux, FreeBSD
|
||||
Supported GraphicsAPIs: DirectX12-Ultimate, Vulkan 1.4, Metal, OpenGL 4.6
|
||||
|
||||
Dependencies: stdlib, meshoptimizer
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
.. light/features
|
||||
|
||||
Features
|
||||
===================================================================================================
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
.. light/demos
|
||||
|
||||
Showcase
|
||||
===================================================================================================
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=.
|
||||
set BUILDDIR=_build
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.https://www.sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
|
||||
:end
|
||||
popd
|
||||
3
external/.clang-tidy
vendored
Normal file
3
external/.clang-tidy
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Disable all checks in this subdirectory
|
||||
Checks: '-*'
|
||||
|
||||
3
external/glad/CMakeLists.txt
vendored
Normal file
3
external/glad/CMakeLists.txt
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
add_library(glad ${CMAKE_CURRENT_SOURCE_DIR}/src/gl.c)
|
||||
target_include_directories(glad PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
target_link_libraries(glad PUBLIC opengl::opengl)
|
||||
311
external/glad/include/KHR/khrplatform.h
vendored
Normal file
311
external/glad/include/KHR/khrplatform.h
vendored
Normal file
|
|
@ -0,0 +1,311 @@
|
|||
#ifndef __khrplatform_h_
|
||||
#define __khrplatform_h_
|
||||
|
||||
/*
|
||||
** Copyright (c) 2008-2018 The Khronos Group Inc.
|
||||
**
|
||||
** Permission is hereby granted, free of charge, to any person obtaining a
|
||||
** copy of this software and/or associated documentation files (the
|
||||
** "Materials"), to deal in the Materials without restriction, including
|
||||
** without limitation the rights to use, copy, modify, merge, publish,
|
||||
** distribute, sublicense, and/or sell copies of the Materials, and to
|
||||
** permit persons to whom the Materials are furnished to do so, subject to
|
||||
** the following conditions:
|
||||
**
|
||||
** The above copyright notice and this permission notice shall be included
|
||||
** in all copies or substantial portions of the Materials.
|
||||
**
|
||||
** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
|
||||
*/
|
||||
|
||||
/* Khronos platform-specific types and definitions.
|
||||
*
|
||||
* The master copy of khrplatform.h is maintained in the Khronos EGL
|
||||
* Registry repository at https://github.com/KhronosGroup/EGL-Registry
|
||||
* The last semantic modification to khrplatform.h was at commit ID:
|
||||
* 67a3e0864c2d75ea5287b9f3d2eb74a745936692
|
||||
*
|
||||
* Adopters may modify this file to suit their platform. Adopters are
|
||||
* encouraged to submit platform specific modifications to the Khronos
|
||||
* group so that they can be included in future versions of this file.
|
||||
* Please submit changes by filing pull requests or issues on
|
||||
* the EGL Registry repository linked above.
|
||||
*
|
||||
*
|
||||
* See the Implementer's Guidelines for information about where this file
|
||||
* should be located on your system and for more details of its use:
|
||||
* http://www.khronos.org/registry/implementers_guide.pdf
|
||||
*
|
||||
* This file should be included as
|
||||
* #include <KHR/khrplatform.h>
|
||||
* by Khronos client API header files that use its types and defines.
|
||||
*
|
||||
* The types in khrplatform.h should only be used to define API-specific types.
|
||||
*
|
||||
* Types defined in khrplatform.h:
|
||||
* khronos_int8_t signed 8 bit
|
||||
* khronos_uint8_t unsigned 8 bit
|
||||
* khronos_int16_t signed 16 bit
|
||||
* khronos_uint16_t unsigned 16 bit
|
||||
* khronos_int32_t signed 32 bit
|
||||
* khronos_uint32_t unsigned 32 bit
|
||||
* khronos_int64_t signed 64 bit
|
||||
* khronos_uint64_t unsigned 64 bit
|
||||
* khronos_intptr_t signed same number of bits as a pointer
|
||||
* khronos_uintptr_t unsigned same number of bits as a pointer
|
||||
* khronos_ssize_t signed size
|
||||
* khronos_usize_t unsigned size
|
||||
* khronos_float_t signed 32 bit floating point
|
||||
* khronos_time_ns_t unsigned 64 bit time in nanoseconds
|
||||
* khronos_utime_nanoseconds_t unsigned time interval or absolute time in
|
||||
* nanoseconds
|
||||
* khronos_stime_nanoseconds_t signed time interval in nanoseconds
|
||||
* khronos_boolean_enum_t enumerated boolean type. This should
|
||||
* only be used as a base type when a client API's boolean type is
|
||||
* an enum. Client APIs which use an integer or other type for
|
||||
* booleans cannot use this as the base type for their boolean.
|
||||
*
|
||||
* Tokens defined in khrplatform.h:
|
||||
*
|
||||
* KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values.
|
||||
*
|
||||
* KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0.
|
||||
* KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0.
|
||||
*
|
||||
* Calling convention macros defined in this file:
|
||||
* KHRONOS_APICALL
|
||||
* KHRONOS_APIENTRY
|
||||
* KHRONOS_APIATTRIBUTES
|
||||
*
|
||||
* These may be used in function prototypes as:
|
||||
*
|
||||
* KHRONOS_APICALL void KHRONOS_APIENTRY funcname(
|
||||
* int arg1,
|
||||
* int arg2) KHRONOS_APIATTRIBUTES;
|
||||
*/
|
||||
|
||||
#if defined(__SCITECH_SNAP__) && !defined(KHRONOS_STATIC)
|
||||
# define KHRONOS_STATIC 1
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------
|
||||
* Definition of KHRONOS_APICALL
|
||||
*-------------------------------------------------------------------------
|
||||
* This precedes the return type of the function in the function prototype.
|
||||
*/
|
||||
#if defined(KHRONOS_STATIC)
|
||||
/* If the preprocessor constant KHRONOS_STATIC is defined, make the
|
||||
* header compatible with static linking. */
|
||||
# define KHRONOS_APICALL
|
||||
#elif defined(_WIN32)
|
||||
# define KHRONOS_APICALL __declspec(dllimport)
|
||||
#elif defined (__SYMBIAN32__)
|
||||
# define KHRONOS_APICALL IMPORT_C
|
||||
#elif defined(__ANDROID__)
|
||||
# define KHRONOS_APICALL __attribute__((visibility("default")))
|
||||
#else
|
||||
# define KHRONOS_APICALL
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------
|
||||
* Definition of KHRONOS_APIENTRY
|
||||
*-------------------------------------------------------------------------
|
||||
* This follows the return type of the function and precedes the function
|
||||
* name in the function prototype.
|
||||
*/
|
||||
#if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__)
|
||||
/* Win32 but not WinCE */
|
||||
# define KHRONOS_APIENTRY __stdcall
|
||||
#else
|
||||
# define KHRONOS_APIENTRY
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------
|
||||
* Definition of KHRONOS_APIATTRIBUTES
|
||||
*-------------------------------------------------------------------------
|
||||
* This follows the closing parenthesis of the function prototype arguments.
|
||||
*/
|
||||
#if defined (__ARMCC_2__)
|
||||
#define KHRONOS_APIATTRIBUTES __softfp
|
||||
#else
|
||||
#define KHRONOS_APIATTRIBUTES
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------
|
||||
* basic type definitions
|
||||
*-----------------------------------------------------------------------*/
|
||||
#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__)
|
||||
|
||||
|
||||
/*
|
||||
* Using <stdint.h>
|
||||
*/
|
||||
#include <stdint.h>
|
||||
typedef int32_t khronos_int32_t;
|
||||
typedef uint32_t khronos_uint32_t;
|
||||
typedef int64_t khronos_int64_t;
|
||||
typedef uint64_t khronos_uint64_t;
|
||||
#define KHRONOS_SUPPORT_INT64 1
|
||||
#define KHRONOS_SUPPORT_FLOAT 1
|
||||
/*
|
||||
* To support platform where unsigned long cannot be used interchangeably with
|
||||
* inptr_t (e.g. CHERI-extended ISAs), we can use the stdint.h intptr_t.
|
||||
* Ideally, we could just use (u)intptr_t everywhere, but this could result in
|
||||
* ABI breakage if khronos_uintptr_t is changed from unsigned long to
|
||||
* unsigned long long or similar (this results in different C++ name mangling).
|
||||
* To avoid changes for existing platforms, we restrict usage of intptr_t to
|
||||
* platforms where the size of a pointer is larger than the size of long.
|
||||
*/
|
||||
#if defined(__SIZEOF_LONG__) && defined(__SIZEOF_POINTER__)
|
||||
#if __SIZEOF_POINTER__ > __SIZEOF_LONG__
|
||||
#define KHRONOS_USE_INTPTR_T
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#elif defined(__VMS ) || defined(__sgi)
|
||||
|
||||
/*
|
||||
* Using <inttypes.h>
|
||||
*/
|
||||
#include <inttypes.h>
|
||||
typedef int32_t khronos_int32_t;
|
||||
typedef uint32_t khronos_uint32_t;
|
||||
typedef int64_t khronos_int64_t;
|
||||
typedef uint64_t khronos_uint64_t;
|
||||
#define KHRONOS_SUPPORT_INT64 1
|
||||
#define KHRONOS_SUPPORT_FLOAT 1
|
||||
|
||||
#elif defined(_WIN32) && !defined(__SCITECH_SNAP__)
|
||||
|
||||
/*
|
||||
* Win32
|
||||
*/
|
||||
typedef __int32 khronos_int32_t;
|
||||
typedef unsigned __int32 khronos_uint32_t;
|
||||
typedef __int64 khronos_int64_t;
|
||||
typedef unsigned __int64 khronos_uint64_t;
|
||||
#define KHRONOS_SUPPORT_INT64 1
|
||||
#define KHRONOS_SUPPORT_FLOAT 1
|
||||
|
||||
#elif defined(__sun__) || defined(__digital__)
|
||||
|
||||
/*
|
||||
* Sun or Digital
|
||||
*/
|
||||
typedef int khronos_int32_t;
|
||||
typedef unsigned int khronos_uint32_t;
|
||||
#if defined(__arch64__) || defined(_LP64)
|
||||
typedef long int khronos_int64_t;
|
||||
typedef unsigned long int khronos_uint64_t;
|
||||
#else
|
||||
typedef long long int khronos_int64_t;
|
||||
typedef unsigned long long int khronos_uint64_t;
|
||||
#endif /* __arch64__ */
|
||||
#define KHRONOS_SUPPORT_INT64 1
|
||||
#define KHRONOS_SUPPORT_FLOAT 1
|
||||
|
||||
#elif 0
|
||||
|
||||
/*
|
||||
* Hypothetical platform with no float or int64 support
|
||||
*/
|
||||
typedef int khronos_int32_t;
|
||||
typedef unsigned int khronos_uint32_t;
|
||||
#define KHRONOS_SUPPORT_INT64 0
|
||||
#define KHRONOS_SUPPORT_FLOAT 0
|
||||
|
||||
#else
|
||||
|
||||
/*
|
||||
* Generic fallback
|
||||
*/
|
||||
#include <stdint.h>
|
||||
typedef int32_t khronos_int32_t;
|
||||
typedef uint32_t khronos_uint32_t;
|
||||
typedef int64_t khronos_int64_t;
|
||||
typedef uint64_t khronos_uint64_t;
|
||||
#define KHRONOS_SUPPORT_INT64 1
|
||||
#define KHRONOS_SUPPORT_FLOAT 1
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* Types that are (so far) the same on all platforms
|
||||
*/
|
||||
typedef signed char khronos_int8_t;
|
||||
typedef unsigned char khronos_uint8_t;
|
||||
typedef signed short int khronos_int16_t;
|
||||
typedef unsigned short int khronos_uint16_t;
|
||||
|
||||
/*
|
||||
* Types that differ between LLP64 and LP64 architectures - in LLP64,
|
||||
* pointers are 64 bits, but 'long' is still 32 bits. Win64 appears
|
||||
* to be the only LLP64 architecture in current use.
|
||||
*/
|
||||
#ifdef KHRONOS_USE_INTPTR_T
|
||||
typedef intptr_t khronos_intptr_t;
|
||||
typedef uintptr_t khronos_uintptr_t;
|
||||
#elif defined(_WIN64)
|
||||
typedef signed long long int khronos_intptr_t;
|
||||
typedef unsigned long long int khronos_uintptr_t;
|
||||
#else
|
||||
typedef signed long int khronos_intptr_t;
|
||||
typedef unsigned long int khronos_uintptr_t;
|
||||
#endif
|
||||
|
||||
#if defined(_WIN64)
|
||||
typedef signed long long int khronos_ssize_t;
|
||||
typedef unsigned long long int khronos_usize_t;
|
||||
#else
|
||||
typedef signed long int khronos_ssize_t;
|
||||
typedef unsigned long int khronos_usize_t;
|
||||
#endif
|
||||
|
||||
#if KHRONOS_SUPPORT_FLOAT
|
||||
/*
|
||||
* Float type
|
||||
*/
|
||||
typedef float khronos_float_t;
|
||||
#endif
|
||||
|
||||
#if KHRONOS_SUPPORT_INT64
|
||||
/* Time types
|
||||
*
|
||||
* These types can be used to represent a time interval in nanoseconds or
|
||||
* an absolute Unadjusted System Time. Unadjusted System Time is the number
|
||||
* of nanoseconds since some arbitrary system event (e.g. since the last
|
||||
* time the system booted). The Unadjusted System Time is an unsigned
|
||||
* 64 bit value that wraps back to 0 every 584 years. Time intervals
|
||||
* may be either signed or unsigned.
|
||||
*/
|
||||
typedef khronos_uint64_t khronos_utime_nanoseconds_t;
|
||||
typedef khronos_int64_t khronos_stime_nanoseconds_t;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Dummy value used to pad enum types to 32 bits.
|
||||
*/
|
||||
#ifndef KHRONOS_MAX_ENUM
|
||||
#define KHRONOS_MAX_ENUM 0x7FFFFFFF
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Enumerated boolean type
|
||||
*
|
||||
* Values other than zero should be considered to be true. Therefore
|
||||
* comparisons should not be made against KHRONOS_TRUE.
|
||||
*/
|
||||
typedef enum {
|
||||
KHRONOS_FALSE = 0,
|
||||
KHRONOS_TRUE = 1,
|
||||
KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM
|
||||
} khronos_boolean_enum_t;
|
||||
|
||||
#endif /* __khrplatform_h_ */
|
||||
8319
external/glad/include/glad/gl.h
vendored
Normal file
8319
external/glad/include/glad/gl.h
vendored
Normal file
File diff suppressed because it is too large
Load diff
7841
external/glad/include/glad/vulkan.h
vendored
Normal file
7841
external/glad/include/glad/vulkan.h
vendored
Normal file
File diff suppressed because it is too large
Load diff
84
external/glad/include/vk_platform.h
vendored
Normal file
84
external/glad/include/vk_platform.h
vendored
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
/* */
|
||||
/* File: vk_platform.h */
|
||||
/* */
|
||||
/*
|
||||
** Copyright 2014-2025 The Khronos Group Inc.
|
||||
**
|
||||
** SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
|
||||
#ifndef VK_PLATFORM_H_
|
||||
#define VK_PLATFORM_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/*
|
||||
***************************************************************************************************
|
||||
* Platform-specific directives and type declarations
|
||||
***************************************************************************************************
|
||||
*/
|
||||
|
||||
/* Platform-specific calling convention macros.
|
||||
*
|
||||
* Platforms should define these so that Vulkan clients call Vulkan commands
|
||||
* with the same calling conventions that the Vulkan implementation expects.
|
||||
*
|
||||
* VKAPI_ATTR - Placed before the return type in function declarations.
|
||||
* Useful for C++11 and GCC/Clang-style function attribute syntax.
|
||||
* VKAPI_CALL - Placed after the return type in function declarations.
|
||||
* Useful for MSVC-style calling convention syntax.
|
||||
* VKAPI_PTR - Placed between the '(' and '*' in function pointer types.
|
||||
*
|
||||
* Function declaration: VKAPI_ATTR void VKAPI_CALL vkCommand(void);
|
||||
* Function pointer type: typedef void (VKAPI_PTR *PFN_vkCommand)(void);
|
||||
*/
|
||||
#if defined(_WIN32)
|
||||
/* On Windows, Vulkan commands use the stdcall convention */
|
||||
#define VKAPI_ATTR
|
||||
#define VKAPI_CALL __stdcall
|
||||
#define VKAPI_PTR VKAPI_CALL
|
||||
#elif defined(__ANDROID__) && defined(__ARM_ARCH) && __ARM_ARCH < 7
|
||||
#error "Vulkan is not supported for the 'armeabi' NDK ABI"
|
||||
#elif defined(__ANDROID__) && defined(__ARM_ARCH) && __ARM_ARCH >= 7 && defined(__ARM_32BIT_STATE)
|
||||
/* On Android 32-bit ARM targets, Vulkan functions use the "hardfloat" */
|
||||
/* calling convention, i.e. float parameters are passed in registers. This */
|
||||
/* is true even if the rest of the application passes floats on the stack, */
|
||||
/* as it does by default when compiling for the armeabi-v7a NDK ABI. */
|
||||
#define VKAPI_ATTR __attribute__((pcs("aapcs-vfp")))
|
||||
#define VKAPI_CALL
|
||||
#define VKAPI_PTR VKAPI_ATTR
|
||||
#else
|
||||
/* On other platforms, use the default calling convention */
|
||||
#define VKAPI_ATTR
|
||||
#define VKAPI_CALL
|
||||
#define VKAPI_PTR
|
||||
#endif
|
||||
|
||||
#if !defined(VK_NO_STDDEF_H)
|
||||
#include <stddef.h>
|
||||
#endif /* !defined(VK_NO_STDDEF_H) */
|
||||
|
||||
#if !defined(VK_NO_STDINT_H)
|
||||
#if defined(_MSC_VER) && (_MSC_VER < 1600)
|
||||
typedef signed __int8 int8_t;
|
||||
typedef unsigned __int8 uint8_t;
|
||||
typedef signed __int16 int16_t;
|
||||
typedef unsigned __int16 uint16_t;
|
||||
typedef signed __int32 int32_t;
|
||||
typedef unsigned __int32 uint32_t;
|
||||
typedef signed __int64 int64_t;
|
||||
typedef unsigned __int64 uint64_t;
|
||||
#else
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
#endif /* !defined(VK_NO_STDINT_H) */
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif
|
||||
17892
external/glad/src/gl.c
vendored
Normal file
17892
external/glad/src/gl.c
vendored
Normal file
File diff suppressed because it is too large
Load diff
5537
external/glad/src/vulkan.c
vendored
Normal file
5537
external/glad/src/vulkan.c
vendored
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,26 +1,25 @@
|
|||
# engine
|
||||
add_subdirectory(./std)
|
||||
add_subdirectory(./bitwise)
|
||||
add_subdirectory(./env)
|
||||
add_subdirectory(./memory)
|
||||
add_subdirectory(./base)
|
||||
add_subdirectory(./time)
|
||||
add_subdirectory(./logger)
|
||||
add_subdirectory(./debug)
|
||||
add_subdirectory(./math)
|
||||
#
|
||||
|
||||
add_subdirectory(./asset_baker)
|
||||
add_subdirectory(./assets)
|
||||
#
|
||||
add_subdirectory(./asset_parser)
|
||||
add_subdirectory(./asset_manager)
|
||||
|
||||
add_subdirectory(./camera)
|
||||
add_subdirectory(./input)
|
||||
# add_subdirectory(./ui)
|
||||
#
|
||||
add_subdirectory(./surface)
|
||||
add_subdirectory(./ui)
|
||||
|
||||
add_subdirectory(./window)
|
||||
add_subdirectory(./renderer)
|
||||
add_subdirectory(./ecs)
|
||||
#
|
||||
|
||||
add_subdirectory(./app)
|
||||
|
||||
# apps
|
||||
add_subdirectory(./mirror)
|
||||
|
||||
add_subdirectory(test)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,21 @@
|
|||
add_library_module(app application.cpp)
|
||||
target_link_libraries(
|
||||
app
|
||||
PUBLIC memory
|
||||
PRIVATE lt_debug)
|
||||
add_library_module(app
|
||||
application.cpp
|
||||
layer.cpp
|
||||
layer_stack.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(app
|
||||
PUBLIC
|
||||
renderer
|
||||
logger
|
||||
ui
|
||||
asset_parser
|
||||
asset_manager
|
||||
lt_debug
|
||||
ecs
|
||||
window
|
||||
glad
|
||||
time
|
||||
opengl::opengl
|
||||
EnTT::EnTT
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,55 +1,203 @@
|
|||
#include <app/application.hpp>
|
||||
#include <app/system.hpp>
|
||||
#include <memory/reference.hpp>
|
||||
#include <app/layer.hpp>
|
||||
#include <app/layer_stack.hpp>
|
||||
#include <asset_manager/asset_manager.hpp>
|
||||
#include <input/events/event.hpp>
|
||||
#include <input/events/keyboard.hpp>
|
||||
#include <input/events/window.hpp>
|
||||
#include <input/input.hpp>
|
||||
#include <lt_debug/assertions.hpp>
|
||||
#include <ranges>
|
||||
#include <renderer/blender.hpp>
|
||||
#include <renderer/graphics_context.hpp>
|
||||
#include <renderer/render_command.hpp>
|
||||
#include <renderer/renderer.hpp>
|
||||
#include <ui/ui.hpp>
|
||||
#include <window/window.hpp>
|
||||
|
||||
namespace lt::app {
|
||||
namespace lt {
|
||||
|
||||
Application *Application::s_instance = nullptr;
|
||||
|
||||
Application::Application(): m_window(nullptr)
|
||||
{
|
||||
ensure(!s_instance, "Application constructed twice");
|
||||
s_instance = this;
|
||||
|
||||
m_window = Window::create([this](auto &&PH1) { on_event(std::forward<decltype(PH1)>(PH1)); });
|
||||
|
||||
// create graphics context
|
||||
m_graphics_context = GraphicsContext::create(
|
||||
GraphicsAPI::OpenGL,
|
||||
(GLFWwindow *)m_window->get_handle()
|
||||
);
|
||||
|
||||
AssetManager::load_shader(
|
||||
"LT_ENGINE_RESOURCES_TEXTURE_SHADER",
|
||||
"data/assets/shaders/texture/vs.asset",
|
||||
"data/assets/shaders/texture/ps.asset"
|
||||
);
|
||||
|
||||
AssetManager::load_shader(
|
||||
"LT_ENGINE_RESOURCES_TINTED_TEXTURE_SHADER",
|
||||
"data/assets/shaders/tinted_texture/vs.asset",
|
||||
"data/assets/shaders/tinted_texture/ps.asset"
|
||||
);
|
||||
|
||||
AssetManager::load_shader(
|
||||
"LT_ENGINE_RESOURCES_QUAD_SHADER",
|
||||
"data/assets/shaders/quads/vs.asset",
|
||||
"data/assets/shaders/quads/ps.asset"
|
||||
);
|
||||
|
||||
m_renderer = Renderer::create(
|
||||
(GLFWwindow *)m_window->get_handle(),
|
||||
lt::GraphicsContext::get_shared_context(),
|
||||
Renderer::CreateInfo {
|
||||
.quad_renderer_shader = AssetManager::get_shader("LT_ENGINE_RESOURCES_QUAD_SHADER"),
|
||||
.texture_renderer_shader = AssetManager::get_shader(
|
||||
"LT_ENGINE_RESOURCES_TEXTURE_SHADER"
|
||||
),
|
||||
.tinted_texture_renderer_shader = AssetManager::get_shader(
|
||||
"LT_ENGINE_RESOURCES_TINTED_"
|
||||
"TEXTURE_SHADER"
|
||||
),
|
||||
}
|
||||
);
|
||||
ensure(m_graphics_context, "lWindow::lWindow: failed to create 'GraphicsContext'");
|
||||
|
||||
m_user_interface = UserInterface::create(
|
||||
(GLFWwindow *)m_window->get_handle(),
|
||||
lt::GraphicsContext::get_shared_context()
|
||||
);
|
||||
|
||||
m_layer_stack = create_scope<LayerStack>();
|
||||
}
|
||||
|
||||
Application::~Application()
|
||||
{
|
||||
/** This is required to make forward-declarations possible:
|
||||
* https://stackoverflow.com/questions/34072862/why-is-error-invalid-application-of-sizeof-to-an-incomplete-type-using-uniqu
|
||||
*/
|
||||
}
|
||||
|
||||
void Application::game_loop()
|
||||
{
|
||||
while (true)
|
||||
m_window->set_visibility(true);
|
||||
|
||||
while (!m_window->is_closed())
|
||||
{
|
||||
for (auto &system : m_systems)
|
||||
update_layers();
|
||||
render_layers();
|
||||
render_user_interface();
|
||||
poll_events();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::quit()
|
||||
{
|
||||
s_instance->m_window->close();
|
||||
}
|
||||
|
||||
void Application::update_layers()
|
||||
{
|
||||
for (auto &it : *m_layer_stack)
|
||||
{
|
||||
// narrowing double -> float
|
||||
it->on_update(static_cast<float>(m_timer.elapsed_time().count()));
|
||||
}
|
||||
|
||||
// TODO(Light): each layer should have their own "delta time"
|
||||
m_timer.reset();
|
||||
}
|
||||
|
||||
void Application::render_layers()
|
||||
{
|
||||
m_renderer->begin_frame();
|
||||
|
||||
for (auto &it : *m_layer_stack)
|
||||
{
|
||||
it->on_render();
|
||||
}
|
||||
|
||||
m_renderer->end_frame();
|
||||
}
|
||||
|
||||
void Application::render_user_interface()
|
||||
{
|
||||
m_user_interface->begin();
|
||||
|
||||
for (auto &it : *m_layer_stack)
|
||||
{
|
||||
it->on_user_interface_update();
|
||||
}
|
||||
|
||||
m_user_interface->end();
|
||||
}
|
||||
|
||||
void Application::poll_events()
|
||||
{
|
||||
m_window->poll_events();
|
||||
}
|
||||
|
||||
void Application::on_event(const Event &event)
|
||||
{
|
||||
// window
|
||||
if (event.has_category(WindowEventCategory))
|
||||
{
|
||||
m_window->on_event(event);
|
||||
|
||||
if (event.get_event_type() == EventType::WindowResized)
|
||||
{
|
||||
const auto &last_tick = system->get_last_tick_result();
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
|
||||
system->tick(
|
||||
TickInfo {
|
||||
.delta_time = now - last_tick.end_time,
|
||||
.budget = std::chrono::milliseconds { 10 },
|
||||
.start_time = now,
|
||||
}
|
||||
);
|
||||
m_renderer->on_window_resize(dynamic_cast<const WindowResizedEvent &>(event));
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &system : m_systems_to_be_registered)
|
||||
// input
|
||||
if (event.has_category(InputEventCategory))
|
||||
{
|
||||
Input::instance().on_event(event);
|
||||
|
||||
if (!Input::instance().is_receiving_game_events())
|
||||
{
|
||||
m_systems.emplace_back(system)->on_register();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &system : m_systems_to_be_unregistered)
|
||||
{
|
||||
m_systems.erase(
|
||||
std::remove(m_systems.begin(), m_systems.end(), system),
|
||||
m_systems.end()
|
||||
);
|
||||
}
|
||||
|
||||
if (m_systems.empty())
|
||||
for (auto &it : std::ranges::reverse_view(*m_layer_stack))
|
||||
{
|
||||
if (it->on_event(event))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Application::register_system(memory::Ref<app::ISystem> system)
|
||||
[[nodiscard]] auto Application::sanity_check() const -> bool
|
||||
{
|
||||
m_systems.emplace_back(std::move(system));
|
||||
log_inf("Checking application sanity...");
|
||||
ensure(s_instance, "Application not constructed!?");
|
||||
ensure(m_window, "Window is not initialized");
|
||||
ensure(m_user_interface, "User interface is not initialized");
|
||||
ensure(m_graphics_context, "Graphics context is not initialized");
|
||||
ensure(m_renderer, "Renderer is not initialized");
|
||||
ensure(m_layer_stack, "Layer_stack is not initialized");
|
||||
ensure(!m_layer_stack->is_empty(), "Layer_stack is empty");
|
||||
|
||||
log_inf("Logging application state...");
|
||||
this->log_debug_data();
|
||||
m_graphics_context->log_debug_data();
|
||||
m_user_interface->log_debug_data();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Application::unregister_system(memory::Ref<app::ISystem> system)
|
||||
void Application::log_debug_data() const
|
||||
{
|
||||
m_systems_to_be_unregistered.emplace_back(std::move(system));
|
||||
log_inf("Platform::");
|
||||
log_inf(" Platform name: {}", constants::platform_name);
|
||||
log_inf(" Platform identifier: {}", std::to_underlying(constants::platform));
|
||||
log_inf(" CWD: {}", std::filesystem::current_path().generic_string());
|
||||
}
|
||||
|
||||
} // namespace lt::app
|
||||
} // namespace lt
|
||||
|
|
|
|||
47
modules/app/private/layer.cpp
Normal file
47
modules/app/private/layer.cpp
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
#include <app/layer.hpp>
|
||||
#include <input/events/char.hpp>
|
||||
#include <input/events/event.hpp>
|
||||
#include <input/events/keyboard.hpp>
|
||||
#include <input/events/mouse.hpp>
|
||||
#include <input/events/window.hpp>
|
||||
|
||||
namespace lt {
|
||||
|
||||
Layer::Layer(std::string name): m_layer_name(std::move(name))
|
||||
{
|
||||
}
|
||||
|
||||
auto Layer::on_event(const Event &event) -> bool
|
||||
{
|
||||
switch (event.get_event_type())
|
||||
{
|
||||
case EventType::MouseMoved: return on_mouse_moved(dynamic_cast<const MouseMovedEvent &>(event));
|
||||
case EventType::ButtonPressed:
|
||||
return on_button_pressed(dynamic_cast<const ButtonPressedEvent &>(event));
|
||||
case EventType::ButtonReleased:
|
||||
return on_button_released(dynamic_cast<const ButtonReleasedEvent &>(event));
|
||||
case EventType::WheelScrolled:
|
||||
return on_wheel_scrolled(dynamic_cast<const WheelScrolledEvent &>(event));
|
||||
|
||||
case EventType::KeyPressed: return on_key_pressed(dynamic_cast<const KeyPressedEvent &>(event));
|
||||
case EventType::KeyRepeated: return on_key_repeat(dynamic_cast<const KeyRepeatEvent &>(event));
|
||||
case EventType::KeyReleased:
|
||||
return on_key_released(dynamic_cast<const KeyReleasedEvent &>(event));
|
||||
case EventType::SetChar: return on_set_char(dynamic_cast<const SetCharEvent &>(event));
|
||||
|
||||
case EventType::WindowClosed:
|
||||
return on_window_closed(dynamic_cast<const WindowClosedEvent &>(event));
|
||||
case EventType::WindowResized:
|
||||
return on_window_resized(dynamic_cast<const WindowResizedEvent &>(event));
|
||||
case EventType::WindowMoved:
|
||||
return on_window_moved(dynamic_cast<const WindowMovedEvent &>(event));
|
||||
case EventType::WindowLostFocus:
|
||||
return on_window_lost_focus(dynamic_cast<const WindowLostFocusEvent &>(event));
|
||||
case EventType::WindowGainFocus:
|
||||
return on_window_gain_focus(dynamic_cast<const WindowGainFocusEvent &>(event));
|
||||
|
||||
default: ensure(false, "Invalid event: {}", event.get_info_lt_log());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lt
|
||||
18
modules/app/private/layer_stack.cpp
Normal file
18
modules/app/private/layer_stack.cpp
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#include <app/layer.hpp>
|
||||
#include <app/layer_stack.hpp>
|
||||
|
||||
namespace lt {
|
||||
|
||||
void LayerStack::attach_layer(Ref<Layer> layer)
|
||||
{
|
||||
log_trc("Attaching layer [{}]", layer->get_name());
|
||||
m_layers.emplace_back(std::move(layer));
|
||||
}
|
||||
|
||||
void LayerStack::detach_layer(const Ref<Layer> &layer)
|
||||
{
|
||||
log_trc("Detaching layer [{}]", layer->get_name());
|
||||
m_layers.erase(std::find(m_layers.begin(), m_layers.end(), layer));
|
||||
}
|
||||
|
||||
} // namespace lt
|
||||
|
|
@ -1,18 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory/reference.hpp>
|
||||
#include <memory/scope.hpp>
|
||||
#include <time/timer.hpp>
|
||||
|
||||
namespace lt::app {
|
||||
namespace lt {
|
||||
|
||||
class ISystem;
|
||||
class Renderer;
|
||||
class Window;
|
||||
class Event;
|
||||
class GraphicsContext;
|
||||
class UserInterface;
|
||||
class LayerStack;
|
||||
|
||||
extern memory::Scope<class Application> create_application();
|
||||
extern Scope<class Application> create_application();
|
||||
|
||||
/** The main application class.
|
||||
* Think of this like an aggregate of systems, you register systems through this interface.
|
||||
* Then they'll tick every "application frame".
|
||||
*/
|
||||
class Application
|
||||
{
|
||||
public:
|
||||
|
|
@ -24,24 +24,54 @@ public:
|
|||
|
||||
auto operator=(Application &&) -> Application & = delete;
|
||||
|
||||
virtual ~Application() = default;
|
||||
virtual ~Application();
|
||||
|
||||
[[nodiscard]] auto sanity_check() const -> bool;
|
||||
|
||||
void game_loop();
|
||||
|
||||
void register_system(memory::Ref<app::ISystem> system);
|
||||
[[nodiscard]] auto get_window() -> Window &
|
||||
{
|
||||
return *m_window;
|
||||
}
|
||||
|
||||
void unregister_system(memory::Ref<app::ISystem> system);
|
||||
[[nodiscard]] auto get_layer_stack() -> LayerStack &
|
||||
{
|
||||
return *m_layer_stack;
|
||||
}
|
||||
|
||||
static void quit();
|
||||
|
||||
protected:
|
||||
Application() = default;
|
||||
Application();
|
||||
|
||||
private:
|
||||
std::vector<memory::Ref<app::ISystem>> m_systems;
|
||||
void update_layers();
|
||||
|
||||
std::vector<memory::Ref<app::ISystem>> m_systems_to_be_unregistered;
|
||||
void render_layers();
|
||||
|
||||
std::vector<memory::Ref<app::ISystem>> m_systems_to_be_registered;
|
||||
void render_user_interface();
|
||||
|
||||
void poll_events();
|
||||
|
||||
void on_event(const Event &event);
|
||||
|
||||
void log_debug_data() const;
|
||||
|
||||
Timer m_timer;
|
||||
|
||||
Scope<Window> m_window;
|
||||
|
||||
Scope<UserInterface> m_user_interface;
|
||||
|
||||
Scope<GraphicsContext> m_graphics_context;
|
||||
|
||||
Scope<Renderer> m_renderer;
|
||||
|
||||
Scope<LayerStack> m_layer_stack;
|
||||
|
||||
static Application *s_instance;
|
||||
};
|
||||
|
||||
|
||||
} // namespace lt::app
|
||||
} // namespace lt
|
||||
|
|
|
|||
|
|
@ -1,28 +1,27 @@
|
|||
#pragma once
|
||||
|
||||
#include <app/application.hpp>
|
||||
#include <logger/logger.hpp>
|
||||
#include <memory/scope.hpp>
|
||||
|
||||
auto main(int argc, char *argv[]) -> int32_t
|
||||
int main(int argc, char *argv[]) // NOLINT
|
||||
try
|
||||
{
|
||||
std::ignore = argc;
|
||||
std::ignore = argv;
|
||||
|
||||
auto application = lt::memory::Scope<lt::app::Application> {};
|
||||
application = lt::app::create_application();
|
||||
if (!application)
|
||||
{
|
||||
throw std::runtime_error { "Failed to create application\n" };
|
||||
}
|
||||
auto application = lt::Scope<lt::Application> {};
|
||||
|
||||
application = lt::create_application();
|
||||
|
||||
lt::ensure(application, "Failed to create application");
|
||||
lt::ensure(application->sanity_check(), "Failed to verify the sanity of the application");
|
||||
|
||||
application->game_loop();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
catch (const std::exception &exp)
|
||||
{
|
||||
lt::log::critical("Terminating due to uncaught exception:");
|
||||
lt::log::critical("\texception.what(): {}", exp.what());
|
||||
log_crt("Terminating due to uncaught exception:");
|
||||
log_crt("\texception.what(): {}", exp.what());
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
|
|
|||
116
modules/app/public/layer.hpp
Normal file
116
modules/app/public/layer.hpp
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
#pragma once
|
||||
|
||||
namespace lt {
|
||||
|
||||
class Event;
|
||||
|
||||
class MouseMovedEvent;
|
||||
class ButtonPressedEvent;
|
||||
class ButtonReleasedEvent;
|
||||
class WheelScrolledEvent;
|
||||
class KeyPressedEvent;
|
||||
class KeyRepeatEvent;
|
||||
class KeyReleasedEvent;
|
||||
class SetCharEvent;
|
||||
class WindowClosedEvent;
|
||||
class WindowResizedEvent;
|
||||
class WindowMovedEvent;
|
||||
class WindowLostFocusEvent;
|
||||
class WindowGainFocusEvent;
|
||||
|
||||
class Layer
|
||||
{
|
||||
public:
|
||||
Layer(std::string name);
|
||||
|
||||
virtual ~Layer() = default;
|
||||
|
||||
[[nodiscard]] auto get_name() const -> const std::string &
|
||||
{
|
||||
return m_layer_name;
|
||||
}
|
||||
|
||||
virtual void on_update(float deltaTime)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void on_user_interface_update()
|
||||
{
|
||||
}
|
||||
|
||||
virtual void on_render()
|
||||
{
|
||||
}
|
||||
|
||||
auto on_event(const Event &event) -> bool;
|
||||
|
||||
protected:
|
||||
std::string m_layer_name;
|
||||
|
||||
virtual auto on_mouse_moved(const MouseMovedEvent & /*event*/) -> bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual auto on_button_pressed(const ButtonPressedEvent & /*event*/) -> bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual auto on_button_released(const ButtonReleasedEvent & /*event*/) -> bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual auto on_wheel_scrolled(const WheelScrolledEvent & /*event*/) -> bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual auto on_key_pressed(const KeyPressedEvent & /*event*/) -> bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual auto on_key_repeat(const KeyRepeatEvent & /*event*/) -> bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual auto on_key_released(const KeyReleasedEvent & /*event*/) -> bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual auto on_set_char(const SetCharEvent & /*event*/) -> bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual auto on_window_closed(const WindowClosedEvent & /*event*/) -> bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual auto on_window_resized(const WindowResizedEvent & /*event*/) -> bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual auto on_window_moved(const WindowMovedEvent & /*event*/) -> bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual auto on_window_lost_focus(const WindowLostFocusEvent & /*event*/) -> bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual auto on_window_gain_focus(const WindowGainFocusEvent & /*event*/) -> bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace lt
|
||||
50
modules/app/public/layer_stack.hpp
Normal file
50
modules/app/public/layer_stack.hpp
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
#pragma once
|
||||
|
||||
namespace lt {
|
||||
|
||||
class Layer;
|
||||
class Event;
|
||||
|
||||
class LayerStack
|
||||
{
|
||||
public:
|
||||
template<typename Layer_T, typename... Args>
|
||||
void emplace_layer(Args &&...args)
|
||||
{
|
||||
attach_layer(create_ref<Layer_T>(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
void attach_layer(Ref<Layer> layer);
|
||||
|
||||
void detach_layer(const Ref<Layer> &layer);
|
||||
|
||||
[[nodiscard]] auto is_empty() const -> bool
|
||||
{
|
||||
return m_layers.empty();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto begin() -> std::vector<Ref<Layer>>::iterator
|
||||
{
|
||||
return m_layers.begin();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto end() -> std::vector<Ref<Layer>>::iterator
|
||||
{
|
||||
return m_layers.end();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto rbegin() -> std::vector<Ref<Layer>>::reverse_iterator
|
||||
{
|
||||
return m_layers.rbegin();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto rend() -> std::vector<Ref<Layer>>::reverse_iterator
|
||||
{
|
||||
return m_layers.rend();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<Ref<Layer>> m_layers;
|
||||
};
|
||||
|
||||
} // namespace lt
|
||||
|
|
@ -1,112 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <logger/logger.hpp>
|
||||
|
||||
namespace lt::app {
|
||||
|
||||
/** Information required to tick a system.
|
||||
* @note May be used across an entire application-frame (consisting of multiple systems ticking)
|
||||
*/
|
||||
struct TickInfo
|
||||
{
|
||||
using Timepoint_T = std::chrono::time_point<std::chrono::steady_clock>;
|
||||
|
||||
using Duration_T = std::chrono::duration<double>;
|
||||
|
||||
/** Duration since previous tick's end_time to current tick's start_time. */
|
||||
Duration_T delta_time {};
|
||||
|
||||
/** Maximum duration the system is expected to finish ticking in.
|
||||
*
|
||||
* if end_time - start_time > budget -> the system exceeded its ticking budget.
|
||||
* else end_time - start_time < budget -> the system ticked properly.
|
||||
*
|
||||
* In other words, end_time is expected to be less than start_time + budget.
|
||||
*/
|
||||
Duration_T budget {};
|
||||
|
||||
/** Exact time which ticking started. */
|
||||
Timepoint_T start_time;
|
||||
};
|
||||
|
||||
/** Information about how a system's tick performed */
|
||||
struct TickResult
|
||||
{
|
||||
using Timepoint_T = std::chrono::time_point<std::chrono::steady_clock>;
|
||||
|
||||
using Duration_T = std::chrono::duration<double>;
|
||||
|
||||
/** The info supplied to the system for ticking. */
|
||||
TickInfo info;
|
||||
|
||||
/** Equivalent to end_time - info.start_time. */
|
||||
Duration_T duration {};
|
||||
|
||||
/** Exact time which ticking ended. */
|
||||
Timepoint_T end_time;
|
||||
};
|
||||
|
||||
|
||||
struct SystemDiagnosis
|
||||
{
|
||||
enum class Severity : uint8_t
|
||||
{
|
||||
verbose,
|
||||
info,
|
||||
warning,
|
||||
error,
|
||||
fatal,
|
||||
};
|
||||
|
||||
std::string message;
|
||||
|
||||
std::string code;
|
||||
|
||||
Severity severity;
|
||||
};
|
||||
|
||||
class SystemStats
|
||||
{
|
||||
public:
|
||||
void push_diagnosis(SystemDiagnosis &&diagnosis)
|
||||
{
|
||||
auto diag = m_diagnosis.emplace_back(std::move(diagnosis));
|
||||
|
||||
log::debug("message: {}", diag.message);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto empty_diagnosis() const -> bool
|
||||
{
|
||||
return m_diagnosis.empty();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<SystemDiagnosis> m_diagnosis;
|
||||
};
|
||||
|
||||
class ISystem
|
||||
{
|
||||
public:
|
||||
ISystem() = default;
|
||||
|
||||
virtual ~ISystem() = default;
|
||||
|
||||
ISystem(ISystem &&) = default;
|
||||
|
||||
ISystem(const ISystem &) = delete;
|
||||
|
||||
auto operator=(ISystem &&) -> ISystem & = default;
|
||||
|
||||
auto operator=(const ISystem &) -> ISystem & = delete;
|
||||
|
||||
virtual void on_register() = 0;
|
||||
|
||||
virtual void on_unregister() = 0;
|
||||
|
||||
virtual void tick(TickInfo tick) = 0;
|
||||
|
||||
[[nodiscard]] virtual auto get_last_tick_result() const -> const TickResult & = 0;
|
||||
};
|
||||
|
||||
} // namespace lt::app
|
||||
|
|
@ -1,6 +1,10 @@
|
|||
add_library_module(libasset_baker bakers.cpp)
|
||||
target_link_libraries(libasset_baker PUBLIC assets logger lt_debug tbb)
|
||||
add_test_module(libasset_baker bakers.test.cpp)
|
||||
add_executable_module(
|
||||
asset_baker entrypoint/baker.cpp
|
||||
)
|
||||
|
||||
add_executable_module(asset_baker entrypoint/baker.cpp)
|
||||
target_link_libraries(asset_baker PRIVATE libasset_baker)
|
||||
target_link_libraries(
|
||||
asset_baker
|
||||
PRIVATE asset_parser
|
||||
PRIVATE stb::stb
|
||||
PRIVATE logger
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,2 +0,0 @@
|
|||
#include <asset_baker/bakers.hpp>
|
||||
#include <test/test.hpp>
|
||||
|
|
@ -1,7 +1,69 @@
|
|||
#include <asset_baker/bakers.hpp>
|
||||
#include <assets/shader.hpp>
|
||||
#include <asset_parser/assets/text.hpp>
|
||||
#include <asset_parser/assets/texture.hpp>
|
||||
#include <asset_parser/parser.hpp>
|
||||
#include <filesystem>
|
||||
#include <logger/logger.hpp>
|
||||
|
||||
void try_packing_texture(
|
||||
const std::filesystem::path &in_path,
|
||||
const std::filesystem::path &out_path
|
||||
)
|
||||
{
|
||||
auto texture_loader = lt::TextureLoaderFactory::create(in_path.extension().string());
|
||||
if (!texture_loader)
|
||||
{
|
||||
// Don't log anything; this is expected.
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Assets::TextureAsset::pack(texture_loader->load(in_path), out_path);
|
||||
|
||||
log_inf("Packed a texture asset:");
|
||||
log_inf("\tloader : {}", texture_loader->get_name());
|
||||
log_inf("\tin path: {}", in_path.string());
|
||||
log_inf("\tout path: {}", out_path.string());
|
||||
}
|
||||
catch (const std::exception &exp)
|
||||
{
|
||||
log_err("Failed to pack texture asset:");
|
||||
log_err("\tloader : {}", texture_loader->get_name());
|
||||
log_err("\tin path : {}", in_path.string());
|
||||
log_err("\tout path: {}", out_path.string());
|
||||
log_err("\texp.what: {}", exp.what());
|
||||
}
|
||||
}
|
||||
|
||||
void try_packing_text(const std::filesystem::path &in_path, const std::filesystem::path &out_path)
|
||||
{
|
||||
auto text_loader = lt::TextLoaderFactory::create(in_path.extension().string());
|
||||
if (!text_loader)
|
||||
{
|
||||
// Don't log anything; this is expected.
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Assets::TextAsset::pack(text_loader->load(in_path), out_path);
|
||||
|
||||
log_inf("Packed a text asset:");
|
||||
log_inf("\tloader : {}", text_loader->get_name());
|
||||
log_inf("\tin path: {}", in_path.string());
|
||||
log_inf("\tout path: {}", out_path.string());
|
||||
}
|
||||
catch (const std::exception &exp)
|
||||
{
|
||||
log_err("Failed to pack a text asset:");
|
||||
log_err("\tloader : {}", text_loader->get_name());
|
||||
log_err("\tin path : {}", in_path.string());
|
||||
log_err("\tout path: {}", out_path.string());
|
||||
log_err("\texp.what: {}", exp.what());
|
||||
}
|
||||
}
|
||||
|
||||
auto main(int argc, char *argv[]) -> int32_t
|
||||
try
|
||||
{
|
||||
|
|
@ -19,24 +81,20 @@ try
|
|||
}
|
||||
|
||||
const auto &in_path = directory_iterator.path();
|
||||
const auto out_path = std::format("{}.asset", in_path.c_str());
|
||||
|
||||
if (in_path.extension() == ".vert")
|
||||
{
|
||||
bake_shader(in_path, out_path, lt::assets::ShaderAsset::Type::vertex);
|
||||
}
|
||||
else if (in_path.extension() == ".frag")
|
||||
{
|
||||
bake_shader(in_path, out_path, lt::assets::ShaderAsset::Type::fragment);
|
||||
}
|
||||
auto out_path = in_path;
|
||||
out_path.replace_extension(".asset");
|
||||
|
||||
try_packing_texture(in_path, out_path);
|
||||
try_packing_text(in_path, out_path);
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
catch (const std::exception &exp)
|
||||
{
|
||||
lt::log::critical("Terminating due to uncaught exception:");
|
||||
lt::log::critical("\texception.what: {}:", exp.what());
|
||||
log_crt("Terminating due to uncaught exception:");
|
||||
log_crt("\texception.what: {}:", exp.what());
|
||||
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,65 +1,184 @@
|
|||
#pragma once
|
||||
|
||||
#include <assets/shader.hpp>
|
||||
#include <asset_parser/assets/text.hpp>
|
||||
#include <asset_parser/assets/texture.hpp>
|
||||
#include <filesystem>
|
||||
#include <logger/logger.hpp>
|
||||
#include <string_view>
|
||||
#include <unordered_set>
|
||||
|
||||
inline void bake_shader(
|
||||
const std::filesystem::path &in_path,
|
||||
const std::filesystem::path &out_path,
|
||||
lt::assets::ShaderAsset::Type type
|
||||
)
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include <stb_image.h>
|
||||
|
||||
namespace lt {
|
||||
|
||||
class Loader
|
||||
{
|
||||
using lt::assets::ShaderAsset;
|
||||
using enum lt::assets::ShaderAsset::Type;
|
||||
public:
|
||||
[[nodiscard]] virtual auto get_name() const -> std::string_view = 0;
|
||||
|
||||
auto glsl_path = in_path.string();
|
||||
auto spv_path = std::format("{}.spv", glsl_path);
|
||||
lt::log::trace(
|
||||
"Compiling {} shader {} -> {}",
|
||||
type == vertex ? "vertex" : "fragment",
|
||||
glsl_path,
|
||||
spv_path
|
||||
);
|
||||
Loader() = default;
|
||||
|
||||
// Don't bother linking to shaderc, just invoke the command with a system call.
|
||||
// NOLINTNEXTLINE(concurrency-mt-unsafe)
|
||||
system(
|
||||
std::format(
|
||||
"glslc --target-env=vulkan1.4 -std=450core -fshader-stage={} {} -o {}",
|
||||
type == vertex ? "vert" : "frag",
|
||||
glsl_path,
|
||||
spv_path
|
||||
)
|
||||
.c_str()
|
||||
);
|
||||
Loader(Loader &&) = default;
|
||||
|
||||
auto stream = std::ifstream(spv_path, std::ios::binary);
|
||||
lt::ensure(
|
||||
stream.is_open(),
|
||||
"Failed to open compiled {} shader at: {}",
|
||||
type == vertex ? "vert" : "frag",
|
||||
spv_path
|
||||
);
|
||||
Loader(const Loader &) = delete;
|
||||
|
||||
stream.seekg(0, std::ios::end);
|
||||
const auto size = stream.tellg();
|
||||
auto operator=(Loader &&) -> Loader & = default;
|
||||
|
||||
auto bytes = std::vector<std::byte>(size);
|
||||
stream.seekg(0, std::ios::beg);
|
||||
stream.read((char *)bytes.data(), size); // NOLINT
|
||||
lt::log::debug("BYTES: {}", bytes.size());
|
||||
stream.close();
|
||||
std::filesystem::remove(spv_path);
|
||||
auto operator=(const Loader &) -> Loader & = delete;
|
||||
|
||||
ShaderAsset::pack(
|
||||
out_path,
|
||||
lt::assets::AssetMetadata {
|
||||
.version = lt::assets::current_version,
|
||||
.type = ShaderAsset::asset_type_identifier,
|
||||
},
|
||||
ShaderAsset::Metadata {
|
||||
.type = type,
|
||||
},
|
||||
std::move(bytes)
|
||||
);
|
||||
}
|
||||
virtual ~Loader() = default;
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
class TextureLoader: public Loader
|
||||
{
|
||||
public:
|
||||
TextureLoader() = default;
|
||||
|
||||
[[nodiscard]] virtual auto load(std::filesystem::path file_path) const
|
||||
-> Assets::TextureAsset::PackageData
|
||||
= 0;
|
||||
};
|
||||
|
||||
|
||||
class StbLoader: public TextureLoader
|
||||
{
|
||||
public:
|
||||
StbLoader() = default;
|
||||
|
||||
void load(std::filesystem::path path);
|
||||
|
||||
[[nodiscard]] static auto get_supported_extensions() -> std::unordered_set<std::string_view>
|
||||
{
|
||||
return { ".png" };
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_name() const -> std::string_view override
|
||||
{
|
||||
return "StbLoader";
|
||||
}
|
||||
|
||||
[[nodiscard]] auto load(std::filesystem::path file_path) const
|
||||
-> Assets::TextureAsset::PackageData override
|
||||
{
|
||||
auto width = int {};
|
||||
auto height = int {};
|
||||
auto channels = int {};
|
||||
|
||||
auto *pixels = stbi_load(file_path.string().c_str(), &width, &height, &channels, 4);
|
||||
if (!pixels)
|
||||
{
|
||||
throw std::runtime_error {
|
||||
std::format("Failed to load image file at: {} using stbi_load", file_path.string()),
|
||||
};
|
||||
}
|
||||
|
||||
const auto metadata = Assets::Asset::Metadata {
|
||||
.type = Assets::Asset::Type::Texture,
|
||||
};
|
||||
|
||||
const auto texture_metadata = Assets::TextureAsset::Metadata {
|
||||
.format = Assets::TextureAsset::Format::RGBA8,
|
||||
.num_components = static_cast<uint32_t>(channels),
|
||||
.pixel_size = {
|
||||
static_cast<uint32_t>(width),
|
||||
static_cast<uint32_t>(height),
|
||||
{},
|
||||
},
|
||||
};
|
||||
|
||||
auto pixels_blob = Assets::Blob {};
|
||||
pixels_blob.resize(static_cast<size_t>(width) * height * channels);
|
||||
|
||||
// TODO(Light): figure out if it's possible to directly populate a blob with stbi functions
|
||||
memcpy(pixels_blob.data(), pixels, pixels_blob.size());
|
||||
stbi_image_free(pixels);
|
||||
|
||||
return Assets::TextureAsset::PackageData {
|
||||
.metadata = metadata,
|
||||
.texture_metadata = texture_metadata,
|
||||
.pixels = std::move(pixels_blob),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
class TextureLoaderFactory
|
||||
{
|
||||
public:
|
||||
static auto create(std::string_view file_extension) -> std::unique_ptr<TextureLoader>
|
||||
{
|
||||
if (StbLoader::get_supported_extensions().contains(file_extension))
|
||||
{
|
||||
return std::make_unique<StbLoader>();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
class TextLoader: Loader
|
||||
{
|
||||
public:
|
||||
[[nodiscard]] static auto get_supported_extensions() -> std::unordered_set<std::string_view>
|
||||
{
|
||||
return { ".glsl", ".txt", ".hlsl" };
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_name() const -> std::string_view override
|
||||
{
|
||||
return "TextLoader";
|
||||
}
|
||||
|
||||
[[nodiscard]] auto load(const std::filesystem::path &file_path) const
|
||||
-> Assets::TextAsset::PackageData
|
||||
{
|
||||
auto stream = std::ifstream { file_path, std::ios::binary };
|
||||
if (!stream.good())
|
||||
{
|
||||
throw std::runtime_error {
|
||||
std::format(
|
||||
"Failed to open ifstream for text loading of file: {}",
|
||||
file_path.string()
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
auto file_size = std::filesystem::file_size(file_path);
|
||||
|
||||
auto text_blob = Assets::Blob(file_size);
|
||||
|
||||
stream.read((char *)(text_blob.data()), static_cast<long>(file_size)); // NOLINT
|
||||
|
||||
const auto metadata = Assets::Asset::Metadata {
|
||||
.type = Assets::Asset::Type::Text,
|
||||
};
|
||||
|
||||
const auto text_metadata = Assets::TextAsset::Metadata {
|
||||
.lines = {},
|
||||
};
|
||||
|
||||
return Assets::TextAsset::PackageData {
|
||||
.metadata = metadata,
|
||||
.text_metadata = {},
|
||||
.text_blob = std::move(text_blob),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
class TextLoaderFactory
|
||||
{
|
||||
public:
|
||||
static auto create(std::string_view file_extension) -> std::unique_ptr<TextLoader>
|
||||
{
|
||||
if (TextLoader::get_supported_extensions().contains(file_extension))
|
||||
{
|
||||
return std::make_unique<TextLoader>();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace lt
|
||||
|
|
|
|||
9
modules/asset_manager/CMakeLists.txt
Normal file
9
modules/asset_manager/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
add_library_module(asset_manager
|
||||
asset_manager.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
asset_manager
|
||||
PUBLIC asset_parser
|
||||
PRIVATE renderer
|
||||
)
|
||||
92
modules/asset_manager/private/asset_manager.cpp
Normal file
92
modules/asset_manager/private/asset_manager.cpp
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
#include <asset_manager/asset_manager.hpp>
|
||||
#include <asset_parser/assets/text.hpp>
|
||||
#include <asset_parser/assets/texture.hpp>
|
||||
#include <logger/logger.hpp>
|
||||
#include <renderer/graphics_context.hpp>
|
||||
#include <renderer/shader.hpp>
|
||||
#include <renderer/texture.hpp>
|
||||
|
||||
namespace lt {
|
||||
|
||||
/* static */ auto AssetManager::instance() -> AssetManager &
|
||||
{
|
||||
static auto instance = AssetManager {};
|
||||
return instance;
|
||||
}
|
||||
|
||||
void AssetManager::load_shader_impl(
|
||||
const std::string &name,
|
||||
const std::filesystem::path &vertex_path,
|
||||
const std::filesystem::path &pixel_path
|
||||
)
|
||||
{
|
||||
try
|
||||
{
|
||||
log_trc("Loading shader:");
|
||||
log_trc("\tname : {}", name);
|
||||
log_trc("\tvertex path: {}", vertex_path.string());
|
||||
log_trc("\tpixel path : {}", pixel_path.string());
|
||||
|
||||
m_shaders[name] = Ref<Shader>(Shader::create(
|
||||
get_or_load_text_asset(vertex_path.string()),
|
||||
get_or_load_text_asset(pixel_path),
|
||||
GraphicsContext::get_shared_context()
|
||||
));
|
||||
}
|
||||
catch (const std::exception &exp)
|
||||
{
|
||||
log_err("Failed to load shader:");
|
||||
log_err("\tname : {}", name);
|
||||
log_err("\tvertex path: {}", vertex_path.string());
|
||||
log_err("\tpixel path : {}", pixel_path.string());
|
||||
log_err("\texception : {}", exp.what());
|
||||
}
|
||||
}
|
||||
|
||||
void AssetManager::load_texture_impl(const std::string &name, const std::filesystem::path &path)
|
||||
{
|
||||
try
|
||||
{
|
||||
log_trc("Loading texture:");
|
||||
log_trc("\tname: {}", name);
|
||||
log_trc("\tpath: {}", path.string());
|
||||
|
||||
m_textures[name] = Ref<Texture>(
|
||||
Texture::create(get_or_load_texture_asset(path), GraphicsContext::get_shared_context())
|
||||
);
|
||||
}
|
||||
catch (const std::exception &exp)
|
||||
{
|
||||
log_err("Failed to load texture:");
|
||||
log_err("\tname : {}", name);
|
||||
log_err("\tpath : {}", path.string());
|
||||
log_err("\texception: {}", exp.what());
|
||||
}
|
||||
}
|
||||
|
||||
auto AssetManager::get_or_load_text_asset(const std::filesystem::path &path)
|
||||
-> Ref<Assets::TextAsset>
|
||||
{
|
||||
const auto key = std::filesystem::canonical(path).string();
|
||||
if (!m_text_assets.contains(key))
|
||||
{
|
||||
m_text_assets.emplace(key, create_ref<Assets::TextAsset>(path));
|
||||
}
|
||||
|
||||
return m_text_assets[key];
|
||||
}
|
||||
|
||||
auto AssetManager::get_or_load_texture_asset(const std::filesystem::path &path)
|
||||
-> Ref<Assets::TextureAsset>
|
||||
{
|
||||
const auto key = std::filesystem::canonical(path).string();
|
||||
if (!m_texture_assets.contains(key))
|
||||
{
|
||||
m_texture_assets.emplace(key, create_ref<Assets::TextureAsset>(path));
|
||||
}
|
||||
|
||||
return m_texture_assets[key];
|
||||
}
|
||||
|
||||
|
||||
} // namespace lt
|
||||
78
modules/asset_manager/public/asset_manager.hpp
Normal file
78
modules/asset_manager/public/asset_manager.hpp
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace Assets {
|
||||
|
||||
class TextAsset;
|
||||
|
||||
class TextureAsset;
|
||||
|
||||
} // namespace Assets
|
||||
|
||||
namespace lt {
|
||||
|
||||
class Shader;
|
||||
class Texture;
|
||||
|
||||
/**
|
||||
* Asset is the data on the disk.
|
||||
* Resource is the data on the gpu/cpu
|
||||
*
|
||||
* eg. TextureAsset is the file on the disk
|
||||
* eg. Texture is the representation of it in the GPU
|
||||
*/
|
||||
class AssetManager
|
||||
{
|
||||
public:
|
||||
static void load_shader(
|
||||
const std::string &name,
|
||||
const std::filesystem::path &vertex_path,
|
||||
const std::filesystem::path &pixel_path
|
||||
)
|
||||
{
|
||||
instance().load_shader_impl(name, vertex_path, pixel_path);
|
||||
}
|
||||
|
||||
static void load_texture(const std::string &name, const std::filesystem::path &path)
|
||||
{
|
||||
instance().load_texture_impl(name, path);
|
||||
}
|
||||
|
||||
static auto get_shader(const std::string &name) -> Ref<Shader>
|
||||
{
|
||||
return instance().m_shaders[name];
|
||||
}
|
||||
|
||||
static auto get_texture(const std::string &name) -> Ref<Texture>
|
||||
{
|
||||
return instance().m_textures[name];
|
||||
}
|
||||
|
||||
private:
|
||||
AssetManager() = default;
|
||||
|
||||
static auto instance() -> AssetManager &;
|
||||
|
||||
void load_shader_impl(
|
||||
const std::string &name,
|
||||
const std::filesystem::path &vertex_path,
|
||||
const std::filesystem::path &pixel_path
|
||||
);
|
||||
|
||||
void load_texture_impl(const std::string &name, const std::filesystem::path &path);
|
||||
|
||||
auto get_or_load_text_asset(const std::filesystem::path &path) -> Ref<Assets::TextAsset>;
|
||||
|
||||
auto get_or_load_texture_asset(const std::filesystem::path &path) -> Ref<Assets::TextureAsset>;
|
||||
|
||||
std::unordered_map<std::string, Ref<Assets::TextAsset>> m_text_assets;
|
||||
|
||||
std::unordered_map<std::string, Ref<Assets::TextureAsset>> m_texture_assets;
|
||||
|
||||
std::unordered_map<std::string, Ref<Shader>> m_shaders;
|
||||
|
||||
std::unordered_map<std::string, Ref<Texture>> m_textures;
|
||||
};
|
||||
|
||||
} // namespace lt
|
||||
11
modules/asset_parser/CMakeLists.txt
Normal file
11
modules/asset_parser/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
add_library_module(asset_parser
|
||||
parser.cpp
|
||||
assets/texture.cpp
|
||||
assets/text.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
asset_parser
|
||||
PRIVATE LZ4::lz4_static
|
||||
PRIVATE logger
|
||||
)
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
#include <asset_parser/assets/text.hpp>
|
||||
#include <lz4.h>
|
||||
|
||||
namespace Assets {
|
||||
|
||||
|
|
|
|||
165
modules/asset_parser/private/assets/texture.cpp
Normal file
165
modules/asset_parser/private/assets/texture.cpp
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
#include <asset_parser/assets/texture.hpp>
|
||||
#include <lz4.h>
|
||||
|
||||
namespace Assets {
|
||||
|
||||
/* static */ void TextureAsset::pack(const PackageData &data, const std::filesystem::path &out_path)
|
||||
{
|
||||
const auto &[metadata, texture_metadata, pixels] = data;
|
||||
|
||||
auto stream = std::ofstream { out_path, std::ios::binary | std::ios::trunc };
|
||||
if (!stream.is_open())
|
||||
{
|
||||
throw std::runtime_error {
|
||||
std::format("Failed to open ofstream for packing texture at: {}", out_path.string())
|
||||
};
|
||||
}
|
||||
stream.seekp(0);
|
||||
|
||||
// NOLINTBEGIN(cppcoreguidelines-pro-type-cstyle-cast)
|
||||
stream.write((char *)¤t_version, sizeof(current_version));
|
||||
|
||||
stream.write((char *)&metadata, sizeof(metadata));
|
||||
stream.write((char *)&texture_metadata, sizeof(texture_metadata));
|
||||
|
||||
constexpr auto number_of_blobs = uint32_t { 1 };
|
||||
stream.write((char *)&number_of_blobs, sizeof(number_of_blobs));
|
||||
|
||||
auto pixels_metadata = BlobMetadata {
|
||||
.tag = BlobMetadata::Tag::color,
|
||||
.offset = static_cast<size_t>(stream.tellp()) + sizeof(BlobMetadata),
|
||||
.compression_type = CompressionType::None,
|
||||
.compressed_size = pixels.size(),
|
||||
.uncompressed_size = pixels.size(),
|
||||
};
|
||||
|
||||
stream.write((char *)&pixels_metadata, sizeof(pixels_metadata));
|
||||
stream.write((char *)&pixels[0], static_cast<long>(pixels.size()));
|
||||
// NOLINTEND(cppcoreguidelines-pro-type-cstyle-cast)
|
||||
}
|
||||
|
||||
TextureAsset::TextureAsset(const std::filesystem::path &path)
|
||||
{
|
||||
m_stream = std::ifstream { path, std::ios::binary };
|
||||
if (!m_stream.is_open())
|
||||
{
|
||||
throw std::runtime_error {
|
||||
std::format("Failed to open ifstream for loading texture asset at: {}", path.string())
|
||||
};
|
||||
}
|
||||
|
||||
// NOLINTBEGIN(cppcoreguidelines-pro-type-cstyle-cast)
|
||||
m_stream.read((char *)&version, sizeof(version));
|
||||
m_stream.read((char *)&m_asset_metadata, sizeof(m_asset_metadata));
|
||||
m_stream.read((char *)&m_metadata, sizeof(m_metadata));
|
||||
|
||||
auto num_blobs = uint32_t {};
|
||||
m_stream.read((char *)&num_blobs, sizeof(num_blobs));
|
||||
if (num_blobs != 1)
|
||||
{
|
||||
throw std::runtime_error {
|
||||
std::format("Failed to load texture asset: invalid number of blobs: {}", num_blobs)
|
||||
};
|
||||
}
|
||||
|
||||
m_stream.read((char *)&m_pixel_blob_metadata, sizeof(m_pixel_blob_metadata));
|
||||
if (m_pixel_blob_metadata.tag != BlobMetadata::Tag::color)
|
||||
{
|
||||
throw std::runtime_error {
|
||||
std::format(
|
||||
"Failed to load texture asset: invalid blob tag, expected {}, got {}",
|
||||
std::to_underlying(BlobMetadata::Tag::color),
|
||||
std::to_underlying(m_pixel_blob_metadata.tag)
|
||||
),
|
||||
};
|
||||
}
|
||||
// NOLINTEND(cppcoreguidelines-pro-type-cstyle-cast)
|
||||
}
|
||||
|
||||
void TextureAsset::unpack_blob(
|
||||
BlobMetadata::Tag tag,
|
||||
std::byte *destination,
|
||||
size_t destination_capacity
|
||||
)
|
||||
{
|
||||
if (tag != BlobMetadata::Tag::color)
|
||||
{
|
||||
throw std::runtime_error {
|
||||
std::format("Invalid tag for unpack_blob of TextureAsset: {}", std::to_underlying(tag))
|
||||
};
|
||||
}
|
||||
|
||||
m_stream.seekg(static_cast<long>(m_pixel_blob_metadata.offset));
|
||||
switch (m_pixel_blob_metadata.compression_type)
|
||||
{
|
||||
case Assets::CompressionType::None:
|
||||
if (m_pixel_blob_metadata.uncompressed_size != m_pixel_blob_metadata.compressed_size)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"Failed to unpack blob from TextureAsset: "
|
||||
"compressed/uncompressed size mismatch for no compression "
|
||||
"type"
|
||||
);
|
||||
}
|
||||
|
||||
if (m_pixel_blob_metadata.uncompressed_size > destination_capacity)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"Failed to unpack blob from TextureAsset: "
|
||||
"uncompressed_size > destination_capacity, unpacking "
|
||||
"would result in segfault"
|
||||
);
|
||||
}
|
||||
|
||||
if (!m_stream.is_open())
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"Failed to unpack blob from TextureAsset: ifstream is "
|
||||
"closed"
|
||||
);
|
||||
}
|
||||
|
||||
m_stream.read(
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
|
||||
(char *)destination,
|
||||
static_cast<long>(m_pixel_blob_metadata.uncompressed_size)
|
||||
);
|
||||
|
||||
return;
|
||||
|
||||
default:
|
||||
throw std::runtime_error(
|
||||
std::format(
|
||||
"Failed to unpack blob from TextureAsset: unsupported "
|
||||
"compression type: {}",
|
||||
std::to_underlying(m_pixel_blob_metadata.compression_type)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] auto TextureAsset::get_asset_metadata() const -> const Asset::Metadata &
|
||||
{
|
||||
return m_asset_metadata;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto TextureAsset::get_metadata() const -> const Metadata &
|
||||
{
|
||||
return m_metadata;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto TextureAsset::get_blob_metadata(BlobMetadata::Tag tag) const
|
||||
-> const BlobMetadata &
|
||||
{
|
||||
if (tag != BlobMetadata::Tag::color)
|
||||
{
|
||||
throw std::runtime_error { std::format(
|
||||
"Invalid tag for get_blob_metadata of TextureAsset: {}",
|
||||
std::to_underlying(tag)
|
||||
) };
|
||||
}
|
||||
|
||||
return m_pixel_blob_metadata;
|
||||
}
|
||||
|
||||
} // namespace Assets
|
||||
62
modules/asset_parser/private/parser.cpp
Normal file
62
modules/asset_parser/private/parser.cpp
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
#include <asset_parser/parser.hpp>
|
||||
#include <format>
|
||||
#include <fstream>
|
||||
#include <utility>
|
||||
|
||||
namespace Assets {
|
||||
|
||||
// void Asset::unpack(std::byte *destination)
|
||||
// {
|
||||
// if (!m_stream.is_open())
|
||||
// {
|
||||
// throw std::logic_error {
|
||||
// "Failed to unpack asset: "
|
||||
// "ifstream is closed",
|
||||
// };
|
||||
// }
|
||||
//
|
||||
// switch (m_metadata.blob_compression_type)
|
||||
// {
|
||||
// case CompressionType::None:
|
||||
// if (m_metadata.packed_size != m_metadata.unpacked_size)
|
||||
// {
|
||||
// throw std::logic_error {
|
||||
// "Failed to unpack asset: "
|
||||
// "compression type set to none but packed/unpacked sizes differ",
|
||||
// };
|
||||
// }
|
||||
//
|
||||
// m_stream.read(
|
||||
// std::bit_cast<char *>(destination),
|
||||
// static_cast<long>(m_metadata.packed_size)
|
||||
// );
|
||||
// m_stream.close();
|
||||
//
|
||||
// case CompressionType::LZ4:
|
||||
// m_stream.close();
|
||||
// throw std::logic_error {
|
||||
// "Failed to unpack asset: "
|
||||
// "LZ4 compression is not implemented yet",
|
||||
// };
|
||||
//
|
||||
//
|
||||
// case CompressionType::LZ4HC:
|
||||
// m_stream.close();
|
||||
// throw std::logic_error {
|
||||
// "Failed to unpack asset: "
|
||||
// "LZ4HC compression is not implemented yet",
|
||||
// };
|
||||
//
|
||||
// default:
|
||||
// m_stream.close();
|
||||
// throw std::logic_error {
|
||||
// std::format(
|
||||
// "Failed to unpack asset: "
|
||||
// "Compression type was not recognized: {}",
|
||||
// std::to_underlying(m_metadata.blob_compression_type)
|
||||
// ),
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
|
||||
} // namespace Assets
|
||||
58
modules/asset_parser/public/assets/text.hpp
Normal file
58
modules/asset_parser/public/assets/text.hpp
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
#pragma once
|
||||
|
||||
#include <asset_parser/compressors/compressors.hpp>
|
||||
#include <asset_parser/parser.hpp>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <logger/logger.hpp>
|
||||
|
||||
namespace Assets {
|
||||
|
||||
class TextAsset: public Asset
|
||||
{
|
||||
public:
|
||||
struct Metadata
|
||||
{
|
||||
uint32_t lines {};
|
||||
};
|
||||
|
||||
/** Data required to pack a text asset */
|
||||
struct PackageData
|
||||
{
|
||||
Asset::Metadata metadata;
|
||||
|
||||
Metadata text_metadata;
|
||||
|
||||
Blob text_blob;
|
||||
};
|
||||
|
||||
static void pack(const PackageData &data, const std::filesystem::path &out_path);
|
||||
|
||||
TextAsset(const std::filesystem::path &path);
|
||||
|
||||
void unpack_blob(
|
||||
BlobMetadata::Tag tag,
|
||||
std::byte *destination,
|
||||
size_t destination_capacity
|
||||
) const;
|
||||
|
||||
[[nodiscard]] auto get_asset_metadata() const -> const Asset::Metadata &;
|
||||
|
||||
[[nodiscard]] auto get_metadata() const -> const Metadata &;
|
||||
|
||||
[[nodiscard]] auto get_blob_metadata(BlobMetadata::Tag tag) const -> const BlobMetadata &;
|
||||
|
||||
private:
|
||||
uint32_t version {};
|
||||
|
||||
Asset::Metadata m_asset_metadata {};
|
||||
|
||||
Metadata m_metadata {};
|
||||
|
||||
BlobMetadata m_text_blob_metadata {};
|
||||
|
||||
mutable std::ifstream m_stream;
|
||||
};
|
||||
|
||||
} // namespace Assets
|
||||
64
modules/asset_parser/public/assets/texture.hpp
Normal file
64
modules/asset_parser/public/assets/texture.hpp
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
#pragma once
|
||||
|
||||
#include <asset_parser/compressors/compressors.hpp>
|
||||
#include <asset_parser/parser.hpp>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <logger/logger.hpp>
|
||||
|
||||
namespace Assets {
|
||||
|
||||
class TextureAsset: public Asset
|
||||
{
|
||||
public:
|
||||
enum class Format : uint32_t // NOLINT(performance-enum-size)
|
||||
{
|
||||
None = 0,
|
||||
RGBA8,
|
||||
};
|
||||
|
||||
struct Metadata
|
||||
{
|
||||
Format format;
|
||||
|
||||
uint32_t num_components;
|
||||
|
||||
std::array<uint32_t, 3> pixel_size;
|
||||
};
|
||||
|
||||
/** Data required to pack a texture asset */
|
||||
struct PackageData
|
||||
{
|
||||
Asset::Metadata metadata;
|
||||
|
||||
Metadata texture_metadata;
|
||||
|
||||
Blob pixels;
|
||||
};
|
||||
|
||||
static void pack(const PackageData &data, const std::filesystem::path &out_path);
|
||||
|
||||
TextureAsset(const std::filesystem::path &path);
|
||||
|
||||
void unpack_blob(BlobMetadata::Tag tag, std::byte *destination, size_t destination_capacity);
|
||||
|
||||
[[nodiscard]] auto get_asset_metadata() const -> const Asset::Metadata &;
|
||||
|
||||
[[nodiscard]] auto get_metadata() const -> const Metadata &;
|
||||
|
||||
[[nodiscard]] auto get_blob_metadata(BlobMetadata::Tag tag) const -> const BlobMetadata &;
|
||||
|
||||
private:
|
||||
uint32_t version {};
|
||||
|
||||
Asset::Metadata m_asset_metadata {};
|
||||
|
||||
Metadata m_metadata {};
|
||||
|
||||
BlobMetadata m_pixel_blob_metadata {};
|
||||
|
||||
std::ifstream m_stream;
|
||||
};
|
||||
|
||||
} // namespace Assets
|
||||
14
modules/asset_parser/public/compressors/compressors.hpp
Normal file
14
modules/asset_parser/public/compressors/compressors.hpp
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Assets {
|
||||
|
||||
enum class CompressionType : uint32_t // NOLINT(performance-enum-size)
|
||||
{
|
||||
None,
|
||||
LZ4,
|
||||
LZ4HC,
|
||||
};
|
||||
|
||||
}
|
||||
68
modules/asset_parser/public/parser.hpp
Normal file
68
modules/asset_parser/public/parser.hpp
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
#pragma once
|
||||
|
||||
#include <asset_parser/compressors/compressors.hpp>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <logger/logger.hpp>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Assets {
|
||||
|
||||
constexpr auto current_version = uint32_t { 1 };
|
||||
|
||||
struct BlobMetadata
|
||||
{
|
||||
enum class Tag : uint8_t
|
||||
{
|
||||
text,
|
||||
color,
|
||||
depth,
|
||||
vertices,
|
||||
indices,
|
||||
};
|
||||
|
||||
Tag tag;
|
||||
|
||||
size_t offset;
|
||||
|
||||
CompressionType compression_type;
|
||||
|
||||
size_t compressed_size;
|
||||
|
||||
size_t uncompressed_size;
|
||||
};
|
||||
|
||||
|
||||
using Blob = std::vector<std::byte>;
|
||||
|
||||
class Asset
|
||||
{
|
||||
public:
|
||||
enum class Type : uint32_t // NOLINT(performance-enum-size)
|
||||
{
|
||||
None,
|
||||
Texture,
|
||||
Text,
|
||||
Mesh,
|
||||
Material,
|
||||
};
|
||||
|
||||
struct Metadata
|
||||
{
|
||||
Type type;
|
||||
};
|
||||
|
||||
Asset() = default;
|
||||
|
||||
/** Directly unpacks from disk to the destination.
|
||||
*
|
||||
* @note The destination MUST have at least blob_metadata.unpacked_size bytes available for
|
||||
* writing, otherwise segfault could occur!
|
||||
*/
|
||||
void unpack_blob(BlobMetadata::Tag blob_tag, std::byte *destination);
|
||||
};
|
||||
|
||||
|
||||
} // namespace Assets
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
add_library_module(assets shader.cpp)
|
||||
|
||||
target_link_libraries(assets PUBLIC logger lt_debug)
|
||||
|
||||
add_test_module(assets shader.test.cpp)
|
||||
|
|
@ -1,148 +0,0 @@
|
|||
#include <assets/shader.hpp>
|
||||
|
||||
namespace lt::assets {
|
||||
|
||||
constexpr auto total_metadata_size = //
|
||||
sizeof(AssetMetadata::type) //
|
||||
+ sizeof(AssetMetadata::version) //
|
||||
+ sizeof(ShaderAsset::Metadata::type) //
|
||||
+ sizeof(BlobMetadata::tag) //
|
||||
+ sizeof(BlobMetadata::offset) //
|
||||
+ sizeof(BlobMetadata::compression_type) //
|
||||
+ sizeof(BlobMetadata::compressed_size) //
|
||||
+ sizeof(BlobMetadata::uncompressed_size);
|
||||
|
||||
ShaderAsset::ShaderAsset(const std::filesystem::path &path): m_stream(path)
|
||||
{
|
||||
ensure(m_stream.is_open(), "Failed to open shader asset at: {}", path.string());
|
||||
const auto read = [this](auto &field) {
|
||||
m_stream.read(std::bit_cast<char *>(&field), sizeof(field));
|
||||
};
|
||||
|
||||
m_stream.seekg(0, std::ifstream::end);
|
||||
const auto file_size = static_cast<size_t>(m_stream.tellg());
|
||||
ensure(
|
||||
file_size > total_metadata_size,
|
||||
"Failed to open shader asset at: {}, file smaller than metadata: {} < {}",
|
||||
path.string(),
|
||||
total_metadata_size,
|
||||
file_size
|
||||
);
|
||||
|
||||
m_stream.seekg(0, std::ifstream::beg);
|
||||
read(m_asset_metadata.type);
|
||||
read(m_asset_metadata.version);
|
||||
read(m_metadata.type);
|
||||
read(m_code_blob_metadata.tag);
|
||||
read(m_code_blob_metadata.offset);
|
||||
read(m_code_blob_metadata.compression_type);
|
||||
read(m_code_blob_metadata.compressed_size);
|
||||
read(m_code_blob_metadata.uncompressed_size);
|
||||
|
||||
ensure(
|
||||
m_asset_metadata.type == asset_type_identifier,
|
||||
"Failed to open shader asset at: {}, incorrect asset type: {} != {}",
|
||||
path.string(),
|
||||
m_asset_metadata.type,
|
||||
asset_type_identifier
|
||||
);
|
||||
|
||||
ensure(
|
||||
m_asset_metadata.version == current_version,
|
||||
"Failed to open shader asset at: {}, version mismatch: {} != {}",
|
||||
path.string(),
|
||||
m_asset_metadata.version,
|
||||
current_version
|
||||
);
|
||||
|
||||
ensure(
|
||||
std::to_underlying(m_metadata.type) <= std::to_underlying(Type::compute),
|
||||
"Failed to open shader asset at: {}, invalid shader type: {}",
|
||||
path.string(),
|
||||
std::to_underlying(m_metadata.type)
|
||||
);
|
||||
|
||||
ensure(
|
||||
m_code_blob_metadata.tag == std::to_underlying(BlobTag::code),
|
||||
"Failed to open shader asset at: {}, invalid blob tag: {}",
|
||||
path.string(),
|
||||
m_code_blob_metadata.tag
|
||||
);
|
||||
|
||||
ensure(
|
||||
m_code_blob_metadata.offset + m_code_blob_metadata.compressed_size <= file_size,
|
||||
"Failed to open shader asset at: {}, file smaller than blob: {} > {} + {}",
|
||||
path.string(),
|
||||
file_size,
|
||||
m_code_blob_metadata.offset,
|
||||
m_code_blob_metadata.compressed_size
|
||||
);
|
||||
}
|
||||
|
||||
/* static */ void ShaderAsset::pack(
|
||||
const std::filesystem::path &destination,
|
||||
AssetMetadata asset_metadata,
|
||||
Metadata metadata,
|
||||
Blob code_blob
|
||||
)
|
||||
{
|
||||
auto stream = std::ofstream {
|
||||
destination,
|
||||
std::ios::binary | std::ios::trunc,
|
||||
};
|
||||
|
||||
const auto code_blob_metadata = BlobMetadata {
|
||||
.tag = std::to_underlying(BlobTag::code),
|
||||
.offset = total_metadata_size,
|
||||
.compression_type = CompressionType::none,
|
||||
.compressed_size = code_blob.size(),
|
||||
.uncompressed_size = code_blob.size(),
|
||||
};
|
||||
|
||||
ensure(stream.is_open(), "Failed to pack shader asset to {}", destination.string());
|
||||
const auto write = [&stream](auto &field) {
|
||||
stream.write(std::bit_cast<char *>(&field), sizeof(field));
|
||||
};
|
||||
write(asset_metadata.type);
|
||||
write(asset_metadata.version);
|
||||
write(metadata.type);
|
||||
write(code_blob_metadata.tag);
|
||||
write(code_blob_metadata.offset);
|
||||
write(code_blob_metadata.compression_type);
|
||||
write(code_blob_metadata.compressed_size);
|
||||
write(code_blob_metadata.uncompressed_size);
|
||||
stream.write(std::bit_cast<char *>(code_blob.data()), static_cast<long long>(code_blob.size()));
|
||||
}
|
||||
|
||||
void ShaderAsset::unpack_to(BlobTag tag, std::span<std::byte> destination) const
|
||||
{
|
||||
ensure(tag == BlobTag::code, "Invalid blob tag for shader asset: {}", std::to_underlying(tag));
|
||||
|
||||
ensure(
|
||||
destination.size() >= m_code_blob_metadata.uncompressed_size,
|
||||
"Failed to unpack shader blob {} to destination ({}) of size {} since it's smaller "
|
||||
"than the blobl's uncompressed size: {}",
|
||||
std::to_underlying(tag),
|
||||
std::bit_cast<size_t>(destination.data()),
|
||||
destination.size(),
|
||||
m_code_blob_metadata.uncompressed_size
|
||||
);
|
||||
|
||||
m_stream.seekg(static_cast<long long>(m_code_blob_metadata.offset));
|
||||
m_stream.read(
|
||||
std::bit_cast<char *>(destination.data()),
|
||||
static_cast<long long>(m_code_blob_metadata.uncompressed_size)
|
||||
);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto ShaderAsset::unpack(BlobTag tag) const -> Blob
|
||||
{
|
||||
ensure(tag == BlobTag::code, "Invalid blob tag for shader asset: {}", std::to_underlying(tag));
|
||||
|
||||
auto blob = Blob(m_code_blob_metadata.uncompressed_size);
|
||||
unpack_to(tag, blob);
|
||||
|
||||
return blob;
|
||||
}
|
||||
|
||||
} // namespace lt::assets
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
#include <assets/shader.hpp>
|
||||
#include <ranges>
|
||||
#include <test/test.hpp>
|
||||
|
||||
using ::lt::assets::AssetMetadata;
|
||||
using ::lt::assets::BlobMetadata;
|
||||
using ::lt::assets::ShaderAsset;
|
||||
using ::lt::test::Case;
|
||||
using ::lt::test::expect_eq;
|
||||
using ::lt::test::expect_throw;
|
||||
using ::lt::test::expect_true;
|
||||
using ::lt::test::Suite;
|
||||
|
||||
const auto test_data_path = std::filesystem::path { "./data/test_assets" };
|
||||
const auto tmp_path = std::filesystem::path { "/tmp/lt_assets_tests/" };
|
||||
|
||||
Suite raii = "shader_raii"_suite = [] {
|
||||
std::filesystem::current_path(test_data_path);
|
||||
std::filesystem::create_directories(tmp_path);
|
||||
|
||||
Case { "happy path won't throw" } = [] {
|
||||
|
||||
};
|
||||
|
||||
Case { "many won't freeze/throw" } = [] {
|
||||
};
|
||||
|
||||
Case { "unhappy path throws" } = [] {
|
||||
expect_throw([] { ShaderAsset { "random_path" }; });
|
||||
};
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-interfaces-global-init)
|
||||
Suite packing = "shader_pack"_suite = [] {
|
||||
Case { "" } = [] {
|
||||
const auto out_path = tmp_path / "shader_packing";
|
||||
auto dummy_blob = lt::assets::Blob {};
|
||||
for (auto idx : std::views::iota(0, 255))
|
||||
{
|
||||
dummy_blob.emplace_back(static_cast<std::byte>(idx));
|
||||
}
|
||||
|
||||
const auto expected_size = //
|
||||
sizeof(AssetMetadata::type) //
|
||||
+ sizeof(AssetMetadata::version) //
|
||||
+ sizeof(ShaderAsset::Metadata::type) //
|
||||
+ sizeof(BlobMetadata::tag) //
|
||||
+ sizeof(BlobMetadata::offset) //
|
||||
+ sizeof(BlobMetadata::compression_type) //
|
||||
+ sizeof(BlobMetadata::compressed_size) //
|
||||
+ sizeof(BlobMetadata::uncompressed_size) //
|
||||
+ dummy_blob.size();
|
||||
|
||||
ShaderAsset::pack(
|
||||
out_path,
|
||||
lt::assets::AssetMetadata {
|
||||
.version = lt::assets::current_version,
|
||||
.type = ShaderAsset::asset_type_identifier,
|
||||
},
|
||||
ShaderAsset::Metadata {
|
||||
.type = ShaderAsset::Type::vertex,
|
||||
},
|
||||
std::move(dummy_blob)
|
||||
);
|
||||
|
||||
auto stream = std::ifstream {
|
||||
out_path,
|
||||
std::ios::binary,
|
||||
};
|
||||
expect_true(stream.is_open());
|
||||
|
||||
stream.seekg(0, std::ios::end);
|
||||
const auto file_size = static_cast<size_t>(stream.tellg());
|
||||
expect_eq(file_size, expected_size);
|
||||
stream.close();
|
||||
|
||||
auto shader_asset = ShaderAsset { out_path };
|
||||
|
||||
const auto &asset_metadata = shader_asset.get_asset_metadata();
|
||||
expect_eq(asset_metadata.type, ShaderAsset::asset_type_identifier);
|
||||
expect_eq(asset_metadata.version, lt::assets::current_version);
|
||||
|
||||
const auto &metadata = shader_asset.get_metadata();
|
||||
expect_eq(metadata.type, ShaderAsset::Type::vertex);
|
||||
|
||||
auto blob = shader_asset.unpack(ShaderAsset::BlobTag::code);
|
||||
expect_eq(blob.size(), 255);
|
||||
|
||||
for (auto idx : std::views::iota(0, 255))
|
||||
{
|
||||
expect_eq(blob[idx], static_cast<std::byte>(idx));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
// TO BE DOOO
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
namespace lt::assets {
|
||||
|
||||
using Type_T = std::array<const char, 16>;
|
||||
|
||||
using Tag_T = uint8_t;
|
||||
|
||||
using Version = uint8_t;
|
||||
|
||||
using Blob = std::vector<std::byte>;
|
||||
|
||||
constexpr auto current_version = Version { 1u };
|
||||
|
||||
enum class CompressionType : uint8_t
|
||||
{
|
||||
none,
|
||||
lz4,
|
||||
lz4_hc,
|
||||
};
|
||||
|
||||
struct AssetMetadata
|
||||
{
|
||||
Version version;
|
||||
|
||||
Type_T type;
|
||||
};
|
||||
|
||||
struct BlobMetadata
|
||||
{
|
||||
Tag_T tag;
|
||||
|
||||
size_t offset;
|
||||
|
||||
CompressionType compression_type;
|
||||
|
||||
size_t compressed_size;
|
||||
|
||||
size_t uncompressed_size;
|
||||
};
|
||||
|
||||
} // namespace lt::assets
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <assets/metadata.hpp>
|
||||
|
||||
namespace lt::assets {
|
||||
|
||||
class ShaderAsset
|
||||
{
|
||||
public:
|
||||
static constexpr auto asset_type_identifier = Type_T { "SHADER_________" };
|
||||
|
||||
enum class BlobTag : Tag_T
|
||||
{
|
||||
code,
|
||||
};
|
||||
|
||||
enum class Type : uint8_t
|
||||
{
|
||||
vertex,
|
||||
fragment,
|
||||
geometry,
|
||||
compute,
|
||||
};
|
||||
|
||||
struct Metadata
|
||||
{
|
||||
Type type;
|
||||
};
|
||||
|
||||
static void pack(
|
||||
const std::filesystem::path &destination,
|
||||
AssetMetadata asset_metadata,
|
||||
Metadata metadata,
|
||||
Blob code_blob
|
||||
);
|
||||
|
||||
ShaderAsset(const std::filesystem::path &path);
|
||||
|
||||
void unpack_to(BlobTag tag, std::span<std::byte> destination) const;
|
||||
|
||||
[[nodiscard]] auto unpack(BlobTag tag) const -> Blob;
|
||||
|
||||
[[nodiscard]] auto get_asset_metadata() const -> const AssetMetadata &
|
||||
{
|
||||
return m_asset_metadata;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_metadata() const -> const Metadata &
|
||||
{
|
||||
return m_metadata;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_blob_metadata(BlobTag tag) const -> const BlobMetadata &
|
||||
{
|
||||
ensure(
|
||||
tag == BlobTag::code,
|
||||
"Invalid blob tag for shader asset: {}",
|
||||
std::to_underlying(tag)
|
||||
);
|
||||
|
||||
return m_code_blob_metadata;
|
||||
}
|
||||
|
||||
private:
|
||||
AssetMetadata m_asset_metadata {};
|
||||
|
||||
Metadata m_metadata {};
|
||||
|
||||
BlobMetadata m_code_blob_metadata {};
|
||||
|
||||
mutable std::ifstream m_stream;
|
||||
};
|
||||
|
||||
} // namespace lt::assets
|
||||
2
modules/base/CMakeLists.txt
Normal file
2
modules/base/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
add_library_module(base)
|
||||
target_precompile_headers(base INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/private/pch.hpp)
|
||||
36
modules/base/private/pch.hpp
Normal file
36
modules/base/private/pch.hpp
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
|
||||
#include <base/base.hpp>
|
||||
|
||||
/* windows */
|
||||
#ifdef _WIN32
|
||||
#define NOMINMAX
|
||||
#include <Windows.h>
|
||||
#undef NOMINMAX
|
||||
#endif
|
||||
|
||||
/** Stdlib */
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <bitset>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <math.h>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <span>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <time.h>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
91
modules/base/public/base.hpp
Normal file
91
modules/base/public/base.hpp
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace lt {
|
||||
|
||||
// Ref (Ref)
|
||||
template<typename t>
|
||||
using Ref = std::shared_ptr<t>;
|
||||
|
||||
template<typename t, typename... Args>
|
||||
constexpr Ref<t> create_ref(Args &&...args)
|
||||
{
|
||||
return std::make_shared<t>(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<typename t>
|
||||
constexpr Ref<t> make_ref(t *rawPointer)
|
||||
{
|
||||
return std::shared_ptr<t>(rawPointer);
|
||||
}
|
||||
|
||||
// Scope (std::unique_ptr)
|
||||
template<typename t>
|
||||
using Scope = std::unique_ptr<t>;
|
||||
|
||||
template<typename t, typename... Args>
|
||||
constexpr std::unique_ptr<t> create_scope(Args &&...args)
|
||||
{
|
||||
return std::make_unique<t>(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<typename t>
|
||||
constexpr std::unique_ptr<t> make_scope(t *rawPointer)
|
||||
{
|
||||
return std::unique_ptr<t>(rawPointer);
|
||||
}
|
||||
|
||||
} // namespace lt
|
||||
|
||||
#define lt_win(x) // windows
|
||||
#define lt_lin(x) // linux
|
||||
#define lt_mac(x) // mac
|
||||
|
||||
enum class Platform : uint8_t
|
||||
{
|
||||
windows,
|
||||
|
||||
/** Named like so because "linux" is a built-in identifier. */
|
||||
gnu,
|
||||
|
||||
mac,
|
||||
};
|
||||
|
||||
namespace constants {
|
||||
|
||||
#if defined(LIGHT_PLATFORM_WINDOWS)
|
||||
#define lt_win(x)
|
||||
constexpr auto platform = Platform::windows;
|
||||
constexpr auto platform_name = "windows";
|
||||
|
||||
#undef LIGHT_PLATFORM_WINDOWS
|
||||
|
||||
#elif defined(LIGHT_PLATFORM_LINUX)
|
||||
#define lt_lin(x) x
|
||||
constexpr auto platform = Platform::gnu;
|
||||
constexpr auto platform_name = "linux";
|
||||
|
||||
#elif defined(LIGHT_PLATFORM_MAC)
|
||||
#define lt_mac(x) x
|
||||
constexpr auto platform = Platform::mac;
|
||||
constexpr auto platform_name = "mac";
|
||||
|
||||
#else
|
||||
#error "Unsupported platform: Unknown"
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
} // namespace constants
|
||||
|
||||
/* bit-wise */
|
||||
constexpr auto bit(auto x)
|
||||
{
|
||||
return 1 << x;
|
||||
}
|
||||
|
||||
/* token */
|
||||
#define lt_pair_token_value_to_name(token) { token, #token }
|
||||
#define lt_pair_token_name_to_value(token) { #token, token }
|
||||
#define lt_token_name(token) #token
|
||||
|
|
@ -1 +0,0 @@
|
|||
add_library_module(bitwise)
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace lt::bitwise {
|
||||
|
||||
/* bit-wise */
|
||||
constexpr auto bit(uint32_t x) -> uint32_t
|
||||
{
|
||||
return 1u << x;
|
||||
}
|
||||
|
||||
} // namespace lt::bitwise
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
add_library_module(camera)
|
||||
add_library_module(camera camera.cpp scene.cpp)
|
||||
|
||||
target_link_libraries(camera INTERFACE math)
|
||||
target_link_libraries(camera PUBLIC math)
|
||||
|
|
|
|||
6
modules/camera/private/camera.cpp
Normal file
6
modules/camera/private/camera.cpp
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#include <camera/camera.hpp>
|
||||
|
||||
namespace lt {
|
||||
|
||||
|
||||
}
|
||||
83
modules/camera/private/scene.cpp
Normal file
83
modules/camera/private/scene.cpp
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
#include <camera/scene.hpp>
|
||||
#include <math/algebra.hpp>
|
||||
#include <math/trig.hpp>
|
||||
|
||||
namespace lt {
|
||||
|
||||
SceneCamera::SceneCamera()
|
||||
: m_orthographic_specification { .size = 1000.0f, .near_plane = -1.0f, .far_plane = 10000.0f }
|
||||
, m_perspective_specification { .vertical_fov = math::radians(45.0f),
|
||||
.near_plane = 0.01f,
|
||||
.far_plane = 10000.0f }
|
||||
, m_aspect_ratio(16.0f / 9.0f)
|
||||
|
||||
{
|
||||
calculate_projection();
|
||||
}
|
||||
|
||||
void SceneCamera::set_viewport_size(unsigned int width, unsigned int height)
|
||||
{
|
||||
m_aspect_ratio = static_cast<float>(width) / static_cast<float>(height);
|
||||
calculate_projection();
|
||||
}
|
||||
|
||||
void SceneCamera::set_projection_type(ProjectionType projection_type)
|
||||
{
|
||||
m_projection_type = projection_type;
|
||||
calculate_projection();
|
||||
}
|
||||
|
||||
void SceneCamera::set_orthographic_size(float size)
|
||||
{
|
||||
m_orthographic_specification.size = size;
|
||||
calculate_projection();
|
||||
}
|
||||
|
||||
void SceneCamera::set_orthographic_far_plane(float far_plane)
|
||||
{
|
||||
m_orthographic_specification.far_plane = far_plane;
|
||||
calculate_projection();
|
||||
}
|
||||
|
||||
void SceneCamera::set_orthographic_near_plane(float near_plane)
|
||||
{
|
||||
m_orthographic_specification.near_plane = near_plane;
|
||||
calculate_projection();
|
||||
}
|
||||
|
||||
void SceneCamera::set_perspective_vertical_fov(float vertical_fov)
|
||||
{
|
||||
m_perspective_specification.vertical_fov = vertical_fov;
|
||||
calculate_projection();
|
||||
}
|
||||
|
||||
void SceneCamera::set_perspective_far_plane(float far_plane)
|
||||
{
|
||||
m_perspective_specification.far_plane = far_plane;
|
||||
calculate_projection();
|
||||
}
|
||||
|
||||
void SceneCamera::set_perspective_near_plane(float near_plane)
|
||||
{
|
||||
m_perspective_specification.near_plane = near_plane;
|
||||
calculate_projection();
|
||||
}
|
||||
|
||||
void SceneCamera::calculate_projection()
|
||||
{
|
||||
// TODO(Light): implement ortho perspective
|
||||
if (m_projection_type == ProjectionType::Orthographic)
|
||||
{
|
||||
// throw std::runtime_error { "ortho perspective not supported yet" };
|
||||
}
|
||||
|
||||
// defaults to perspective for now...
|
||||
m_projection = math::perspective(
|
||||
m_perspective_specification.vertical_fov,
|
||||
m_aspect_ratio,
|
||||
m_perspective_specification.near_plane,
|
||||
m_perspective_specification.far_plane
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace lt
|
||||
35
modules/camera/public/camera.hpp
Normal file
35
modules/camera/public/camera.hpp
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#pragma once
|
||||
|
||||
#include <math/mat4.hpp>
|
||||
#include <math/vec4.hpp>
|
||||
|
||||
namespace lt {
|
||||
|
||||
class Camera
|
||||
{
|
||||
public:
|
||||
Camera() = default;
|
||||
|
||||
[[nodiscard]] auto get_projection() const -> const math::mat4 &
|
||||
{
|
||||
return m_projection;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_background_color() const -> const math::vec4 &
|
||||
{
|
||||
return m_background_color;
|
||||
}
|
||||
|
||||
void set_background_color(const math::vec4 &color)
|
||||
{
|
||||
m_background_color = color;
|
||||
}
|
||||
|
||||
protected:
|
||||
math::mat4 m_projection;
|
||||
|
||||
private:
|
||||
math::vec4 m_background_color = math::vec4(1.0f, 0.0f, 0.0f, 1.0f);
|
||||
};
|
||||
|
||||
} // namespace lt
|
||||
29
modules/camera/public/component.hpp
Normal file
29
modules/camera/public/component.hpp
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
|
||||
#include <camera/scene.hpp>
|
||||
|
||||
namespace lt {
|
||||
|
||||
struct CameraComponent
|
||||
{
|
||||
CameraComponent() = default;
|
||||
|
||||
CameraComponent(const CameraComponent &) = default;
|
||||
|
||||
CameraComponent(SceneCamera _camera, bool _isPrimary = false)
|
||||
: camera(_camera)
|
||||
, isPrimary(_isPrimary)
|
||||
{
|
||||
}
|
||||
|
||||
operator SceneCamera() const
|
||||
{
|
||||
return camera;
|
||||
}
|
||||
|
||||
SceneCamera camera;
|
||||
|
||||
bool isPrimary {};
|
||||
};
|
||||
|
||||
} // namespace lt
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <math/mat4.hpp>
|
||||
|
||||
namespace lt::camera::components {
|
||||
|
||||
struct PerspectiveCamera
|
||||
{
|
||||
float vertical_fov {};
|
||||
|
||||
float near_plane {};
|
||||
|
||||
float far_plane {};
|
||||
|
||||
float aspect_ratio {};
|
||||
|
||||
math::vec4 background_color;
|
||||
|
||||
bool is_primary {};
|
||||
};
|
||||
|
||||
} // namespace lt::camera::components
|
||||
100
modules/camera/public/scene.hpp
Normal file
100
modules/camera/public/scene.hpp
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
#pragma once
|
||||
|
||||
#include <camera/camera.hpp>
|
||||
|
||||
namespace lt {
|
||||
|
||||
class SceneCamera: public Camera
|
||||
{
|
||||
public:
|
||||
enum class ProjectionType
|
||||
{
|
||||
Orthographic = 0,
|
||||
Perspetcive = 1
|
||||
};
|
||||
|
||||
struct OrthographicSpecification
|
||||
{
|
||||
float size;
|
||||
|
||||
float near_plane;
|
||||
|
||||
float far_plane;
|
||||
};
|
||||
|
||||
struct PerspectiveSpecification
|
||||
{
|
||||
float vertical_fov;
|
||||
|
||||
float near_plane;
|
||||
|
||||
float far_plane;
|
||||
};
|
||||
|
||||
SceneCamera();
|
||||
|
||||
void set_viewport_size(unsigned int width, unsigned int height);
|
||||
|
||||
void set_projection_type(ProjectionType projection_type);
|
||||
|
||||
void set_orthographic_size(float size);
|
||||
|
||||
void set_orthographic_far_plane(float far_plane);
|
||||
|
||||
void set_orthographic_near_plane(float near_plane);
|
||||
|
||||
void set_perspective_vertical_fov(float vertical_fov);
|
||||
|
||||
void set_perspective_far_plane(float far_plane);
|
||||
|
||||
void set_perspective_near_plane(float near_plane);
|
||||
|
||||
[[nodiscard]] auto get_orthographic_size() const -> float
|
||||
{
|
||||
return m_orthographic_specification.size;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_orthographic_far_plane() const -> float
|
||||
{
|
||||
return m_orthographic_specification.far_plane;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_orthographic_near_plane() const -> float
|
||||
{
|
||||
return m_orthographic_specification.near_plane;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_perspective_vertical_fov() const -> float
|
||||
{
|
||||
return m_perspective_specification.vertical_fov;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_perspective_far_plane() const -> float
|
||||
{
|
||||
return m_perspective_specification.far_plane;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_perspective_near_plane() const -> float
|
||||
{
|
||||
return m_perspective_specification.near_plane;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_projection_type() const -> ProjectionType
|
||||
{
|
||||
return m_projection_type;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
OrthographicSpecification m_orthographic_specification;
|
||||
|
||||
PerspectiveSpecification m_perspective_specification;
|
||||
|
||||
float m_aspect_ratio;
|
||||
|
||||
ProjectionType m_projection_type { ProjectionType::Orthographic };
|
||||
|
||||
void calculate_projection();
|
||||
};
|
||||
|
||||
} // namespace lt
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
add_library_module(lt_debug instrumentor.cpp)
|
||||
target_link_libraries(lt_debug PUBLIC logger)
|
||||
target_precompile_headers(lt_debug PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/private/pch.hpp)
|
||||
target_precompile_headers(lt_debug PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/private/pch.hpp)
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ void Instrumentor::end_session_impl()
|
|||
{
|
||||
if (m_current_session_count == 0u)
|
||||
{
|
||||
log::warn("0 profiling for the ended session");
|
||||
log_wrn("0 profiling for the ended session");
|
||||
}
|
||||
|
||||
m_current_session_count = 0u;
|
||||
|
|
|
|||
|
|
@ -1,34 +1,36 @@
|
|||
#pragma once
|
||||
|
||||
#include <format>
|
||||
#include <source_location>
|
||||
#include <logger/logger.hpp>
|
||||
|
||||
namespace lt {
|
||||
|
||||
template<typename Expression_T, typename... Args_T>
|
||||
struct ensure
|
||||
struct FailedAssertion: std::exception
|
||||
{
|
||||
ensure(
|
||||
const Expression_T &expression,
|
||||
std::format_string<Args_T...> fmt,
|
||||
Args_T &&...args,
|
||||
const std::source_location &location = std::source_location::current()
|
||||
)
|
||||
FailedAssertion(const char *file, int line)
|
||||
{
|
||||
if (!static_cast<bool>(expression))
|
||||
{
|
||||
throw std::runtime_error { std::format(
|
||||
"exception: {}\nlocation: {}:{}",
|
||||
std::format(fmt, std::forward<Args_T>(args)...),
|
||||
location.file_name(),
|
||||
location.line()
|
||||
) };
|
||||
}
|
||||
log_crt("Assertion failed in: {} (line {})", file, line);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Expression_T, typename... Args_T>
|
||||
ensure(Expression_T, std::format_string<Args_T...>, Args_T &&...)
|
||||
-> ensure<Expression_T, Args_T...>;
|
||||
|
||||
template<typename Expression_T, typename... Args>
|
||||
constexpr void ensure(Expression_T &&expression, std::format_string<Args...> fmt, Args &&...args)
|
||||
{
|
||||
if (!static_cast<bool>(expression))
|
||||
{
|
||||
Logger::log(LogLvl::critical, fmt, std::forward<Args>(args)...);
|
||||
throw ::lt::FailedAssertion(__FILE__, __LINE__);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Expression_T>
|
||||
constexpr void ensure(Expression_T &&expression, const char *message)
|
||||
{
|
||||
if (!static_cast<bool>(expression))
|
||||
{
|
||||
Logger::log(LogLvl::critical, message);
|
||||
throw ::lt::FailedAssertion(__FILE__, __LINE__);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lt
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
add_library_module(ecs sparse_set.cpp)
|
||||
target_link_libraries(ecs PUBLIC logger lt_debug memory)
|
||||
|
||||
add_test_module(ecs sparse_set.test.cpp registry.test.cpp)
|
||||
add_library_module(ecs entity.cpp scene.cpp uuid.cpp serializer.cpp)
|
||||
target_link_libraries(ecs
|
||||
PUBLIC logger lt_debug EnTT::EnTT renderer input camera
|
||||
PRIVATE yaml-cpp::yaml-cpp asset_manager
|
||||
)
|
||||
|
|
|
|||
10
modules/ecs/private/entity.cpp
Normal file
10
modules/ecs/private/entity.cpp
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#include <ecs/entity.hpp>
|
||||
#include <ecs/scene.hpp>
|
||||
|
||||
namespace lt {
|
||||
|
||||
Entity::Entity(entt::entity handle, Scene *scene): m_handle(handle), m_scene(scene)
|
||||
{
|
||||
}
|
||||
|
||||
} // namespace lt
|
||||
|
|
@ -1,351 +0,0 @@
|
|||
#include <ecs/registry.hpp>
|
||||
#include <ranges>
|
||||
#include <test/expects.hpp>
|
||||
#include <test/test.hpp>
|
||||
|
||||
using lt::test::Case;
|
||||
using lt::test::expect_unreachable;
|
||||
using lt::test::Suite;
|
||||
|
||||
using lt::test::expect_eq;
|
||||
|
||||
using lt::test::expect_false;
|
||||
using lt::test::expect_true;
|
||||
|
||||
using lt::ecs::EntityId;
|
||||
using lt::ecs::Registry;
|
||||
|
||||
struct Component
|
||||
{
|
||||
int m_int {};
|
||||
std::string m_string;
|
||||
|
||||
[[nodiscard]] friend auto operator==(const Component &lhs, const Component &rhs) -> bool
|
||||
{
|
||||
return lhs.m_int == rhs.m_int && lhs.m_string == rhs.m_string;
|
||||
}
|
||||
};
|
||||
template<>
|
||||
struct std::formatter<Component>
|
||||
{
|
||||
constexpr auto parse(std::format_parse_context &context)
|
||||
{
|
||||
return context.begin();
|
||||
}
|
||||
|
||||
auto format(const Component &val, std::format_context &context) const
|
||||
{
|
||||
return std::format_to(context.out(), "{}, {}", val.m_int, val.m_string);
|
||||
}
|
||||
};
|
||||
|
||||
struct Component_B
|
||||
{
|
||||
float m_float {};
|
||||
|
||||
[[nodiscard]] friend auto operator==(const Component_B lhs, const Component_B &rhs) -> bool
|
||||
{
|
||||
return lhs.m_float == rhs.m_float;
|
||||
}
|
||||
};
|
||||
template<>
|
||||
struct std::formatter<Component_B>
|
||||
{
|
||||
constexpr auto parse(std::format_parse_context &context)
|
||||
{
|
||||
return context.begin();
|
||||
}
|
||||
|
||||
auto format(const Component_B &val, std::format_context &context) const
|
||||
{
|
||||
return std::format_to(context.out(), "{}", val.m_float);
|
||||
}
|
||||
};
|
||||
|
||||
Suite raii = "raii"_suite = [] {
|
||||
Case { "happy path won't throw" } = [] {
|
||||
std::ignore = Registry {};
|
||||
};
|
||||
|
||||
Case { "many won't freeze/throw" } = [] {
|
||||
for (auto idx : std::views::iota(0, 100'000))
|
||||
{
|
||||
std::ignore = Registry {};
|
||||
}
|
||||
};
|
||||
|
||||
Case { "unhappy path throws" } = [] {
|
||||
};
|
||||
|
||||
Case { "post construct has correct state" } = [] {
|
||||
auto registry = Registry {};
|
||||
expect_eq(registry.get_entity_count(), 0);
|
||||
};
|
||||
};
|
||||
|
||||
Suite entity_raii = "entity_raii"_suite = [] {
|
||||
Case { "create_entity returns unique values" } = [] {
|
||||
auto registry = Registry {};
|
||||
auto set = std::unordered_set<EntityId> {};
|
||||
|
||||
for (auto idx : std::views::iota(0, 10'000))
|
||||
{
|
||||
auto entity = registry.create_entity();
|
||||
expect_false(set.contains(entity));
|
||||
|
||||
set.insert(entity);
|
||||
expect_eq(set.size(), idx + 1);
|
||||
}
|
||||
};
|
||||
|
||||
Case { "post create/destroy_entity has correct state" } = [] {
|
||||
auto registry = Registry {};
|
||||
|
||||
auto entities = std::vector<EntityId> {};
|
||||
for (auto idx : std::views::iota(0, 10'000))
|
||||
{
|
||||
entities.emplace_back(registry.create_entity());
|
||||
expect_eq(registry.get_entity_count(), idx + 1);
|
||||
}
|
||||
|
||||
for (auto idx : std::views::iota(0, 10'000))
|
||||
{
|
||||
auto entity = entities.back();
|
||||
registry.destroy_entity(entity);
|
||||
|
||||
entities.pop_back();
|
||||
expect_eq(registry.get_entity_count(), 10'000 - (idx + 1));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
Suite component_raii = "component_raii"_suite = [] {
|
||||
Case { "add has correct state" } = [] {
|
||||
auto registry = Registry {};
|
||||
for (auto idx : std::views::iota(0, 100'000))
|
||||
{
|
||||
auto entity = registry.create_entity();
|
||||
auto &component = registry.add<Component>(
|
||||
entity,
|
||||
{ .m_int = idx, .m_string = std::to_string(idx) }
|
||||
);
|
||||
|
||||
expect_eq(component.m_int, idx);
|
||||
expect_eq(component.m_string, std::to_string(idx));
|
||||
}
|
||||
};
|
||||
|
||||
Case { "remove has correct state" } = [] {
|
||||
auto registry = Registry {};
|
||||
for (auto idx : std::views::iota(0, 100'000))
|
||||
{
|
||||
auto entity = registry.create_entity();
|
||||
auto &component = registry.add<Component>(
|
||||
entity,
|
||||
{ .m_int = idx, .m_string = std::to_string(idx) }
|
||||
);
|
||||
|
||||
expect_eq(component.m_int, idx);
|
||||
expect_eq(component.m_string, std::to_string(idx));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
Suite callbacks = "callbacks"_suite = [] {
|
||||
Case { "connecting on_construct/on_destruct won't throw" } = [] {
|
||||
auto registry = Registry {};
|
||||
registry.connect_on_construct<Component>([&](Registry &, EntityId) {});
|
||||
registry.connect_on_destruct<Component>([&](Registry &, EntityId) {});
|
||||
};
|
||||
|
||||
Case { "on_construct/on_destruct won't get called on unrelated component" } = [] {
|
||||
auto registry = Registry {};
|
||||
registry.connect_on_construct<Component>([&](Registry &, EntityId) {
|
||||
expect_unreachable();
|
||||
});
|
||||
registry.connect_on_destruct<Component>([&](Registry &, EntityId) {
|
||||
expect_unreachable();
|
||||
});
|
||||
|
||||
for (auto idx : std::views::iota(0, 100'000))
|
||||
{
|
||||
registry.add<Component_B>(registry.create_entity(), {});
|
||||
}
|
||||
};
|
||||
|
||||
Case { "on_construct/on_destruct gets called" } = [] {
|
||||
auto registry = Registry {};
|
||||
auto all_entities = std::vector<EntityId> {};
|
||||
auto on_construct_called = std::vector<EntityId> {};
|
||||
auto on_destruct_called = std::vector<EntityId> {};
|
||||
|
||||
registry.connect_on_construct<Component>([&](Registry &, EntityId entity) {
|
||||
on_construct_called.emplace_back(entity);
|
||||
});
|
||||
registry.connect_on_destruct<Component>([&](Registry &, EntityId entity) {
|
||||
on_destruct_called.emplace_back(entity);
|
||||
});
|
||||
|
||||
expect_true(on_construct_called.empty());
|
||||
expect_true(on_destruct_called.empty());
|
||||
for (auto idx : std::views::iota(0, 100'000))
|
||||
{
|
||||
auto entity = all_entities.emplace_back(registry.create_entity());
|
||||
registry.add<Component>(entity, {});
|
||||
}
|
||||
expect_eq(on_construct_called, all_entities);
|
||||
expect_true(on_destruct_called.empty());
|
||||
|
||||
for (auto &entity : all_entities)
|
||||
{
|
||||
registry.remove<Component>(entity);
|
||||
}
|
||||
expect_eq(on_construct_called, all_entities);
|
||||
expect_eq(on_destruct_called, all_entities);
|
||||
};
|
||||
};
|
||||
|
||||
Suite each = "each"_suite = [] {
|
||||
auto registry = Registry {};
|
||||
|
||||
auto shared_entity_counter = 0u;
|
||||
|
||||
auto component_map_a = std::unordered_map<EntityId, Component> {};
|
||||
auto entities_a = std::vector<EntityId> {};
|
||||
|
||||
for (auto idx : std::views::iota(0, 10'000))
|
||||
{
|
||||
auto entity = entities_a.emplace_back(registry.create_entity());
|
||||
auto &component = registry.add<Component>(
|
||||
entity,
|
||||
{ .m_int = idx, .m_string = std::to_string(idx) }
|
||||
);
|
||||
|
||||
component_map_a[entity] = component;
|
||||
}
|
||||
|
||||
auto component_map_b = std::unordered_map<lt::ecs::EntityId, Component_B> {};
|
||||
for (auto idx : std::views::iota(0, 10'000))
|
||||
{
|
||||
auto entity = EntityId {};
|
||||
if (idx % 3 == 0)
|
||||
{
|
||||
entity = entities_a[idx];
|
||||
++shared_entity_counter;
|
||||
}
|
||||
else
|
||||
{
|
||||
entity = registry.create_entity();
|
||||
}
|
||||
auto &component = registry.add<Component_B>(
|
||||
entity,
|
||||
{ .m_float = static_cast<float>(idx) / 2.0f }
|
||||
);
|
||||
|
||||
component_map_b[entity] = component;
|
||||
}
|
||||
|
||||
Case { "each one element" } = [&] {
|
||||
auto counter = 0u;
|
||||
registry.each<Component>([&](EntityId entity, Component &component) {
|
||||
++counter;
|
||||
expect_eq(component_map_a[entity], component);
|
||||
});
|
||||
|
||||
expect_eq(component_map_a.size(), counter);
|
||||
|
||||
counter = 0u;
|
||||
registry.each<Component_B>([&](EntityId entity, Component_B &component) {
|
||||
++counter;
|
||||
expect_eq(component_map_b[entity], component);
|
||||
});
|
||||
expect_eq(component_map_b.size(), counter);
|
||||
};
|
||||
|
||||
Case { "each two element" } = [&] {
|
||||
auto counter = 0u;
|
||||
registry.each<Component, Component_B>(
|
||||
[&](EntityId entity, Component &component_a, Component_B &component_b) {
|
||||
expect_eq(component_map_a[entity], component_a);
|
||||
expect_eq(component_map_b[entity], component_b);
|
||||
++counter;
|
||||
}
|
||||
);
|
||||
|
||||
expect_eq(counter, shared_entity_counter);
|
||||
};
|
||||
};
|
||||
|
||||
Suite views = "views"_suite = [] {
|
||||
auto registry = Registry {};
|
||||
|
||||
auto shared_entity_counter = 0u;
|
||||
|
||||
auto component_map_a = std::unordered_map<EntityId, Component> {};
|
||||
auto entities_a = std::vector<EntityId> {};
|
||||
|
||||
for (auto idx : std::views::iota(0, 10'000))
|
||||
{
|
||||
auto entity = entities_a.emplace_back(registry.create_entity());
|
||||
auto &component = registry.add<Component>(
|
||||
entity,
|
||||
{ .m_int = idx, .m_string = std::to_string(idx) }
|
||||
);
|
||||
|
||||
component_map_a[entity] = component;
|
||||
}
|
||||
|
||||
auto component_map_b = std::unordered_map<EntityId, Component_B> {};
|
||||
for (auto idx : std::views::iota(0, 10'000))
|
||||
{
|
||||
auto entity = EntityId {};
|
||||
if (idx % 3 == 0)
|
||||
{
|
||||
entity = entities_a[idx];
|
||||
++shared_entity_counter;
|
||||
}
|
||||
else
|
||||
{
|
||||
entity = registry.create_entity();
|
||||
}
|
||||
auto &component = registry.add<Component_B>(
|
||||
entity,
|
||||
{ .m_float = static_cast<float>(idx) / 2.0f }
|
||||
);
|
||||
|
||||
component_map_b[entity] = component;
|
||||
}
|
||||
|
||||
|
||||
Case { "view one component" } = [&] {
|
||||
for (const auto &[entity, component] : registry.view<Component>())
|
||||
{
|
||||
expect_eq(component_map_a[entity], component);
|
||||
}
|
||||
|
||||
for (const auto &[entity, component] : registry.view<Component_B>())
|
||||
{
|
||||
expect_eq(component_map_b[entity], component);
|
||||
}
|
||||
};
|
||||
|
||||
Case { "view two component" } = [&] {
|
||||
auto counter = 0u;
|
||||
for (const auto &[entity, component, component_b] : registry.view<Component, Component_B>())
|
||||
{
|
||||
expect_eq(component_map_a[entity], component);
|
||||
expect_eq(component_map_b[entity], component_b);
|
||||
++counter;
|
||||
}
|
||||
expect_eq(counter, shared_entity_counter);
|
||||
|
||||
counter = 0u;
|
||||
for (const auto &[entity, component_b, component] : registry.view<Component_B, Component>())
|
||||
{
|
||||
expect_eq(component_map_b[entity], component_b);
|
||||
expect_eq(component_map_a[entity], component);
|
||||
++counter;
|
||||
}
|
||||
expect_eq(counter, shared_entity_counter);
|
||||
};
|
||||
};
|
||||
110
modules/ecs/private/scene.cpp
Normal file
110
modules/ecs/private/scene.cpp
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
#include <camera/component.hpp>
|
||||
#include <ecs/components.hpp>
|
||||
#include <ecs/entity.hpp>
|
||||
#include <ecs/scene.hpp>
|
||||
#include <renderer/renderer.hpp>
|
||||
|
||||
namespace lt {
|
||||
|
||||
void Scene::on_create()
|
||||
{
|
||||
/* native scripts */
|
||||
{
|
||||
m_registry.view<NativeScriptComponent>().each([](NativeScriptComponent &nsc) {
|
||||
if (nsc.instance == nullptr)
|
||||
{
|
||||
nsc.instance = nsc.CreateInstance();
|
||||
nsc.instance->on_create();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void Scene::on_update(float deltaTime)
|
||||
{
|
||||
/* native scripts */
|
||||
{
|
||||
m_registry.view<NativeScriptComponent>().each([=](NativeScriptComponent &nsc) {
|
||||
nsc.instance->on_update(deltaTime);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void Scene::on_render(const Ref<Framebuffer> &targetFrameBuffer /* = nullptr */)
|
||||
{
|
||||
auto *sceneCamera = (Camera *)nullptr;
|
||||
auto *sceneCameraTransform = (TransformComponent *)nullptr;
|
||||
|
||||
/* scene camera */
|
||||
{
|
||||
m_registry.group(entt::get<TransformComponent, CameraComponent>)
|
||||
.each([&](TransformComponent &transformComp, CameraComponent &cameraComp) {
|
||||
if (cameraComp.isPrimary)
|
||||
{
|
||||
sceneCamera = &cameraComp.camera;
|
||||
sceneCameraTransform = &transformComp;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* draw quads */
|
||||
{
|
||||
if (sceneCamera)
|
||||
{
|
||||
Renderer::begin_scene(sceneCamera, *sceneCameraTransform, targetFrameBuffer);
|
||||
|
||||
m_registry.group(entt::get<TransformComponent, SpriteRendererComponent>)
|
||||
.each([](TransformComponent &transformComp,
|
||||
SpriteRendererComponent &spriteRendererComp) {
|
||||
Renderer::draw_quad(
|
||||
transformComp,
|
||||
spriteRendererComp.tint,
|
||||
spriteRendererComp.texture
|
||||
);
|
||||
});
|
||||
|
||||
Renderer::end_scene();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Scene::create_entity(const std::string &name, const TransformComponent &transform) -> Entity
|
||||
{
|
||||
return create_entity_with_uuid(name, UUID(), transform);
|
||||
}
|
||||
|
||||
auto Scene::get_entity_by_tag(const std::string &tag) -> Entity
|
||||
{
|
||||
// TagComponent tagComp(tag);
|
||||
// entt::entity entity = entt::to_entity(m_registry, tagComp);
|
||||
auto entity = Entity {};
|
||||
|
||||
m_registry.view<TagComponent>().each([&](TagComponent &tagComp) {
|
||||
// if (tagComp.tag == tag)
|
||||
// entity = entity(entt::to_entity(m_registry, tagComp), this);
|
||||
});
|
||||
|
||||
if (entity.is_valid())
|
||||
{
|
||||
return entity;
|
||||
}
|
||||
|
||||
ensure(false, "Scene::get_entity_by_tag: failed to find entity by tag: {}", tag);
|
||||
return {};
|
||||
}
|
||||
|
||||
auto Scene::create_entity_with_uuid(
|
||||
const std::string &name,
|
||||
UUID uuid,
|
||||
const TransformComponent &transform
|
||||
) -> Entity
|
||||
{
|
||||
auto entity = Entity { m_registry.create(), this };
|
||||
entity.add_component<TagComponent>(name);
|
||||
entity.add_component<TransformComponent>(transform);
|
||||
entity.add_component<UUIDComponent>(uuid);
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
} // namespace lt
|
||||
325
modules/ecs/private/serializer.cpp
Normal file
325
modules/ecs/private/serializer.cpp
Normal file
|
|
@ -0,0 +1,325 @@
|
|||
#include <asset_manager/asset_manager.hpp>
|
||||
#include <camera/component.hpp>
|
||||
#include <ecs/components.hpp>
|
||||
#include <ecs/serializer.hpp>
|
||||
#include <math/vec3.hpp>
|
||||
#include <math/vec4.hpp>
|
||||
#include <yaml-cpp/yaml.h>
|
||||
|
||||
namespace YAML {
|
||||
|
||||
template<>
|
||||
struct convert<lt::math::vec3>
|
||||
{
|
||||
static auto encode(const lt::math::vec3 &rhs) -> Node
|
||||
{
|
||||
auto node = Node {};
|
||||
node.push_back(rhs.x);
|
||||
node.push_back(rhs.y);
|
||||
node.push_back(rhs.z);
|
||||
return node;
|
||||
}
|
||||
|
||||
static auto decode(const Node &node, lt::math::vec3 &rhs) -> bool
|
||||
{
|
||||
if (!node.IsSequence() || node.size() != 3)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
rhs.x = node[0].as<float>();
|
||||
rhs.y = node[1].as<float>();
|
||||
rhs.z = node[2].as<float>();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct convert<lt::math::vec4>
|
||||
{
|
||||
static auto encode(const lt::math::vec4 &rhs) -> Node
|
||||
{
|
||||
auto node = Node {};
|
||||
node.push_back(rhs.x);
|
||||
node.push_back(rhs.y);
|
||||
node.push_back(rhs.z);
|
||||
node.push_back(rhs.w);
|
||||
return node;
|
||||
}
|
||||
|
||||
static auto decode(const Node &node, lt::math::vec4 &rhs) -> bool
|
||||
{
|
||||
if (!node.IsSequence() || node.size() != 4)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
rhs.x = node[0].as<float>();
|
||||
rhs.y = node[1].as<float>();
|
||||
rhs.z = node[2].as<float>();
|
||||
rhs.w = node[3].as<float>();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
} // namespace YAML
|
||||
|
||||
namespace lt {
|
||||
|
||||
auto operator<<(YAML::Emitter &out, const math::vec3 &v) -> YAML::Emitter &
|
||||
{
|
||||
out << YAML::Flow;
|
||||
out << YAML::BeginSeq << v.x << v.y << v.z << YAML::EndSeq;
|
||||
return out;
|
||||
}
|
||||
|
||||
auto operator<<(YAML::Emitter &out, const math::vec4 &v) -> YAML::Emitter &
|
||||
{
|
||||
out << YAML::Flow;
|
||||
out << YAML::BeginSeq << v.x << v.y << v.z << v.w << YAML::EndSeq;
|
||||
return out;
|
||||
}
|
||||
|
||||
SceneSerializer::SceneSerializer(const Ref<Scene> &scene): m_scene(scene)
|
||||
{
|
||||
}
|
||||
|
||||
void SceneSerializer::serialize(const std::string &filePath)
|
||||
{
|
||||
auto out = YAML::Emitter {};
|
||||
out << YAML::BeginMap; // Scene
|
||||
out << YAML::Key << "Scene" << YAML::Value << "Untitled";
|
||||
|
||||
out << YAML::Key << "Entities" << YAML::Value << YAML::BeginSeq;
|
||||
for (auto [entityID, storage] : m_scene->m_registry.storage())
|
||||
{
|
||||
auto entity = Entity { static_cast<entt::entity>(entityID), m_scene.get() };
|
||||
if (!entity.is_valid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
serialize_entity(out, entity);
|
||||
};
|
||||
out << YAML::EndSeq;
|
||||
out << YAML::EndMap;
|
||||
|
||||
std::filesystem::create_directories(filePath.substr(0ull, filePath.find_last_of('\\')));
|
||||
|
||||
auto fout = std::ofstream { filePath };
|
||||
if (!fout.is_open())
|
||||
{
|
||||
log_trc("Failed to create fout at: {}", filePath);
|
||||
}
|
||||
|
||||
fout << out.c_str();
|
||||
}
|
||||
|
||||
auto SceneSerializer::deserialize(const std::string &file_path) -> bool
|
||||
{
|
||||
auto stream = std::ifstream { file_path };
|
||||
auto ss = std::stringstream {};
|
||||
ss << stream.rdbuf();
|
||||
|
||||
auto data = YAML::Load(ss.str());
|
||||
if (!data["Scene"])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto sceneName = data["Scene"].as<std::string>();
|
||||
log_trc("Deserializing scene: '{}'", sceneName);
|
||||
|
||||
auto entities = data["Entities"];
|
||||
if (entities)
|
||||
{
|
||||
auto texturePaths = std::unordered_set<std::string> {};
|
||||
|
||||
for (auto entity : entities)
|
||||
{
|
||||
auto uuid = entity["entity"].as<uint64_t>(); // #todo
|
||||
|
||||
auto name = std::string {};
|
||||
auto tagComponent = entity["TagComponent"];
|
||||
if (tagComponent)
|
||||
{
|
||||
name = tagComponent["Tag"].as<std::string>();
|
||||
}
|
||||
|
||||
log_trc("Deserialized entity '{}' : '{}'", uuid, name);
|
||||
|
||||
auto deserializedEntity = m_scene->create_entity_with_uuid(name, uuid);
|
||||
|
||||
auto gg = deserializedEntity.get_component<TagComponent>();
|
||||
log_trc("tag: {}", gg.tag);
|
||||
auto transformComponent = entity["TransformComponent"];
|
||||
if (transformComponent)
|
||||
{
|
||||
auto &entityTransforomComponent = deserializedEntity
|
||||
.get_component<TransformComponent>();
|
||||
|
||||
entityTransforomComponent.translation = transformComponent["Translation"]
|
||||
.as<math::vec3>();
|
||||
entityTransforomComponent.rotation = transformComponent["Rotation"]
|
||||
.as<math::vec3>();
|
||||
entityTransforomComponent.scale = transformComponent["Scale"].as<math::vec3>();
|
||||
}
|
||||
|
||||
/* #TEMPORARY SOLUTION# */
|
||||
auto spriteRendererComponent = entity["SpriteRendererComponent"];
|
||||
if (spriteRendererComponent)
|
||||
{
|
||||
auto &entitySpriteRendererComponent = deserializedEntity
|
||||
.add_component<SpriteRendererComponent>();
|
||||
entitySpriteRendererComponent.tint = spriteRendererComponent["Tint"]
|
||||
.as<math::vec4>();
|
||||
|
||||
auto texturePath = spriteRendererComponent["Texture"].as<std::string>();
|
||||
|
||||
if (!texturePaths.contains(texturePath))
|
||||
{
|
||||
AssetManager::load_texture(texturePath, texturePath);
|
||||
texturePaths.insert(texturePath);
|
||||
}
|
||||
|
||||
entitySpriteRendererComponent.texture = AssetManager::get_texture(texturePath);
|
||||
}
|
||||
/* #TEMPORARY SOLUTION# */
|
||||
|
||||
auto cameraComponent = entity["CameraComponent"];
|
||||
if (cameraComponent)
|
||||
{
|
||||
auto &entityCameraComponent = deserializedEntity.add_component<CameraComponent>();
|
||||
|
||||
const auto &cameraSpecifications = cameraComponent["Camera"];
|
||||
entityCameraComponent.camera.set_projection_type(
|
||||
(SceneCamera::ProjectionType)cameraSpecifications["ProjectionType"].as<int>()
|
||||
);
|
||||
|
||||
entityCameraComponent.camera.set_orthographic_size(
|
||||
cameraSpecifications["OrthographicSize"].as<float>()
|
||||
);
|
||||
entityCameraComponent.camera.set_orthographic_near_plane(
|
||||
cameraSpecifications["OrthographicNearPlane"].as<float>()
|
||||
);
|
||||
entityCameraComponent.camera.set_orthographic_far_plane(
|
||||
cameraSpecifications["OrthographicFarPlane"].as<float>()
|
||||
);
|
||||
|
||||
entityCameraComponent.camera.set_perspective_vertical_fov(
|
||||
cameraSpecifications["PerspectiveVerticalFOV"].as<float>()
|
||||
);
|
||||
entityCameraComponent.camera.set_perspective_near_plane(
|
||||
cameraSpecifications["PerspectiveNearPlane"].as<float>()
|
||||
);
|
||||
entityCameraComponent.camera.set_perspective_far_plane(
|
||||
cameraSpecifications["PerspectiveFarPlane"].as<float>()
|
||||
);
|
||||
|
||||
entityCameraComponent.camera.set_background_color(
|
||||
cameraSpecifications["BackgroundColor"].as<math::vec4>()
|
||||
);
|
||||
|
||||
entityCameraComponent.isPrimary = cameraComponent["IsPrimary"].as<bool>();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void SceneSerializer::serialize_binary(const std::string & /*filePath*/)
|
||||
{
|
||||
log_err("NO_IMPLEMENT");
|
||||
}
|
||||
|
||||
auto SceneSerializer::deserialize_binary(const std::string & /*filePath*/) -> bool
|
||||
{
|
||||
log_err("NO_IMPLEMENT");
|
||||
return false;
|
||||
}
|
||||
|
||||
void SceneSerializer::serialize_entity(YAML::Emitter &out, Entity entity)
|
||||
{
|
||||
out << YAML::BeginMap; // entity
|
||||
out << YAML::Key << "entity" << YAML::Value << entity.get_uuid(); // dummy uuid
|
||||
|
||||
if (entity.has_component<TagComponent>())
|
||||
{
|
||||
out << YAML::Key << "TagComponent";
|
||||
out << YAML::BeginMap; // tag component
|
||||
|
||||
auto &tagComponent = entity.get_component<TagComponent>().tag;
|
||||
out << YAML::Key << "Tag" << YAML::Value << tagComponent;
|
||||
|
||||
out << YAML::EndMap; // tag component
|
||||
}
|
||||
|
||||
if (entity.has_component<TransformComponent>())
|
||||
{
|
||||
out << YAML::Key << "TransformComponent";
|
||||
out << YAML::BeginMap; // transform component
|
||||
|
||||
auto &transformComponent = entity.get_component<TransformComponent>();
|
||||
|
||||
out << YAML::Key << "Translation" << YAML::Value << transformComponent.translation;
|
||||
out << YAML::Key << "Rotation" << YAML::Value << transformComponent.rotation;
|
||||
out << YAML::Key << "Scale" << YAML::Value << transformComponent.scale;
|
||||
|
||||
out << YAML::EndMap; // transform component;
|
||||
}
|
||||
|
||||
if (entity.has_component<SpriteRendererComponent>())
|
||||
{
|
||||
// TODO(Light): get scene serialization/de-serialization working.
|
||||
// out << YAML::Key << "SpriteRendererComponent";
|
||||
// out << YAML::BeginMap; // sprite renderer component;
|
||||
|
||||
// auto &spriteRendererComponent = entity.get_component<SpriteRendererComponent>();
|
||||
|
||||
// out << YAML::Key << "Texture" << YAML::Value
|
||||
// << spriteRendererComponent.texture->GetFilePath();
|
||||
// out << YAML::Key << "Tint" << YAML::Value << spriteRendererComponent.tint;
|
||||
|
||||
// out << YAML::EndMap; // sprite renderer component
|
||||
}
|
||||
|
||||
// #todo:
|
||||
// if(entity.has_component<NativeScriptComponent>())
|
||||
|
||||
if (entity.has_component<CameraComponent>())
|
||||
{
|
||||
out << YAML::Key << "CameraComponent";
|
||||
out << YAML::BeginMap; // camera component
|
||||
|
||||
auto &cameraComponent = entity.get_component<CameraComponent>();
|
||||
|
||||
out << YAML::Key << "Camera" << YAML::Value;
|
||||
out << YAML::BeginMap; // camera
|
||||
out << YAML::Key << "OrthographicSize" << YAML::Value
|
||||
<< cameraComponent.camera.get_orthographic_size();
|
||||
out << YAML::Key << "OrthographicFarPlane" << YAML::Value
|
||||
<< cameraComponent.camera.get_orthographic_far_plane();
|
||||
out << YAML::Key << "OrthographicNearPlane" << YAML::Value
|
||||
<< cameraComponent.camera.get_orthographic_near_plane();
|
||||
out << YAML::Key << "PerspectiveVerticalFOV" << YAML::Value
|
||||
<< cameraComponent.camera.get_perspective_vertical_fov();
|
||||
out << YAML::Key << "PerspectiveFarPlane" << YAML::Value
|
||||
<< cameraComponent.camera.get_perspective_far_plane();
|
||||
out << YAML::Key << "PerspectiveNearPlane" << YAML::Value
|
||||
<< cameraComponent.camera.get_perspective_near_plane();
|
||||
out << YAML::Key << "ProjectionType" << YAML::Value
|
||||
<< (int)cameraComponent.camera.get_projection_type();
|
||||
out << YAML::Key << "BackgroundColor" << YAML::Value
|
||||
<< cameraComponent.camera.get_background_color();
|
||||
out << YAML::EndMap; // camera
|
||||
|
||||
out << YAML::Key << "IsPrimary" << YAML::Value << cameraComponent.isPrimary;
|
||||
|
||||
out << YAML::EndMap; // camera component
|
||||
}
|
||||
out << YAML::EndMap; // entity
|
||||
}
|
||||
|
||||
} // namespace lt
|
||||
|
|
@ -1,163 +0,0 @@
|
|||
#include <ecs/sparse_set.hpp>
|
||||
#include <ranges>
|
||||
#include <test/expects.hpp>
|
||||
#include <test/test.hpp>
|
||||
|
||||
using lt::test::Case;
|
||||
using lt::test::Suite;
|
||||
|
||||
using lt::test::expect_eq;
|
||||
using lt::test::expect_false;
|
||||
using lt::test::expect_ne;
|
||||
using lt::test::expect_throw;
|
||||
using lt::test::expect_true;
|
||||
|
||||
using Set = lt::ecs::SparseSet<int>;
|
||||
constexpr auto capacity = 100;
|
||||
|
||||
Suite raii = "raii"_suite = [] {
|
||||
Case { "happy path won't throw" } = [] {
|
||||
std::ignore = Set {};
|
||||
std::ignore = Set { Set::max_capacity };
|
||||
};
|
||||
|
||||
Case { "unhappy path throws" } = [] {
|
||||
expect_throw([] { std::ignore = Set { Set::max_capacity + 1 }; });
|
||||
};
|
||||
|
||||
Case { "post construct has correct state" } = [&] {
|
||||
auto set = Set { capacity };
|
||||
expect_eq(set.get_size(), 0);
|
||||
expect_eq(set.get_capacity(), capacity);
|
||||
};
|
||||
};
|
||||
|
||||
Suite element_raii = "element_raii"_suite = [] {
|
||||
Case { "many inserts/removes won't freeze/throw" } = [] {
|
||||
auto set = Set {};
|
||||
for (auto idx : std::views::iota(0, 10'000))
|
||||
{
|
||||
set.insert(idx, {});
|
||||
}
|
||||
|
||||
for (auto idx : std::views::iota(0, 10'000))
|
||||
{
|
||||
set.remove(idx);
|
||||
}
|
||||
};
|
||||
|
||||
Case { "insert returns reference to inserted value" } = [] {
|
||||
auto set = Set {};
|
||||
for (auto idx : std::views::iota(0, 10'000))
|
||||
{
|
||||
const auto val = Set::Dense_T { idx, {} };
|
||||
expect_eq(set.insert(val.first, val.second), val);
|
||||
}
|
||||
};
|
||||
|
||||
Case { "post insert/remove has correct state" } = [] {
|
||||
auto set = Set {};
|
||||
for (auto idx : std::views::iota(0, 10'000))
|
||||
{
|
||||
set.insert(idx, idx * 2);
|
||||
expect_eq(set.get_size(), idx + 1);
|
||||
expect_eq(set.at(idx), Set::Dense_T { idx, idx * 2 });
|
||||
expect_true(set.contains(idx));
|
||||
}
|
||||
|
||||
for (auto idx : std::views::iota(0, 10'000))
|
||||
{
|
||||
expect_eq(set.at(idx), Set::Dense_T { idx, idx * 2 });
|
||||
expect_true(set.contains(idx));
|
||||
}
|
||||
|
||||
for (auto idx : std::views::iota(0, 10'000))
|
||||
{
|
||||
set.remove(idx);
|
||||
|
||||
expect_eq(set.get_size(), 10'000 - (idx + 1));
|
||||
expect_false(set.contains(idx));
|
||||
expect_throw([&] { std::ignore = set.at(idx); });
|
||||
}
|
||||
};
|
||||
|
||||
Case { "removed elements won't be iterated again" } = [] {
|
||||
auto set = Set {};
|
||||
|
||||
for (auto idx : std::views::iota(0, 10'000))
|
||||
{
|
||||
set.insert(idx, idx);
|
||||
}
|
||||
|
||||
set.remove(0);
|
||||
set.remove(32);
|
||||
set.remove(69);
|
||||
set.remove(420);
|
||||
set.remove(9'999);
|
||||
|
||||
for (auto &[identifier, value] : set)
|
||||
{
|
||||
expect_eq(identifier, value);
|
||||
expect_ne(value, 0);
|
||||
expect_ne(value, 32);
|
||||
expect_ne(value, 69);
|
||||
expect_ne(value, 420);
|
||||
expect_ne(value, 9'999);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
Suite getters = "getters"_suite = [] {
|
||||
Case { "get_size returns correct values" } = [] {
|
||||
auto set = Set {};
|
||||
for (auto idx : std::views::iota(0, 10'000))
|
||||
{
|
||||
expect_eq(set.get_size(), idx);
|
||||
set.insert(idx, {});
|
||||
}
|
||||
|
||||
expect_eq(set.get_size(), 10'000);
|
||||
};
|
||||
|
||||
Case { "get_capacity returns correct values" } = [] {
|
||||
auto set = Set { 10'000 };
|
||||
for (auto idx : std::views::iota(0, 10'000))
|
||||
{
|
||||
expect_eq(set.get_capacity(), 10'000); // are we testing std::vector's implementation?
|
||||
set.insert(idx, {});
|
||||
}
|
||||
|
||||
expect_eq(set.get_capacity(), 10'000);
|
||||
|
||||
set.insert(set.get_size(), {});
|
||||
expect_ne(set.get_capacity(), 10'000);
|
||||
};
|
||||
|
||||
Case { "at throws with out of bound access" } = [] {
|
||||
auto set = Set {};
|
||||
|
||||
for (auto idx : std::views::iota(0, 50))
|
||||
{
|
||||
expect_throw([&] {
|
||||
set.insert(idx, {});
|
||||
std::ignore = set.at(50);
|
||||
});
|
||||
}
|
||||
|
||||
set.insert(50, {});
|
||||
std::ignore = set.at(50); // should not throw
|
||||
};
|
||||
};
|
||||
|
||||
Suite clear = "clear"_suite = [] {
|
||||
Case { "post clear has correct state" } = [] {
|
||||
auto set = Set { 0 };
|
||||
for (auto idx : std::views::iota(0, 10'000))
|
||||
{
|
||||
set.insert(idx, {});
|
||||
}
|
||||
|
||||
set.clear();
|
||||
expect_eq(set.get_size(), 0);
|
||||
};
|
||||
};
|
||||
14
modules/ecs/private/uuid.cpp
Normal file
14
modules/ecs/private/uuid.cpp
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#include <ecs/uuid.hpp>
|
||||
|
||||
namespace lt {
|
||||
|
||||
std::mt19937_64 UUID::s_engine = std::mt19937_64(std::random_device()());
|
||||
|
||||
std::uniform_int_distribution<uint64_t>
|
||||
UUID::s_distribution = std::uniform_int_distribution<uint64_t> {};
|
||||
|
||||
UUID::UUID(uint64_t uuid /* = -1 */): m_uuid(uuid == -1 ? s_distribution(s_engine) : uuid)
|
||||
{
|
||||
}
|
||||
|
||||
} // namespace lt
|
||||
6
modules/ecs/public/components.hpp
Normal file
6
modules/ecs/public/components.hpp
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <ecs/components/native_script.hpp>
|
||||
#include <ecs/components/sprite_renderer.hpp>
|
||||
#include <ecs/components/tag.hpp>
|
||||
#include <ecs/components/transform.hpp>
|
||||
28
modules/ecs/public/components/native_script.hpp
Normal file
28
modules/ecs/public/components/native_script.hpp
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#include <ecs/components/scriptable_entity.hpp>
|
||||
|
||||
namespace lt {
|
||||
|
||||
struct NativeScriptComponent
|
||||
{
|
||||
NativeScript *(*CreateInstance)();
|
||||
|
||||
void (*DestroyInstance)(NativeScriptComponent *);
|
||||
|
||||
template<typename t>
|
||||
void bind()
|
||||
{
|
||||
CreateInstance = []() {
|
||||
return static_cast<NativeScript *>(new t());
|
||||
};
|
||||
DestroyInstance = [](NativeScriptComponent *nsc) {
|
||||
delete (t *)(nsc->instance);
|
||||
nsc->instance = nullptr;
|
||||
};
|
||||
}
|
||||
|
||||
NativeScript *instance;
|
||||
};
|
||||
|
||||
} // namespace lt
|
||||
46
modules/ecs/public/components/scriptable_entity.hpp
Normal file
46
modules/ecs/public/components/scriptable_entity.hpp
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
#pragma once
|
||||
|
||||
#include <ecs/entity.hpp>
|
||||
|
||||
namespace lt {
|
||||
|
||||
class NativeScript
|
||||
{
|
||||
public:
|
||||
friend class Scene;
|
||||
|
||||
NativeScript() = default;
|
||||
|
||||
virtual ~NativeScript() = default;
|
||||
|
||||
[[nodiscard]] auto get_uid() const -> unsigned int
|
||||
{
|
||||
return m_unique_identifier;
|
||||
}
|
||||
|
||||
template<typename t>
|
||||
auto GetComponent() -> t &
|
||||
{
|
||||
return m_entity.get_component<t>();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void on_create()
|
||||
{
|
||||
}
|
||||
|
||||
virtual void on_destroy()
|
||||
{
|
||||
}
|
||||
|
||||
virtual void on_update(float ts)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
Entity m_entity;
|
||||
|
||||
unsigned int m_unique_identifier = 0; // :#todo
|
||||
};
|
||||
|
||||
} // namespace lt
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue