Compare commits
176 commits
ci/libx11_
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 311b20bdf2 | |||
| 6a257566bd | |||
| 4534ed11d2 | |||
| d029c0e473 | |||
| 604ee5e6a1 | |||
| 7ee4381bbf | |||
| 8730d31e2f | |||
| f50208653e | |||
| 5422792705 | |||
| 2ddb90faff | |||
| 736c37d2f1 | |||
| 97ca429d38 | |||
| 5a404d5269 | |||
| a9e27d6935 | |||
| 80662983a3 | |||
| c39ce89a9b | |||
| f1a91c9b81 | |||
| ec5483d13f | |||
| 598e1b232d | |||
| 3f5a85197a | |||
| 479a15bfd0 | |||
| 30548ea4db | |||
| 9de1bc7ba7 | |||
| 4b5d380a0e | |||
| 2612a19f3c | |||
| bd8a111607 | |||
| 3066153d6c | |||
| d5dc37d081 | |||
| b393cbb31c | |||
| 847ad7dd74 | |||
| 80a3afbb75 | |||
| a6a1a9e243 | |||
| 2890853ffe | |||
| 49596c31f3 | |||
| a19e095a8e | |||
| b6976c01c4 | |||
| 36d2d81e8a | |||
| cbe391ba32 | |||
| b763b10034 | |||
| 400b49f1d8 | |||
| 94335375ec | |||
| df056d08ed | |||
| 686ccc8f71 | |||
| 23d84c251a | |||
| 07a3dfcb36 | |||
| e70438706c | |||
| 56ef217142 | |||
| 38ec10f4fd | |||
| 00332ee958 | |||
| 9cbebaef06 | |||
| b804360884 | |||
| dc7c6ff0aa | |||
| 0471969615 | |||
| eb9e358d83 | |||
| fc0e63455b | |||
| 487f907ffb | |||
| 20ef8c04d8 | |||
| 77c04d38c9 | |||
| 81811351b8 | |||
| e1360cabce | |||
| 054af3fd8f | |||
| 5fbd9282d1 | |||
| 7132bd3324 | |||
| 3a06d51ee4 | |||
| 61473c2758 | |||
| e7c61b2faf | |||
| 41575df141 | |||
| 237d852ede | |||
| 879d375b7f | |||
| 8defb9a3ec | |||
| 68c49ebdfb | |||
| d506d6a6a7 | |||
| 16f3a80fd3 | |||
| 9ca94dc7d7 | |||
| b05762c95b | |||
| 6af758643e | |||
| ef2f728cd6 | |||
| 1ce8aed8a2 | |||
| c4403b7c90 | |||
| ebf1f54d31 | |||
| 01db551fa9 | |||
| 4ad50122ef | |||
| 0c4b3dd0f9 | |||
| 0fe399a33e | |||
| 85dbe47990 | |||
| 83a872f3f3 | |||
| 4976773218 | |||
| 5148b8836c | |||
| 405c707e23 | |||
| 21a82ff57d | |||
| a46f36aefd | |||
| 723ade84ea | |||
| cce627a350 | |||
| a77abe312b | |||
| 84d0026051 | |||
| 34fa8344ac | |||
| fa1bfaae1e | |||
| d411c9ab2c | |||
| 607e6864b4 | |||
| f268724034 | |||
| 030556c733 | |||
| 26dd49188b | |||
| 131d3472ac | |||
| bf6f2e9981 | |||
| bf8ffc3dc9 | |||
| 963032617e | |||
| 55d68e3b71 | |||
| 6e838afbab | |||
| c2f2abedd7 | |||
| fc0f039395 | |||
| 4e96a871c9 | |||
| 1765dd0bd0 | |||
| f04e3652a5 | |||
| 46a8ebf6da | |||
| eb7b780a20 | |||
| 1ad45dec5e | |||
| c3142f3117 | |||
| f2ac6daf1f | |||
| ac2d5c7c20 | |||
| 65d086c3d4 | |||
| 3e2cf440c9 | |||
| 19df29495c | |||
| 1f1535262f | |||
| cda81f7b3c | |||
| fef6c4bf52 | |||
| 47b8cbc3aa | |||
| febe633520 | |||
| b99167fc4f | |||
| 25e742b8ab | |||
| 8063903344 | |||
| f465f152e3 | |||
| d66ef55cc8 | |||
| e77a42cf7f | |||
| 53dd008df5 | |||
| d924d14ab0 | |||
| b6834310a7 | |||
| a58b0c030f | |||
| dd0f8ebf0a | |||
| ca91c5c1d1 | |||
| 85a1bbfcab | |||
| 5cb331def9 | |||
| 154d6eacf4 | |||
| 1555f2867d | |||
| b1e0e6a9e0 | |||
| fa8a1c53b4 | |||
| e3a20e2c33 | |||
| 4ef2bca643 | |||
| fc01fb6d6e | |||
| 57eb9797ca | |||
| 3800c62827 | |||
| 6d301ec510 | |||
| 6537b456f9 | |||
| 3d3ddd2197 | |||
| 5de1037e93 | |||
| 5c96e2deb9 | |||
| 0700ab282a | |||
| f0f8836042 | |||
| dc0258219d | |||
| 8b98768539 | |||
| 9267214300 | |||
| 04c2e59ada | |||
| 1b6d53f1c1 | |||
| 9badcddeae | |||
| 120b6c24d9 | |||
| d72ee8d9ef | |||
| 03225b3ae6 | |||
| 7266451b45 | |||
| 91d86545dc | |||
| 21e7291189 | |||
| 0c35c13ac1 | |||
| b179149597 | |||
| ca29c61521 | |||
| 2061fc74c2 | |||
| b570653c82 | |||
| f47afbdbcc | |||
| 4be35c76c0 |
294 changed files with 9890 additions and 8538 deletions
5
.clangd
Normal file
5
.clangd
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
CompileFlags:
|
||||||
|
DriverMode: cl
|
||||||
|
Add:
|
||||||
|
- /EHsc
|
||||||
|
- /std:c++latest
|
||||||
33
.drone.yml
33
.drone.yml
|
|
@ -25,13 +25,13 @@ trigger:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: unit tests
|
- name: unit tests
|
||||||
image: amd64_gcc_unit_tests:latest
|
image: ci:latest
|
||||||
pull: if-not-exists
|
pull: if-not-exists
|
||||||
commands:
|
commands:
|
||||||
- ./tools/ci/amd64/gcc/unit_tests.sh
|
- ./tools/ci/amd64/gcc/unit_tests.sh
|
||||||
|
|
||||||
- name: valgrind
|
- name: valgrind
|
||||||
image: amd64_gcc_valgrind:latest
|
image: ci:latest
|
||||||
pull: if-not-exists
|
pull: if-not-exists
|
||||||
commands:
|
commands:
|
||||||
- ./tools/ci/amd64/gcc/valgrind.sh
|
- ./tools/ci/amd64/gcc/valgrind.sh
|
||||||
|
|
@ -46,7 +46,7 @@ trigger:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: code coverage
|
- name: code coverage
|
||||||
image: amd64_clang_coverage:latest
|
image: ci:latest
|
||||||
pull: if-not-exists
|
pull: if-not-exists
|
||||||
environment:
|
environment:
|
||||||
CODECOV_TOKEN:
|
CODECOV_TOKEN:
|
||||||
|
|
@ -55,13 +55,13 @@ steps:
|
||||||
- ./tools/ci/amd64/clang/coverage.sh
|
- ./tools/ci/amd64/clang/coverage.sh
|
||||||
|
|
||||||
- name: leak sanitizer
|
- name: leak sanitizer
|
||||||
image: amd64_clang_lsan:latest
|
image: ci:latest
|
||||||
pull: if-not-exists
|
pull: if-not-exists
|
||||||
commands:
|
commands:
|
||||||
- ./tools/ci/amd64/clang/lsan.sh
|
- ./tools/ci/amd64/clang/lsan.sh
|
||||||
|
|
||||||
- name: memory sanitizer
|
- name: memory sanitizer
|
||||||
image: amd64_clang_msan:latest
|
image: ci:latest
|
||||||
pull: if-not-exists
|
pull: if-not-exists
|
||||||
commands:
|
commands:
|
||||||
- ./tools/ci/amd64/clang/msan.sh
|
- ./tools/ci/amd64/clang/msan.sh
|
||||||
|
|
@ -76,18 +76,36 @@ trigger:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: clang tidy
|
- name: clang tidy
|
||||||
image: clang_tidy:latest
|
image: ci:latest
|
||||||
pull: if-not-exists
|
pull: if-not-exists
|
||||||
privileged: true
|
privileged: true
|
||||||
commands:
|
commands:
|
||||||
- ./tools/ci/static_analysis/clang_tidy.sh
|
- ./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
|
- name: clang format
|
||||||
image: clang_format:latest
|
image: ci:latest
|
||||||
pull: if-not-exists
|
pull: if-not-exists
|
||||||
commands:
|
commands:
|
||||||
- ./tools/ci/static_analysis/clang_format.sh
|
- ./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
|
||||||
|
|
||||||
---
|
---
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
type: docker
|
type: docker
|
||||||
|
|
@ -137,3 +155,4 @@ steps:
|
||||||
|
|
||||||
- rm -rf /light_docs/*
|
- rm -rf /light_docs/*
|
||||||
- mv ./html/* /light_docs/
|
- mv ./html/* /light_docs/
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,42 +1,8 @@
|
||||||
cmake_minimum_required(VERSION 3.14)
|
cmake_minimum_required(VERSION 3.14)
|
||||||
project(Light)
|
project(Light)
|
||||||
set(CMAKE_CXX_STANDARD 23)
|
|
||||||
set(CMAKE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake)
|
|
||||||
|
|
||||||
include(CheckCXXSourceCompiles)
|
include(${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake/functions.cmake)
|
||||||
include(${CMAKE_DIR}/functions.cmake)
|
include(${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake/definitions.cmake)
|
||||||
include(${CMAKE_DIR}/definitions.cmake)
|
include(${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake/options.cmake)
|
||||||
include(${CMAKE_DIR}/dependencies.cmake)
|
|
||||||
|
|
||||||
add_option(ENABLE_UNIT_TESTS "Enables the building of the unit test modules")
|
|
||||||
add_option(ENABLE_FUZZ_TESTS "Enables the building of the fuzz test modules")
|
|
||||||
|
|
||||||
add_option(ENABLE_STATIC_ANALYSIS "Makes clang-tidy checks mandatory for compilation")
|
|
||||||
if (ENABLE_STATIC_ANALYSIS)
|
|
||||||
set(CMAKE_CXX_CLANG_TIDY "clang-tidy;--warnings-as-errors=*;--allow-no-checks")
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
add_option(ENABLE_LLVM_COVERAGE "Enables the code coverage instrumentation for clang")
|
|
||||||
if(ENABLE_LLVM_COVERAGE)
|
|
||||||
if (NOT "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
|
|
||||||
message(FATAL_ERROR "ENABLE_LLVM_COVERAGE only supports the clang compiler")
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
# Check for libc++
|
|
||||||
check_cxx_source_compiles("
|
|
||||||
#include <string>
|
|
||||||
#ifdef _LIBCPP_VERSION
|
|
||||||
int main() { return 0; }
|
|
||||||
#else
|
|
||||||
#error Not using libc++
|
|
||||||
#endif
|
|
||||||
" USING_LIBCXX)
|
|
||||||
if(NOT USING_LIBCXX)
|
|
||||||
message(FATAL_ERROR "ENABLE_LLVM_COVERAGE requires libc++, please compile with -stdlib=libc++")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_compile_options(-fprofile-instr-generate -fcoverage-mapping)
|
|
||||||
add_link_options(-fprofile-instr-generate -fcoverage-mapping)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/modules)
|
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/modules)
|
||||||
|
|
|
||||||
|
|
@ -1,128 +0,0 @@
|
||||||
# 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,6 +1,4 @@
|
||||||
# Light
|
# Light
|
||||||
See docs.light7734.com for a comprehensive project documentation
|
See docs.light7734.com for a comprehensive project documentation
|
||||||
|
|
||||||
<!---FUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUCK
|
###### “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
|
||||||
MEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!!!!!!!!-->
|
|
||||||
|
|
|
||||||
63
conanfile.py
63
conanfile.py
|
|
@ -1,63 +0,0 @@
|
||||||
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_unit_tests": [True, False],
|
|
||||||
"enable_fuzz_tests": [True, False],
|
|
||||||
"enable_llvm_coverage": [True, False],
|
|
||||||
"enable_static_analysis": [True, False],
|
|
||||||
"use_mold": [True, False],
|
|
||||||
"export_compile_commands": [True, False],
|
|
||||||
}
|
|
||||||
|
|
||||||
default_options = {
|
|
||||||
"enable_unit_tests": True,
|
|
||||||
"enable_fuzz_tests": False,
|
|
||||||
"enable_llvm_coverage": False,
|
|
||||||
"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("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_UNIT_TESTS"] = self.options.enable_unit_tests
|
|
||||||
tc.cache_variables["ENABLE_FUZZ_TESTS"] = self.options.enable_fuzz_tests
|
|
||||||
tc.cache_variables["ENABLE_LLVM_COVERAGE"] = self.options.enable_llvm_coverage
|
|
||||||
tc.cache_variables["ENABLE_STATIC_ANALYSIS"] = self.options.enable_static_analysis
|
|
||||||
|
|
||||||
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()
|
|
||||||
Binary file not shown.
|
|
@ -1,10 +0,0 @@
|
||||||
#version 440 core
|
|
||||||
|
|
||||||
in vec4 vso_FragmentColor;
|
|
||||||
|
|
||||||
out vec4 fso_FragmentColor;
|
|
||||||
|
|
||||||
void main()
|
|
||||||
{
|
|
||||||
fso_FragmentColor = vso_FragmentColor;
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
|
@ -1,17 +0,0 @@
|
||||||
#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;
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
|
@ -1,12 +0,0 @@
|
||||||
#version 450 core
|
|
||||||
|
|
||||||
in vec2 vso_TexCoord;
|
|
||||||
|
|
||||||
uniform sampler2D u_Texture;
|
|
||||||
|
|
||||||
out vec4 fso_FragmentColor;
|
|
||||||
|
|
||||||
void main()
|
|
||||||
{
|
|
||||||
fso_FragmentColor = texture(u_Texture, vso_TexCoord);
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
|
@ -1,19 +0,0 @@
|
||||||
#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;
|
|
||||||
}
|
|
||||||
|
|
||||||
Binary file not shown.
|
|
@ -1,14 +0,0 @@
|
||||||
#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;
|
|
||||||
}
|
|
||||||
|
|
||||||
Binary file not shown.
|
|
@ -1,21 +0,0 @@
|
||||||
#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
data/test_assets/dummytext
Normal file
1
data/test_assets/dummytext
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
The quick brown fox jumps over the lazy dog
|
||||||
10
data/test_assets/triangle.frag
Normal file
10
data/test_assets/triangle.frag
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
#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);
|
||||||
|
}
|
||||||
BIN
data/test_assets/triangle.frag.asset
Normal file
BIN
data/test_assets/triangle.frag.asset
Normal file
Binary file not shown.
26
data/test_assets/triangle.vert
Normal file
26
data/test_assets/triangle.vert
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
#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];
|
||||||
|
}
|
||||||
|
|
||||||
BIN
data/test_assets/triangle.vert.asset
Normal file
BIN
data/test_assets/triangle.vert.asset
Normal file
Binary file not shown.
|
|
@ -1,48 +0,0 @@
|
||||||
[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
|
|
||||||
|
|
||||||
|
|
@ -1,24 +1,26 @@
|
||||||
# engine
|
# engine
|
||||||
add_subdirectory(./base)
|
add_subdirectory(./std)
|
||||||
|
add_subdirectory(./bitwise)
|
||||||
|
add_subdirectory(./env)
|
||||||
|
add_subdirectory(./memory)
|
||||||
add_subdirectory(./time)
|
add_subdirectory(./time)
|
||||||
add_subdirectory(./logger)
|
add_subdirectory(./logger)
|
||||||
add_subdirectory(./debug)
|
add_subdirectory(./debug)
|
||||||
add_subdirectory(./math)
|
add_subdirectory(./math)
|
||||||
#
|
#
|
||||||
add_subdirectory(./asset_baker)
|
add_subdirectory(./asset_baker)
|
||||||
add_subdirectory(./asset_parser)
|
add_subdirectory(./assets)
|
||||||
# add_subdirectory(./asset_manager)
|
#
|
||||||
#
|
|
||||||
add_subdirectory(./camera)
|
add_subdirectory(./camera)
|
||||||
add_subdirectory(./input)
|
add_subdirectory(./input)
|
||||||
# add_subdirectory(./ui)
|
# add_subdirectory(./ui)
|
||||||
#
|
#
|
||||||
add_subdirectory(./surface)
|
add_subdirectory(./surface)
|
||||||
# add_subdirectory(./renderer)
|
add_subdirectory(./renderer)
|
||||||
add_subdirectory(./ecs)
|
add_subdirectory(./ecs)
|
||||||
#
|
#
|
||||||
add_subdirectory(./app)
|
add_subdirectory(./app)
|
||||||
|
|
||||||
# apps
|
# apps
|
||||||
add_subdirectory(./mirror)
|
add_subdirectory(./mirror)
|
||||||
add_subdirectory(test)
|
add_subdirectory(test)
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,5 @@
|
||||||
add_library_module(app application.cpp)
|
add_library_module(app application.cpp)
|
||||||
target_link_libraries(app PRIVATE lt_debug)
|
target_link_libraries(
|
||||||
|
app
|
||||||
|
PUBLIC memory
|
||||||
|
PRIVATE lt_debug)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#include <app/application.hpp>
|
#include <app/application.hpp>
|
||||||
#include <app/system.hpp>
|
#include <app/system.hpp>
|
||||||
|
#include <memory/reference.hpp>
|
||||||
|
|
||||||
namespace lt::app {
|
namespace lt::app {
|
||||||
|
|
||||||
|
|
@ -9,10 +10,16 @@ void Application::game_loop()
|
||||||
{
|
{
|
||||||
for (auto &system : m_systems)
|
for (auto &system : m_systems)
|
||||||
{
|
{
|
||||||
if (system->tick())
|
const auto &last_tick = system->get_last_tick_result();
|
||||||
{
|
const auto now = std::chrono::steady_clock::now();
|
||||||
return;
|
|
||||||
}
|
system->tick(
|
||||||
|
TickInfo {
|
||||||
|
.delta_time = now - last_tick.end_time,
|
||||||
|
.budget = std::chrono::milliseconds { 10 },
|
||||||
|
.start_time = now,
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto &system : m_systems_to_be_registered)
|
for (auto &system : m_systems_to_be_registered)
|
||||||
|
|
@ -35,12 +42,12 @@ void Application::game_loop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::register_system(Ref<app::ISystem> system)
|
void Application::register_system(memory::Ref<app::ISystem> system)
|
||||||
{
|
{
|
||||||
m_systems.emplace_back(std::move(system));
|
m_systems.emplace_back(std::move(system));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::unregister_system(Ref<app::ISystem> system)
|
void Application::unregister_system(memory::Ref<app::ISystem> system)
|
||||||
{
|
{
|
||||||
m_systems_to_be_unregistered.emplace_back(std::move(system));
|
m_systems_to_be_unregistered.emplace_back(std::move(system));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory/reference.hpp>
|
||||||
|
#include <memory/scope.hpp>
|
||||||
|
|
||||||
namespace lt::app {
|
namespace lt::app {
|
||||||
|
|
||||||
class ISystem;
|
class ISystem;
|
||||||
|
|
||||||
extern Scope<class Application> create_application();
|
extern memory::Scope<class Application> create_application();
|
||||||
|
|
||||||
/** The main application class.
|
/** The main application class.
|
||||||
* Think of this like an aggregate of systems, you register systems through this interface.
|
* Think of this like an aggregate of systems, you register systems through this interface.
|
||||||
|
|
@ -25,19 +28,19 @@ public:
|
||||||
|
|
||||||
void game_loop();
|
void game_loop();
|
||||||
|
|
||||||
void register_system(Ref<app::ISystem> system);
|
void register_system(memory::Ref<app::ISystem> system);
|
||||||
|
|
||||||
void unregister_system(Ref<app::ISystem> system);
|
void unregister_system(memory::Ref<app::ISystem> system);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Application() = default;
|
Application() = default;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<Ref<app::ISystem>> m_systems;
|
std::vector<memory::Ref<app::ISystem>> m_systems;
|
||||||
|
|
||||||
std::vector<Ref<app::ISystem>> m_systems_to_be_unregistered;
|
std::vector<memory::Ref<app::ISystem>> m_systems_to_be_unregistered;
|
||||||
|
|
||||||
std::vector<Ref<app::ISystem>> m_systems_to_be_registered;
|
std::vector<memory::Ref<app::ISystem>> m_systems_to_be_registered;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <app/application.hpp>
|
#include <app/application.hpp>
|
||||||
|
#include <logger/logger.hpp>
|
||||||
|
#include <memory/scope.hpp>
|
||||||
|
|
||||||
auto main(int argc, char *argv[]) -> int32_t
|
auto main(int argc, char *argv[]) -> int32_t
|
||||||
try
|
try
|
||||||
|
|
@ -8,8 +10,7 @@ try
|
||||||
std::ignore = argc;
|
std::ignore = argc;
|
||||||
std::ignore = argv;
|
std::ignore = argv;
|
||||||
|
|
||||||
auto application = lt::Scope<lt::app::Application> {};
|
auto application = lt::memory::Scope<lt::app::Application> {};
|
||||||
|
|
||||||
application = lt::app::create_application();
|
application = lt::app::create_application();
|
||||||
if (!application)
|
if (!application)
|
||||||
{
|
{
|
||||||
|
|
@ -21,7 +22,7 @@ try
|
||||||
}
|
}
|
||||||
catch (const std::exception &exp)
|
catch (const std::exception &exp)
|
||||||
{
|
{
|
||||||
log_crt("Terminating due to uncaught exception:");
|
lt::log::critical("Terminating due to uncaught exception:");
|
||||||
log_crt("\texception.what(): {}", exp.what());
|
lt::log::critical("\texception.what(): {}", exp.what());
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,90 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <logger/logger.hpp>
|
||||||
|
|
||||||
namespace lt::app {
|
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
|
class ISystem
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
@ -21,7 +104,9 @@ public:
|
||||||
|
|
||||||
virtual void on_unregister() = 0;
|
virtual void on_unregister() = 0;
|
||||||
|
|
||||||
virtual auto tick() -> bool = 0;
|
virtual void tick(TickInfo tick) = 0;
|
||||||
|
|
||||||
|
[[nodiscard]] virtual auto get_last_tick_result() const -> const TickResult & = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace lt::app
|
} // namespace lt::app
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,6 @@
|
||||||
add_executable_module(
|
add_library_module(libasset_baker bakers.cpp)
|
||||||
asset_baker entrypoint/baker.cpp
|
target_link_libraries(libasset_baker PUBLIC assets logger lt_debug tbb)
|
||||||
)
|
add_test_module(libasset_baker bakers.test.cpp)
|
||||||
|
|
||||||
target_link_libraries(
|
add_executable_module(asset_baker entrypoint/baker.cpp)
|
||||||
asset_baker
|
target_link_libraries(asset_baker PRIVATE libasset_baker)
|
||||||
PRIVATE asset_parser
|
|
||||||
PRIVATE stb::stb
|
|
||||||
PRIVATE logger
|
|
||||||
)
|
|
||||||
|
|
|
||||||
2
modules/asset_baker/private/bakers.test.cpp
Normal file
2
modules/asset_baker/private/bakers.test.cpp
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
#include <asset_baker/bakers.hpp>
|
||||||
|
#include <test/test.hpp>
|
||||||
|
|
@ -1,69 +1,7 @@
|
||||||
#include <asset_baker/bakers.hpp>
|
#include <asset_baker/bakers.hpp>
|
||||||
#include <asset_parser/assets/text.hpp>
|
#include <assets/shader.hpp>
|
||||||
#include <asset_parser/assets/texture.hpp>
|
|
||||||
#include <asset_parser/parser.hpp>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <logger/logger.hpp>
|
#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
|
auto main(int argc, char *argv[]) -> int32_t
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -81,20 +19,24 @@ try
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto &in_path = directory_iterator.path();
|
const auto &in_path = directory_iterator.path();
|
||||||
|
const auto out_path = std::format("{}.asset", in_path.c_str());
|
||||||
|
|
||||||
auto out_path = in_path;
|
if (in_path.extension() == ".vert")
|
||||||
out_path.replace_extension(".asset");
|
{
|
||||||
|
bake_shader(in_path, out_path, lt::assets::ShaderAsset::Type::vertex);
|
||||||
try_packing_texture(in_path, out_path);
|
}
|
||||||
try_packing_text(in_path, out_path);
|
else if (in_path.extension() == ".frag")
|
||||||
|
{
|
||||||
|
bake_shader(in_path, out_path, lt::assets::ShaderAsset::Type::fragment);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
catch (const std::exception &exp)
|
catch (const std::exception &exp)
|
||||||
{
|
{
|
||||||
log_crt("Terminating due to uncaught exception:");
|
lt::log::critical("Terminating due to uncaught exception:");
|
||||||
log_crt("\texception.what: {}:", exp.what());
|
lt::log::critical("\texception.what: {}:", exp.what());
|
||||||
|
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,184 +1,65 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <asset_parser/assets/text.hpp>
|
#include <assets/shader.hpp>
|
||||||
#include <asset_parser/assets/texture.hpp>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <logger/logger.hpp>
|
#include <logger/logger.hpp>
|
||||||
#include <string_view>
|
|
||||||
#include <unordered_set>
|
|
||||||
|
|
||||||
#define STB_IMAGE_IMPLEMENTATION
|
inline void bake_shader(
|
||||||
#include <stb_image.h>
|
const std::filesystem::path &in_path,
|
||||||
|
const std::filesystem::path &out_path,
|
||||||
namespace lt {
|
lt::assets::ShaderAsset::Type type
|
||||||
|
)
|
||||||
class Loader
|
|
||||||
{
|
{
|
||||||
public:
|
using lt::assets::ShaderAsset;
|
||||||
[[nodiscard]] virtual auto get_name() const -> std::string_view = 0;
|
using enum lt::assets::ShaderAsset::Type;
|
||||||
|
|
||||||
Loader() = default;
|
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(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(const Loader &) = delete;
|
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
|
||||||
|
);
|
||||||
|
|
||||||
auto operator=(Loader &&) -> Loader & = default;
|
stream.seekg(0, std::ios::end);
|
||||||
|
const auto size = stream.tellg();
|
||||||
|
|
||||||
auto operator=(const Loader &) -> Loader & = delete;
|
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);
|
||||||
|
|
||||||
virtual ~Loader() = default;
|
ShaderAsset::pack(
|
||||||
|
out_path,
|
||||||
private:
|
lt::assets::AssetMetadata {
|
||||||
};
|
.version = lt::assets::current_version,
|
||||||
|
.type = ShaderAsset::asset_type_identifier,
|
||||||
class TextureLoader: public Loader
|
},
|
||||||
{
|
ShaderAsset::Metadata {
|
||||||
public:
|
.type = type,
|
||||||
TextureLoader() = default;
|
},
|
||||||
|
std::move(bytes)
|
||||||
[[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
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
add_library_module(asset_manager
|
|
||||||
asset_manager.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
target_link_libraries(
|
|
||||||
asset_manager
|
|
||||||
PUBLIC asset_parser
|
|
||||||
PRIVATE logger
|
|
||||||
)
|
|
||||||
|
|
@ -1,92 +0,0 @@
|
||||||
#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
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
#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
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
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,5 +1,4 @@
|
||||||
#include <asset_parser/assets/text.hpp>
|
#include <asset_parser/assets/text.hpp>
|
||||||
#include <lz4.h>
|
|
||||||
|
|
||||||
namespace Assets {
|
namespace Assets {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,165 +0,0 @@
|
||||||
#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
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
#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
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
#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
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
#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
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
namespace Assets {
|
|
||||||
|
|
||||||
enum class CompressionType : uint32_t // NOLINT(performance-enum-size)
|
|
||||||
{
|
|
||||||
None,
|
|
||||||
LZ4,
|
|
||||||
LZ4HC,
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
#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
|
|
||||||
5
modules/assets/CMakeLists.txt
Normal file
5
modules/assets/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
add_library_module(assets shader.cpp)
|
||||||
|
|
||||||
|
target_link_libraries(assets PUBLIC logger lt_debug)
|
||||||
|
|
||||||
|
add_test_module(assets shader.test.cpp)
|
||||||
148
modules/assets/private/shader.cpp
Normal file
148
modules/assets/private/shader.cpp
Normal file
|
|
@ -0,0 +1,148 @@
|
||||||
|
#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
|
||||||
94
modules/assets/private/shader.test.cpp
Normal file
94
modules/assets/private/shader.test.cpp
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
#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));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
3
modules/assets/public/compressors/lz4.hpp
Normal file
3
modules/assets/public/compressors/lz4.hpp
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// TO BE DOOO
|
||||||
42
modules/assets/public/metadata.hpp
Normal file
42
modules/assets/public/metadata.hpp
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
#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
|
||||||
74
modules/assets/public/shader.hpp
Normal file
74
modules/assets/public/shader.hpp
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
#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
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
add_library_module(base)
|
|
||||||
target_precompile_headers(base INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/private/pch.hpp)
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
#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>
|
|
||||||
|
|
@ -1,91 +0,0 @@
|
||||||
#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
modules/bitwise/CMakeLists.txt
Normal file
1
modules/bitwise/CMakeLists.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
add_library_module(bitwise)
|
||||||
13
modules/bitwise/public/operations.hpp
Normal file
13
modules/bitwise/public/operations.hpp
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
#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 camera.cpp scene.cpp)
|
add_library_module(camera)
|
||||||
|
|
||||||
target_link_libraries(camera PUBLIC math)
|
target_link_libraries(camera INTERFACE math)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
#include <camera/camera.hpp>
|
|
||||||
|
|
||||||
namespace lt {
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
#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
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
#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
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
#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
|
|
||||||
22
modules/camera/public/components.hpp
Normal file
22
modules/camera/public/components.hpp
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
#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
|
||||||
|
|
@ -1,100 +0,0 @@
|
||||||
#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,3 +1,4 @@
|
||||||
add_library_module(lt_debug instrumentor.cpp)
|
add_library_module(lt_debug instrumentor.cpp)
|
||||||
target_link_libraries(lt_debug PUBLIC logger)
|
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)
|
if (m_current_session_count == 0u)
|
||||||
{
|
{
|
||||||
log_wrn("0 profiling for the ended session");
|
log::warn("0 profiling for the ended session");
|
||||||
}
|
}
|
||||||
|
|
||||||
m_current_session_count = 0u;
|
m_current_session_count = 0u;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <format>
|
#include <format>
|
||||||
#include <logger/logger.hpp>
|
|
||||||
#include <source_location>
|
#include <source_location>
|
||||||
|
|
||||||
namespace lt {
|
namespace lt {
|
||||||
|
|
@ -10,7 +9,7 @@ template<typename Expression_T, typename... Args_T>
|
||||||
struct ensure
|
struct ensure
|
||||||
{
|
{
|
||||||
ensure(
|
ensure(
|
||||||
Expression_T expression,
|
const Expression_T &expression,
|
||||||
std::format_string<Args_T...> fmt,
|
std::format_string<Args_T...> fmt,
|
||||||
Args_T &&...args,
|
Args_T &&...args,
|
||||||
const std::source_location &location = std::source_location::current()
|
const std::source_location &location = std::source_location::current()
|
||||||
|
|
@ -28,7 +27,6 @@ struct ensure
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
template<typename Expression_T, typename... Args_T>
|
template<typename Expression_T, typename... Args_T>
|
||||||
ensure(Expression_T, std::format_string<Args_T...>, Args_T &&...)
|
ensure(Expression_T, std::format_string<Args_T...>, Args_T &&...)
|
||||||
-> ensure<Expression_T, Args_T...>;
|
-> ensure<Expression_T, Args_T...>;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
add_library_module(ecs entity.cpp scene.cpp uuid.cpp )
|
add_library_module(ecs sparse_set.cpp)
|
||||||
target_link_libraries(ecs
|
target_link_libraries(ecs PUBLIC logger lt_debug memory)
|
||||||
PUBLIC logger lt_debug EnTT::EnTT camera math
|
|
||||||
)
|
add_test_module(ecs sparse_set.test.cpp registry.test.cpp)
|
||||||
|
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
#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
|
|
||||||
351
modules/ecs/private/registry.test.cpp
Normal file
351
modules/ecs/private/registry.test.cpp
Normal file
|
|
@ -0,0 +1,351 @@
|
||||||
|
#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);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
#include <ecs/components.hpp>
|
|
||||||
#include <ecs/entity.hpp>
|
|
||||||
#include <ecs/scene.hpp>
|
|
||||||
|
|
||||||
namespace lt {
|
|
||||||
|
|
||||||
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
|
|
||||||
{
|
|
||||||
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
|
|
||||||
|
|
@ -1,325 +0,0 @@
|
||||||
#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
|
|
||||||
163
modules/ecs/private/sparse_set.test.cpp
Normal file
163
modules/ecs/private/sparse_set.test.cpp
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
#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);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
#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
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <ecs/components/native_script.hpp>
|
|
||||||
#include <ecs/components/sprite_renderer.hpp>
|
|
||||||
#include <ecs/components/tag.hpp>
|
|
||||||
#include <ecs/components/transform.hpp>
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
#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
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
#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
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <math/vec4.hpp>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
namespace lt {
|
|
||||||
|
|
||||||
class Texture;
|
|
||||||
|
|
||||||
struct SpriteRendererComponent
|
|
||||||
{
|
|
||||||
SpriteRendererComponent() = default;
|
|
||||||
|
|
||||||
SpriteRendererComponent(const SpriteRendererComponent &) = default;
|
|
||||||
|
|
||||||
SpriteRendererComponent(
|
|
||||||
Ref<Texture> _texture,
|
|
||||||
const math::vec4 &_tint = math::vec4 { 1.0f, 1.0f, 1.0f, 1.0f }
|
|
||||||
)
|
|
||||||
: texture(std::move(std::move(_texture)))
|
|
||||||
, tint(_tint)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
operator Ref<Texture>() const
|
|
||||||
{
|
|
||||||
return texture;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ref<Texture> texture;
|
|
||||||
|
|
||||||
math::vec4 tint {};
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace lt
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
namespace lt {
|
|
||||||
|
|
||||||
struct TagComponent
|
|
||||||
{
|
|
||||||
TagComponent() = default;
|
|
||||||
|
|
||||||
TagComponent(const TagComponent &) = default;
|
|
||||||
|
|
||||||
TagComponent(std::string _tag): tag(std::move(_tag))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
operator std::string() const
|
|
||||||
{
|
|
||||||
return tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
operator const std::string &() const
|
|
||||||
{
|
|
||||||
return tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string tag = "Unnamed";
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace lt
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <math/mat4.hpp>
|
|
||||||
#include <math/vec3.hpp>
|
|
||||||
|
|
||||||
namespace lt {
|
|
||||||
|
|
||||||
struct TransformComponent
|
|
||||||
{
|
|
||||||
TransformComponent(const TransformComponent &) = default;
|
|
||||||
|
|
||||||
TransformComponent(
|
|
||||||
const math::vec3 &_translation = math::vec3(0.0f, 0.0f, 0.0f),
|
|
||||||
const math::vec3 &_scale = math::vec3(1.0f, 1.0f, 1.0f),
|
|
||||||
const math::vec3 &_rotation = math::vec3(0.0f, 0.0f, 0.0f)
|
|
||||||
)
|
|
||||||
|
|
||||||
: translation(_translation)
|
|
||||||
, scale(_scale)
|
|
||||||
, rotation(_rotation)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] auto get_transform() const -> math::mat4
|
|
||||||
{
|
|
||||||
return math::translate(translation)
|
|
||||||
* math::rotate(rotation.z, math::vec3 { 0.0f, 0.0f, 1.0f }) //
|
|
||||||
* math::scale(scale);
|
|
||||||
}
|
|
||||||
|
|
||||||
operator const math::mat4() const
|
|
||||||
{
|
|
||||||
return get_transform();
|
|
||||||
}
|
|
||||||
|
|
||||||
math::vec3 translation;
|
|
||||||
|
|
||||||
math::vec3 scale;
|
|
||||||
|
|
||||||
math::vec3 rotation;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace lt
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <ecs/uuid.hpp>
|
|
||||||
|
|
||||||
namespace lt {
|
|
||||||
|
|
||||||
struct UUIDComponent
|
|
||||||
{
|
|
||||||
UUIDComponent(UUID _uuid): uuid(_uuid)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
UUIDComponent(const UUIDComponent &) = default;
|
|
||||||
|
|
||||||
UUID uuid;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace lt
|
|
||||||
|
|
@ -1,59 +1,54 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <ecs/components/uuid.hpp>
|
#include <ecs/registry.hpp>
|
||||||
#include <ecs/scene.hpp>
|
#include <memory/reference.hpp>
|
||||||
#include <entt/entt.hpp>
|
|
||||||
|
|
||||||
namespace lt {
|
namespace lt::ecs {
|
||||||
|
|
||||||
|
/** High-level entity convenience wrapper */
|
||||||
class Entity
|
class Entity
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Entity(entt::entity handle = entt::null, Scene *scene = nullptr);
|
Entity(memory::Ref<Registry> registry, EntityId identifier)
|
||||||
|
: m_registry(std::move(registry))
|
||||||
template<typename t, typename... Args>
|
, m_identifier(identifier)
|
||||||
auto add_component(Args &&...args) -> t &
|
|
||||||
{
|
{
|
||||||
return m_scene->m_registry.emplace<t>(m_handle, std::forward<Args>(args)...);
|
ensure(m_registry, "Failed to create Entity ({}): null registry", m_identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename t>
|
template<typename Component_T>
|
||||||
auto get_component() -> t &
|
auto add(Component_T component) -> Component_T &
|
||||||
{
|
{
|
||||||
return m_scene->m_registry.get<t>(m_handle);
|
return m_registry->add(m_identifier, component);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename t>
|
template<typename Component_T>
|
||||||
auto has_component() -> bool
|
auto get() -> Component_T &
|
||||||
{
|
{
|
||||||
return m_scene->m_registry.any_of<t>(m_handle);
|
return m_registry->get<Component_T>(m_identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename t>
|
template<typename Component_T>
|
||||||
void remove_component()
|
auto get() const -> const Component_T &
|
||||||
{
|
{
|
||||||
m_scene->m_registry.remove<t>(m_handle);
|
return m_registry->get<Component_T>(m_identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto get_uuid() -> uint64_t
|
auto get_registry() -> memory::Ref<Registry>
|
||||||
{
|
{
|
||||||
return get_component<UUIDComponent>().uuid;
|
return m_registry;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] auto is_valid() const -> bool
|
[[nodiscard]] auto id() const -> EntityId
|
||||||
{
|
{
|
||||||
return m_handle != entt::null && m_scene != nullptr;
|
return m_identifier;
|
||||||
}
|
|
||||||
|
|
||||||
operator uint32_t()
|
|
||||||
{
|
|
||||||
return (uint32_t)m_handle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
entt::entity m_handle;
|
memory::Ref<Registry> m_registry;
|
||||||
|
|
||||||
Scene *m_scene;
|
EntityId m_identifier;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace lt
|
|
||||||
|
} // namespace lt::ecs
|
||||||
|
|
|
||||||
260
modules/ecs/public/registry.hpp
Normal file
260
modules/ecs/public/registry.hpp
Normal file
|
|
@ -0,0 +1,260 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ecs/sparse_set.hpp>
|
||||||
|
#include <memory/scope.hpp>
|
||||||
|
|
||||||
|
namespace lt::ecs {
|
||||||
|
|
||||||
|
using EntityId = uint32_t;
|
||||||
|
|
||||||
|
constexpr auto null_entity = std::numeric_limits<EntityId>::max();
|
||||||
|
|
||||||
|
/** A registry of components, the heart of an ECS architecture.
|
||||||
|
*
|
||||||
|
* @todo(Light): Implement grouping
|
||||||
|
* @todo(Light): Implement identifier recycling
|
||||||
|
* @todo(Light): Optimize views/each
|
||||||
|
* @todo(Light): Support >2 component views
|
||||||
|
* @todo(Light): Handle more edge cases or specify the undefined behaviors
|
||||||
|
*
|
||||||
|
* @ref https://skypjack.github.io/
|
||||||
|
* @ref https://github.com/alecthomas/entityx
|
||||||
|
* @ref https://github.com/skypjack/entt
|
||||||
|
* @ref https://github.com/SanderMertens/flecs
|
||||||
|
*/
|
||||||
|
class Registry
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using UnderlyingSparseSet_T = TypeErasedSparseSet<EntityId>;
|
||||||
|
|
||||||
|
using Callback_T = std::function<void(Registry &, EntityId)>;
|
||||||
|
|
||||||
|
template<typename Component_T>
|
||||||
|
void connect_on_construct(Callback_T callback)
|
||||||
|
{
|
||||||
|
m_on_construct_hooks[get_type_id<Component_T>()] = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Component_T>
|
||||||
|
void connect_on_destruct(Callback_T callback)
|
||||||
|
{
|
||||||
|
m_on_destruct_hooks[get_type_id<Component_T>()] = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Component_T>
|
||||||
|
void disconnect_on_construct()
|
||||||
|
{
|
||||||
|
m_on_construct_hooks.erase(get_type_id<Component_T>());
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Component_T>
|
||||||
|
void disconnect_on_destruct()
|
||||||
|
{
|
||||||
|
m_on_destruct_hooks.erase(get_type_id<Component_T>());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto create_entity() -> EntityId
|
||||||
|
{
|
||||||
|
++m_entity_count;
|
||||||
|
return m_current++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroy_entity(EntityId entity)
|
||||||
|
{
|
||||||
|
for (const auto &[key, set] : m_sparsed_sets)
|
||||||
|
{
|
||||||
|
set->remove(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
--m_entity_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Component_T>
|
||||||
|
auto get(EntityId entity) const -> const Component_T &
|
||||||
|
{
|
||||||
|
auto &derived_set = get_derived_set<Component_T>();
|
||||||
|
return derived_set.at(entity).second;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Component_T>
|
||||||
|
auto get(EntityId entity) -> Component_T &
|
||||||
|
{
|
||||||
|
auto &derived_set = get_derived_set<Component_T>();
|
||||||
|
return derived_set.at(entity).second;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Component_T>
|
||||||
|
auto add(EntityId entity, Component_T component) -> Component_T &
|
||||||
|
{
|
||||||
|
auto &derived_set = get_derived_set<Component_T>();
|
||||||
|
auto &added_component = derived_set.insert(entity, std::move(component)).second;
|
||||||
|
|
||||||
|
if (m_on_construct_hooks.contains(get_type_id<Component_T>()))
|
||||||
|
{
|
||||||
|
m_on_construct_hooks[get_type_id<Component_T>()](*this, entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
return added_component;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Component_T>
|
||||||
|
void remove(EntityId entity)
|
||||||
|
{
|
||||||
|
if (m_on_destruct_hooks.contains(get_type_id<Component_T>()))
|
||||||
|
{
|
||||||
|
m_on_destruct_hooks[get_type_id<Component_T>()](*this, entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto &derived_set = get_derived_set<Component_T>();
|
||||||
|
derived_set.remove(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Component_T>
|
||||||
|
auto view() -> SparseSet<Component_T, EntityId> &
|
||||||
|
{
|
||||||
|
return get_derived_set<Component_T>();
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename ComponentA_T, typename ComponentB_T>
|
||||||
|
requires(!std::is_same_v<ComponentA_T, ComponentB_T>)
|
||||||
|
auto view() -> std::vector<std::tuple<EntityId, ComponentA_T &, ComponentB_T &>>
|
||||||
|
{
|
||||||
|
auto &set_a = get_derived_set<ComponentA_T>();
|
||||||
|
auto &set_b = get_derived_set<ComponentB_T>();
|
||||||
|
auto view = std::vector<std::tuple<EntityId, ComponentA_T &, ComponentB_T &>> {};
|
||||||
|
|
||||||
|
/* iterate over the "smaller" component-set, and check if its entities have the other
|
||||||
|
* component */
|
||||||
|
if (set_a.get_size() > set_b.get_size())
|
||||||
|
{
|
||||||
|
for (auto &[entity, component_b] : set_b)
|
||||||
|
{
|
||||||
|
if (set_a.contains(entity))
|
||||||
|
{
|
||||||
|
view.emplace_back(std::tie(entity, set_a.at(entity).second, component_b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (auto &[entity, component_a] : set_a)
|
||||||
|
{
|
||||||
|
if (set_b.contains(entity))
|
||||||
|
{
|
||||||
|
view.emplace_back(std::tie(entity, component_a, set_b.at(entity).second));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return view;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Component_T>
|
||||||
|
void each(std::function<void(EntityId, Component_T &)> functor)
|
||||||
|
{
|
||||||
|
for (auto &[entity, component] : get_derived_set<Component_T>())
|
||||||
|
{
|
||||||
|
functor(entity, component);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename ComponentA_T, typename ComponentB_T>
|
||||||
|
requires(!std::is_same_v<ComponentA_T, ComponentB_T>)
|
||||||
|
void each(std::function<void(EntityId, ComponentA_T &, ComponentB_T &)> functor)
|
||||||
|
{
|
||||||
|
auto &set_a = get_derived_set<ComponentA_T>();
|
||||||
|
auto &set_b = get_derived_set<ComponentB_T>();
|
||||||
|
|
||||||
|
/* iterate over the "smaller" component-set, and check if its entities have the other
|
||||||
|
* component */
|
||||||
|
if (set_a.get_size() > set_b.get_size())
|
||||||
|
{
|
||||||
|
for (auto &[entity, component_b] : set_b)
|
||||||
|
{
|
||||||
|
if (set_a.contains(entity))
|
||||||
|
{
|
||||||
|
functor(entity, set_a.at(entity).second, component_b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &[entity, component_a] : set_a)
|
||||||
|
{
|
||||||
|
if (set_b.contains(entity))
|
||||||
|
{
|
||||||
|
functor(entity, component_a, set_b.at(entity).second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] auto get_entity_count() const -> size_t
|
||||||
|
{
|
||||||
|
return static_cast<size_t>(m_entity_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
using TypeId = size_t;
|
||||||
|
|
||||||
|
static consteval auto hash_cstr(const char *str) -> TypeId
|
||||||
|
{
|
||||||
|
constexpr auto fnv_offset_basis = size_t { 14695981039346656037ull };
|
||||||
|
constexpr auto fnv_prime = size_t { 1099511628211ull };
|
||||||
|
|
||||||
|
auto hash = fnv_offset_basis;
|
||||||
|
|
||||||
|
for (const auto &ch : std::string_view { str })
|
||||||
|
{
|
||||||
|
hash *= fnv_prime;
|
||||||
|
hash ^= static_cast<uint8_t>(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
static consteval auto get_type_id() -> TypeId
|
||||||
|
{
|
||||||
|
#if defined _MSC_VER
|
||||||
|
#define GENERATOR_PRETTY_FUNCTION __FUNCSIG__
|
||||||
|
#elif defined __clang__ || (defined __GNUC__)
|
||||||
|
#define GENERATOR_PRETTY_FUNCTION __PRETTY_FUNCTION__
|
||||||
|
#else
|
||||||
|
#error "Compiler not supported"
|
||||||
|
#endif
|
||||||
|
constexpr auto value = hash_cstr(GENERATOR_PRETTY_FUNCTION);
|
||||||
|
|
||||||
|
#undef GENERATOR_PRETTY_FUNCTION
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
auto get_derived_set() -> SparseSet<T, EntityId> &
|
||||||
|
{
|
||||||
|
constexpr auto type_id = get_type_id<T>();
|
||||||
|
if (!m_sparsed_sets.contains(type_id))
|
||||||
|
{
|
||||||
|
m_sparsed_sets[type_id] = memory::create_scope<SparseSet<T, EntityId>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto *base_set = m_sparsed_sets[type_id].get();
|
||||||
|
auto *derived_set = dynamic_cast<SparseSet<T, EntityId> *>(base_set);
|
||||||
|
ensure(derived_set, "Failed to downcast to derived set");
|
||||||
|
|
||||||
|
return *derived_set;
|
||||||
|
}
|
||||||
|
|
||||||
|
EntityId m_current;
|
||||||
|
|
||||||
|
TypeId m_entity_count;
|
||||||
|
|
||||||
|
std::flat_map<TypeId, memory::Scope<UnderlyingSparseSet_T>> m_sparsed_sets;
|
||||||
|
|
||||||
|
std::flat_map<TypeId, Callback_T> m_on_construct_hooks;
|
||||||
|
|
||||||
|
std::flat_map<TypeId, Callback_T> m_on_destruct_hooks;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace lt::ecs
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <ecs/components/transform.hpp>
|
|
||||||
#include <ecs/uuid.hpp>
|
|
||||||
#include <entt/entt.hpp>
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
namespace lt {
|
|
||||||
|
|
||||||
class Entity;
|
|
||||||
class Framebuffer;
|
|
||||||
|
|
||||||
class Scene
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
template<typename... T>
|
|
||||||
auto group()
|
|
||||||
{
|
|
||||||
return m_registry.group(entt::get<T...>);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
auto view()
|
|
||||||
{
|
|
||||||
return m_registry.view<T>();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto create_entity(
|
|
||||||
const std::string &name,
|
|
||||||
const TransformComponent &transform = TransformComponent()
|
|
||||||
) -> Entity;
|
|
||||||
|
|
||||||
auto get_entity_by_tag(const std::string &tag) -> Entity;
|
|
||||||
|
|
||||||
auto get_entt_registry() -> entt::registry &
|
|
||||||
{
|
|
||||||
return m_registry;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private:
|
|
||||||
friend class Entity;
|
|
||||||
|
|
||||||
friend class SceneSerializer;
|
|
||||||
|
|
||||||
friend class SceneHierarchyPanel;
|
|
||||||
|
|
||||||
entt::registry m_registry;
|
|
||||||
|
|
||||||
auto create_entity_with_uuid(
|
|
||||||
const std::string &name,
|
|
||||||
UUID uuid,
|
|
||||||
const TransformComponent &transform = TransformComponent()
|
|
||||||
) -> Entity;
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace ecs {
|
|
||||||
|
|
||||||
using Registry = Scene;
|
|
||||||
|
|
||||||
using Entity = ::lt::Entity;
|
|
||||||
|
|
||||||
} // namespace ecs
|
|
||||||
|
|
||||||
} // namespace lt
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <ecs/entity.hpp>
|
|
||||||
#include <ecs/scene.hpp>
|
|
||||||
|
|
||||||
namespace YAML {
|
|
||||||
|
|
||||||
class Emitter;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace lt {
|
|
||||||
|
|
||||||
class SceneSerializer
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
SceneSerializer(const Ref<Scene> &scene);
|
|
||||||
|
|
||||||
void serialize(const std::string &filePath);
|
|
||||||
|
|
||||||
auto deserialize(const std::string &file_path) -> bool;
|
|
||||||
|
|
||||||
void serialize_binary(const std::string &file_path);
|
|
||||||
|
|
||||||
auto deserialize_binary(const std::string &file_path) -> bool;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Ref<Scene> m_scene;
|
|
||||||
|
|
||||||
void serialize_entity(YAML::Emitter &out, Entity entity);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace lt
|
|
||||||
173
modules/ecs/public/sparse_set.hpp
Normal file
173
modules/ecs/public/sparse_set.hpp
Normal file
|
|
@ -0,0 +1,173 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace lt::ecs {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @ref https://programmingpraxis.com/2012/03/09/sparse-sets/
|
||||||
|
*/
|
||||||
|
template<typename Identifier_T = uint32_t>
|
||||||
|
class TypeErasedSparseSet
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TypeErasedSparseSet() = default;
|
||||||
|
|
||||||
|
TypeErasedSparseSet(TypeErasedSparseSet &&) = default;
|
||||||
|
|
||||||
|
TypeErasedSparseSet(const TypeErasedSparseSet &) = default;
|
||||||
|
|
||||||
|
auto operator=(TypeErasedSparseSet &&) -> TypeErasedSparseSet & = default;
|
||||||
|
|
||||||
|
auto operator=(const TypeErasedSparseSet &) -> TypeErasedSparseSet & = default;
|
||||||
|
|
||||||
|
virtual ~TypeErasedSparseSet() = default;
|
||||||
|
|
||||||
|
virtual void remove(Identifier_T identifier) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Value_T, typename Identifier_T = uint32_t>
|
||||||
|
class SparseSet: public TypeErasedSparseSet<Identifier_T>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using Dense_T = std::pair<Identifier_T, Value_T>;
|
||||||
|
|
||||||
|
static constexpr auto max_capacity = size_t { 1'000'000 };
|
||||||
|
|
||||||
|
static constexpr auto null_identifier = std::numeric_limits<Identifier_T>().max();
|
||||||
|
|
||||||
|
explicit SparseSet(size_t initial_capacity = 1)
|
||||||
|
{
|
||||||
|
ensure(
|
||||||
|
initial_capacity <= max_capacity,
|
||||||
|
"Failed to create SparseSet: capacity too large ({} > {})",
|
||||||
|
initial_capacity,
|
||||||
|
max_capacity
|
||||||
|
);
|
||||||
|
|
||||||
|
m_dense.reserve(initial_capacity);
|
||||||
|
m_sparse.resize(initial_capacity, null_identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto insert(Identifier_T identifier, Value_T value) -> Dense_T &
|
||||||
|
{
|
||||||
|
if (m_sparse.size() < identifier + 1)
|
||||||
|
{
|
||||||
|
auto new_capacity = std::max(static_cast<size_t>(identifier + 1), m_sparse.size() * 2);
|
||||||
|
new_capacity = std::min(new_capacity, max_capacity);
|
||||||
|
|
||||||
|
// log::debug("Increasing sparse vector size:", m_dead_count);
|
||||||
|
// log::debug("\tdead_count: {}", m_dead_count);
|
||||||
|
// log::debug("\talive_count: {}", m_alive_count);
|
||||||
|
// log::debug("\tsparse.size: {} -> {}", m_sparse.size(), new_capacity);
|
||||||
|
|
||||||
|
m_sparse.resize(new_capacity, null_identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
++m_alive_count;
|
||||||
|
m_sparse[identifier] = m_dense.size();
|
||||||
|
return m_dense.emplace_back(identifier, std::move(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @warn invalidates begin/end iterators
|
||||||
|
*
|
||||||
|
* @todo(Light): make it not invalidate the iterators >:c
|
||||||
|
*/
|
||||||
|
void remove(Identifier_T identifier) override
|
||||||
|
{
|
||||||
|
auto &idx = m_sparse[identifier];
|
||||||
|
auto &[entity, component] = m_dense[idx];
|
||||||
|
|
||||||
|
auto &[last_entity, last_component] = m_dense.back();
|
||||||
|
auto &last_idx = m_sparse[last_entity];
|
||||||
|
|
||||||
|
// removed entity is in dense's back, just pop and invalidate sparse[identifier]
|
||||||
|
if (entity == last_entity)
|
||||||
|
{
|
||||||
|
idx = null_identifier;
|
||||||
|
m_dense.pop_back();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// swap dense's 'back' to 'removed'
|
||||||
|
std::swap(component, last_component);
|
||||||
|
entity = last_entity;
|
||||||
|
|
||||||
|
// make sparse point to new idx
|
||||||
|
last_idx = idx;
|
||||||
|
|
||||||
|
// pop dense and invalidate sparse[identifier]
|
||||||
|
idx = null_identifier;
|
||||||
|
m_dense.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
++m_dead_count;
|
||||||
|
--m_alive_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear()
|
||||||
|
{
|
||||||
|
m_dense.clear();
|
||||||
|
m_sparse.clear();
|
||||||
|
m_alive_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto contains(Identifier_T identifier) const -> bool
|
||||||
|
{
|
||||||
|
return m_sparse.size() > identifier //
|
||||||
|
&& m_sparse[identifier] != null_identifier //
|
||||||
|
&& m_dense[m_sparse[identifier]].first == identifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto begin() -> std::vector<Dense_T>::iterator
|
||||||
|
{
|
||||||
|
return m_dense.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto end() -> std::vector<Dense_T>::iterator
|
||||||
|
{
|
||||||
|
return m_dense.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto at(Identifier_T identifier) const -> const Dense_T &
|
||||||
|
{
|
||||||
|
return m_dense.at(m_sparse.at(identifier));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto at(Identifier_T identifier) -> Dense_T &
|
||||||
|
{
|
||||||
|
return m_dense.at(m_sparse.at(identifier));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @warn unsafe, for bound-checked access: use `.at` */
|
||||||
|
[[nodiscard]] auto &&operator[](this auto &&self, Identifier_T identifier)
|
||||||
|
{
|
||||||
|
using Self_T = decltype(self);
|
||||||
|
return std::forward<Self_T>(self).m_dense[std::forward<Self_T>(self).m_sparse[identifier]];
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto get_size() const noexcept -> size_t
|
||||||
|
{
|
||||||
|
return m_alive_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto get_capacity() const noexcept -> size_t
|
||||||
|
{
|
||||||
|
return m_sparse.capacity();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto is_empty() const noexcept -> bool
|
||||||
|
{
|
||||||
|
return m_dense.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<Dense_T> m_dense;
|
||||||
|
|
||||||
|
std::vector<Identifier_T> m_sparse;
|
||||||
|
|
||||||
|
size_t m_alive_count {};
|
||||||
|
|
||||||
|
size_t m_dead_count {};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace lt::ecs
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <random>
|
|
||||||
|
|
||||||
namespace lt {
|
|
||||||
|
|
||||||
class UUID
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
UUID(uint64_t uuid = -1);
|
|
||||||
|
|
||||||
operator uint64_t() const
|
|
||||||
{
|
|
||||||
return m_uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
static std::mt19937_64 s_engine;
|
|
||||||
|
|
||||||
static std::uniform_int_distribution<uint64_t> s_distribution;
|
|
||||||
|
|
||||||
uint64_t m_uuid;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace lt
|
|
||||||
|
|
||||||
namespace std {
|
|
||||||
|
|
||||||
template<>
|
|
||||||
struct hash<lt::UUID>
|
|
||||||
{
|
|
||||||
std::size_t operator()(const lt::UUID &uuid) const
|
|
||||||
{
|
|
||||||
return hash<uint64_t>()(static_cast<uint64_t>(uuid));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace std
|
|
||||||
1
modules/env/CMakeLists.txt
vendored
Normal file
1
modules/env/CMakeLists.txt
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
add_library_module(env)
|
||||||
68
modules/env/public/constants.hpp
vendored
Normal file
68
modules/env/public/constants.hpp
vendored
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace lt {
|
||||||
|
|
||||||
|
enum class Platform : uint8_t
|
||||||
|
{
|
||||||
|
/** The GNU/Linux platform.
|
||||||
|
* Tested on the following distros: arch-x86_64
|
||||||
|
* @note: Named like so because `linux` is a built-in identifier.
|
||||||
|
* */
|
||||||
|
gnu_linux,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Microsoft Windows(tm) platform.
|
||||||
|
* Tested on the following architectures: x86_64
|
||||||
|
*/
|
||||||
|
windows,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The apple's macOS platform.
|
||||||
|
* Currently not supported.
|
||||||
|
*/
|
||||||
|
mac,
|
||||||
|
};
|
||||||
|
|
||||||
|
/** The compiler that was used for compiling the project. */
|
||||||
|
enum class Compiler : uint8_t
|
||||||
|
{
|
||||||
|
clang,
|
||||||
|
gcc,
|
||||||
|
msvc,
|
||||||
|
apple_clang,
|
||||||
|
};
|
||||||
|
|
||||||
|
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)
|
||||||
|
constexpr auto platform = Platform::gnu_linux;
|
||||||
|
constexpr auto platform_name = "gnu_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
|
||||||
|
|
||||||
|
#ifdef __clang__
|
||||||
|
constexpr auto compiler = Compiler::clang;
|
||||||
|
constexpr auto compiler_name = "clang";
|
||||||
|
|
||||||
|
/** @todo(Light): insert the full identifier, including version information and such */
|
||||||
|
constexpr auto full_compiler_identifier = "clang";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace constants
|
||||||
|
|
||||||
|
} // namespace lt
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
add_library_module(input system.cpp)
|
add_library_module(input system.cpp)
|
||||||
target_link_libraries(input PUBLIC surface math logger)
|
target_link_libraries(input PUBLIC surface math logger tbb)
|
||||||
|
|
||||||
add_test_module(input system.test.cpp)
|
add_test_module(input system.test.cpp)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#include <input/components.hpp>
|
#include <input/components.hpp>
|
||||||
#include <input/system.hpp>
|
#include <input/system.hpp>
|
||||||
|
#include <memory/reference.hpp>
|
||||||
|
|
||||||
namespace lt::input {
|
namespace lt::input {
|
||||||
|
|
||||||
|
|
@ -9,22 +10,23 @@ struct overloads: Ts...
|
||||||
using Ts::operator()...;
|
using Ts::operator()...;
|
||||||
};
|
};
|
||||||
|
|
||||||
System::System(Ref<ecs::Registry> registry): m_registry(std::move(registry))
|
System::System(memory::Ref<ecs::Registry> registry): m_registry(std::move(registry))
|
||||||
{
|
{
|
||||||
ensure(m_registry, "Failed to initialize input system: null registry");
|
ensure(m_registry, "Failed to initialize input system: null registry");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto System::tick() -> bool
|
void System::tick(app::TickInfo tick)
|
||||||
{
|
{
|
||||||
m_registry->view<surface::SurfaceComponent>().each([&](const entt::entity,
|
for (auto &[entity, surface] : m_registry->view<surface::SurfaceComponent>())
|
||||||
surface::SurfaceComponent &surface) {
|
{
|
||||||
for (const auto &event : surface.peek_events())
|
for (const auto &event : surface.peek_events())
|
||||||
{
|
{
|
||||||
handle_event(event);
|
handle_event(event);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
m_registry->view<InputComponent>().each([&](const entt::entity, InputComponent &input) {
|
for (auto &[entity, input] : m_registry->view<InputComponent>())
|
||||||
|
{
|
||||||
// TODO(Light): instead of iterating over all actions each frame,
|
// TODO(Light): instead of iterating over all actions each frame,
|
||||||
// make a list of "dirty" actions to reset
|
// make a list of "dirty" actions to reset
|
||||||
// and a surface_input->input_action mapping to get to action through input
|
// and a surface_input->input_action mapping to get to action through input
|
||||||
|
|
@ -48,9 +50,14 @@ auto System::tick() -> bool
|
||||||
action.state = InputAction::State::inactive;
|
action.state = InputAction::State::inactive;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
return false;
|
const auto now = std::chrono::steady_clock::now();
|
||||||
|
m_last_tick_result = app::TickResult {
|
||||||
|
.info = tick,
|
||||||
|
.duration = now - tick.start_time,
|
||||||
|
.end_time = now,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void System::on_register()
|
void System::on_register()
|
||||||
|
|
@ -94,7 +101,7 @@ void System::on_key_press(const lt::surface::KeyPressedEvent &event)
|
||||||
{
|
{
|
||||||
if (event.get_key() > m_keys.size())
|
if (event.get_key() > m_keys.size())
|
||||||
{
|
{
|
||||||
log_dbg(
|
log::debug(
|
||||||
"Key code larger than key container size, implement platform-dependant "
|
"Key code larger than key container size, implement platform-dependant "
|
||||||
"key-code-mapping!"
|
"key-code-mapping!"
|
||||||
);
|
);
|
||||||
|
|
@ -109,7 +116,7 @@ void System::on_key_release(const lt::surface::KeyReleasedEvent &event)
|
||||||
{
|
{
|
||||||
if (event.get_key() > m_keys.size())
|
if (event.get_key() > m_keys.size())
|
||||||
{
|
{
|
||||||
log_dbg(
|
log::debug(
|
||||||
"Key code larger than key container size, implement platform-dependant "
|
"Key code larger than key container size, implement platform-dependant "
|
||||||
"key-code-mapping!"
|
"key-code-mapping!"
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
#include <ecs/entity.hpp>
|
#include <ecs/entity.hpp>
|
||||||
#include <input/components.hpp>
|
#include <input/components.hpp>
|
||||||
#include <input/system.hpp>
|
#include <input/system.hpp>
|
||||||
|
#include <memory/reference.hpp>
|
||||||
|
#include <memory/scope.hpp>
|
||||||
|
#include <ranges>
|
||||||
|
#include <surface/system.hpp>
|
||||||
#include <test/test.hpp>
|
#include <test/test.hpp>
|
||||||
|
|
||||||
// NOLINTBEGIN
|
// NOLINTBEGIN
|
||||||
|
|
@ -17,35 +21,49 @@ using test::expect_throw;
|
||||||
using test::Suite;
|
using test::Suite;
|
||||||
// NOLINTEND
|
// NOLINTEND
|
||||||
|
|
||||||
|
[[nodiscard]] auto tick_info() -> app::TickInfo
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
.delta_time = std::chrono::milliseconds { 16 },
|
||||||
|
.budget = std::chrono::milliseconds { 10 },
|
||||||
|
.start_time = std::chrono::steady_clock::now(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
class Fixture
|
class Fixture
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
[[nodiscard]] auto registry() -> Ref<ecs::Registry>
|
[[nodiscard]] auto registry() -> memory::Ref<ecs::Registry>
|
||||||
{
|
{
|
||||||
return m_registry;
|
return m_registry;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto add_input_component() -> ecs::Entity
|
auto add_input_component() -> ecs::EntityId
|
||||||
{
|
{
|
||||||
auto entity = m_registry->create_entity("");
|
auto entity = m_registry->create_entity();
|
||||||
entity.add_component<InputComponent>();
|
m_registry->add<InputComponent>(entity, {});
|
||||||
|
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto add_surface_component() -> ecs::Entity
|
auto add_surface_component() -> ecs::EntityId
|
||||||
{
|
{
|
||||||
auto entity = m_registry->create_entity("");
|
auto entity = m_registry->create_entity();
|
||||||
entity.add_component<surface::SurfaceComponent>(surface::SurfaceComponent::CreateInfo {});
|
m_surface_system.create_surface_component(
|
||||||
|
entity,
|
||||||
|
{ .title = "", .resolution = { 20u, 20u } }
|
||||||
|
);
|
||||||
|
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ref<ecs::Registry> m_registry = create_ref<ecs::Registry>();
|
memory::Ref<ecs::Registry> m_registry = memory::create_ref<ecs::Registry>();
|
||||||
|
|
||||||
|
surface::System m_surface_system = surface::System { m_registry };
|
||||||
};
|
};
|
||||||
|
|
||||||
Suite raii = [] {
|
Suite raii = "raii"_suite = "raii"_suite = [] {
|
||||||
Case { "happy path won't throw" } = [&] {
|
Case { "happy path won't throw" } = [&] {
|
||||||
System { Fixture {}.registry() };
|
System { Fixture {}.registry() };
|
||||||
};
|
};
|
||||||
|
|
@ -63,67 +81,76 @@ Suite raii = [] {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
Suite system_events = [] {
|
Suite system_events = "system_events"_suite = [] {
|
||||||
Case { "on_register won't throw" } = [] {
|
Case { "on_register won't throw" } = [] {
|
||||||
auto fixture = Fixture {};
|
auto fixture = Fixture {};
|
||||||
auto system = System { fixture.registry() };
|
auto registry = fixture.registry();
|
||||||
|
auto system = System { registry };
|
||||||
|
|
||||||
system.on_register();
|
system.on_register();
|
||||||
expect_eq(fixture.registry()->view<InputComponent>().size(), 0);
|
expect_eq(registry->view<InputComponent>().get_size(), 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
Case { "on_unregister won't throw" } = [] {
|
Case { "on_unregister won't throw" } = [] {
|
||||||
auto fixture = Fixture {};
|
auto fixture = Fixture {};
|
||||||
auto system = System { fixture.registry() };
|
auto registry = fixture.registry();
|
||||||
|
auto system = System { registry };
|
||||||
|
|
||||||
system.on_register();
|
system.on_register();
|
||||||
system.on_unregister();
|
system.on_unregister();
|
||||||
expect_eq(fixture.registry()->view<InputComponent>().size(), 0);
|
expect_eq(registry->view<InputComponent>().get_size(), 0);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
Suite registry_events = [] {
|
Suite registry_events = "registry_events"_suite = [] {
|
||||||
Case { "on_construct<InputComnent>" } = [] {
|
Case { "on_construct<InputComnent>" } = [] {
|
||||||
auto fixture = Fixture {};
|
auto fixture = Fixture {};
|
||||||
auto system = System { fixture.registry() };
|
auto registry = fixture.registry();
|
||||||
|
auto system = System { registry };
|
||||||
|
|
||||||
const auto &entity = fixture.add_input_component();
|
const auto &entity = fixture.add_input_component();
|
||||||
expect_eq(fixture.registry()->view<InputComponent>().size(), 1);
|
expect_eq(registry->view<InputComponent>().get_size(), 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
Case { "on_destrroy<InputComponent>" } = [] {
|
Case { "on_destrroy<InputComponent>" } = [] {
|
||||||
auto fixture = Fixture {};
|
auto fixture = Fixture {};
|
||||||
auto system = create_scope<System>(fixture.registry());
|
auto registry = fixture.registry();
|
||||||
|
auto system = memory::create_scope<System>(registry);
|
||||||
|
|
||||||
auto entity_a = fixture.add_input_component();
|
auto entity_a = fixture.add_input_component();
|
||||||
auto entity_b = fixture.add_input_component();
|
auto entity_b = fixture.add_input_component();
|
||||||
expect_eq(fixture.registry()->view<InputComponent>().size(), 2);
|
expect_eq(registry->view<InputComponent>().get_size(), 2);
|
||||||
|
|
||||||
entity_a.remove_component<InputComponent>();
|
registry->remove<InputComponent>(entity_a);
|
||||||
expect_eq(fixture.registry()->view<InputComponent>().size(), 1);
|
expect_eq(registry->view<InputComponent>().get_size(), 1);
|
||||||
|
|
||||||
system.reset();
|
system.reset();
|
||||||
expect_eq(fixture.registry()->view<InputComponent>().size(), 1);
|
expect_eq(registry->view<InputComponent>().get_size(), 1);
|
||||||
|
|
||||||
entity_b.remove_component<InputComponent>();
|
registry->remove<InputComponent>(entity_b);
|
||||||
expect_eq(fixture.registry()->view<InputComponent>().size(), 0);
|
expect_eq(registry->view<InputComponent>().get_size(), 0);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
Suite tick = [] {
|
Suite tick = "tick"_suite = [] {
|
||||||
Case { "Empty tick won't throw" } = [] {
|
Case { "Empty tick won't throw" } = [] {
|
||||||
auto fixture = Fixture {};
|
auto fixture = Fixture {};
|
||||||
|
auto registry = fixture.registry();
|
||||||
auto system = System { fixture.registry() };
|
auto system = System { fixture.registry() };
|
||||||
|
|
||||||
expect_false(system.tick());
|
system.tick(tick_info());
|
||||||
};
|
};
|
||||||
|
|
||||||
Case { "Tick triggers input action" } = [] {
|
Case { "Tick triggers input action" } = [] {
|
||||||
auto fixture = Fixture {};
|
auto fixture = Fixture {};
|
||||||
|
auto registry = fixture.registry();
|
||||||
auto system = System { fixture.registry() };
|
auto system = System { fixture.registry() };
|
||||||
|
|
||||||
auto &surface = fixture.add_surface_component().get_component<surface::SurfaceComponent>();
|
auto surface_entity = fixture.add_surface_component();
|
||||||
auto &input = fixture.add_input_component().get_component<InputComponent>();
|
auto &surface = registry->get<surface::SurfaceComponent>(surface_entity);
|
||||||
|
|
||||||
|
auto input_entity = fixture.add_input_component();
|
||||||
|
auto &input = registry->get<InputComponent>(input_entity);
|
||||||
|
|
||||||
auto action_key = input.add_action(
|
auto action_key = input.add_action(
|
||||||
{
|
{
|
||||||
|
|
@ -133,32 +160,37 @@ Suite tick = [] {
|
||||||
);
|
);
|
||||||
|
|
||||||
expect_eq(input.get_action(action_key).state, input::InputAction::State::inactive);
|
expect_eq(input.get_action(action_key).state, input::InputAction::State::inactive);
|
||||||
system.tick();
|
system.tick(tick_info());
|
||||||
expect_eq(input.get_action(action_key).state, input::InputAction::State::inactive);
|
expect_eq(input.get_action(action_key).state, input::InputAction::State::inactive);
|
||||||
|
|
||||||
surface.push_event(surface::KeyPressedEvent(69));
|
surface.push_event(surface::KeyPressedEvent(69));
|
||||||
system.tick();
|
system.tick(tick_info());
|
||||||
expect_eq(input.get_action(action_key).state, input::InputAction::State::triggered);
|
expect_eq(input.get_action(action_key).state, input::InputAction::State::triggered);
|
||||||
|
|
||||||
system.tick();
|
system.tick(tick_info());
|
||||||
expect_eq(input.get_action(action_key).state, input::InputAction::State::active);
|
expect_eq(input.get_action(action_key).state, input::InputAction::State::active);
|
||||||
|
|
||||||
system.tick();
|
system.tick(tick_info());
|
||||||
system.tick();
|
system.tick(tick_info());
|
||||||
system.tick();
|
system.tick(tick_info());
|
||||||
expect_eq(input.get_action(action_key).state, input::InputAction::State::active);
|
expect_eq(input.get_action(action_key).state, input::InputAction::State::active);
|
||||||
|
|
||||||
surface.push_event(surface::KeyReleasedEvent(69));
|
surface.push_event(surface::KeyReleasedEvent(69));
|
||||||
system.tick();
|
system.tick(tick_info());
|
||||||
expect_eq(input.get_action(action_key).state, input::InputAction::State::inactive);
|
expect_eq(input.get_action(action_key).state, input::InputAction::State::inactive);
|
||||||
};
|
};
|
||||||
|
|
||||||
Case { "Tick triggers" } = [] {
|
Case { "Tick triggers" } = [] {
|
||||||
auto fixture = Fixture {};
|
auto fixture = Fixture {};
|
||||||
|
auto registry = fixture.registry();
|
||||||
auto system = System { fixture.registry() };
|
auto system = System { fixture.registry() };
|
||||||
|
|
||||||
auto &surface = fixture.add_surface_component().get_component<surface::SurfaceComponent>();
|
auto surface_entity = fixture.add_surface_component();
|
||||||
auto &input = fixture.add_input_component().get_component<InputComponent>();
|
auto &surface = registry->get<surface::SurfaceComponent>(surface_entity);
|
||||||
|
|
||||||
|
auto input_entity = fixture.add_input_component();
|
||||||
|
auto &input = registry->get<InputComponent>(input_entity);
|
||||||
|
|
||||||
|
|
||||||
auto action_key = input.add_action(
|
auto action_key = input.add_action(
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <app/system.hpp>
|
#include <app/system.hpp>
|
||||||
#include <ecs/scene.hpp>
|
#include <ecs/registry.hpp>
|
||||||
|
#include <memory/reference.hpp>
|
||||||
#include <surface/components.hpp>
|
#include <surface/components.hpp>
|
||||||
#include <surface/events/keyboard.hpp>
|
#include <surface/events/keyboard.hpp>
|
||||||
#include <surface/events/mouse.hpp>
|
#include <surface/events/mouse.hpp>
|
||||||
|
|
@ -11,14 +12,19 @@ namespace lt::input {
|
||||||
class System: public app::ISystem
|
class System: public app::ISystem
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
System(Ref<ecs::Registry> registry);
|
System(memory::Ref<ecs::Registry> registry);
|
||||||
|
|
||||||
auto tick() -> bool override;
|
void tick(app::TickInfo tick) override;
|
||||||
|
|
||||||
void on_register() override;
|
void on_register() override;
|
||||||
|
|
||||||
void on_unregister() override;
|
void on_unregister() override;
|
||||||
|
|
||||||
|
[[nodiscard]] auto get_last_tick_result() const -> const app::TickResult & override
|
||||||
|
{
|
||||||
|
return m_last_tick_result;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void handle_event(const surface::SurfaceComponent::Event &event);
|
void handle_event(const surface::SurfaceComponent::Event &event);
|
||||||
|
|
||||||
|
|
@ -34,13 +40,15 @@ private:
|
||||||
|
|
||||||
void on_button_release(const lt::surface::ButtonReleasedEvent &event);
|
void on_button_release(const lt::surface::ButtonReleasedEvent &event);
|
||||||
|
|
||||||
Ref<ecs::Registry> m_registry;
|
memory::Ref<ecs::Registry> m_registry;
|
||||||
|
|
||||||
std::array<bool, 512> m_keys {};
|
std::array<bool, 512> m_keys {};
|
||||||
|
|
||||||
std::array<bool, 512> m_buttons {};
|
std::array<bool, 512> m_buttons {};
|
||||||
|
|
||||||
math::vec2 m_pointer_position;
|
math::vec2 m_pointer_position;
|
||||||
|
|
||||||
|
app::TickResult m_last_tick_result {};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
add_library_module(logger logger.cpp)
|
add_library_module(logger)
|
||||||
|
add_test_module(logger logger.test.cpp)
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
#include <logger/logger.hpp>
|
|
||||||
25
modules/logger/private/logger.test.cpp
Normal file
25
modules/logger/private/logger.test.cpp
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
#include <logger/logger.hpp>
|
||||||
|
#include <test/test.hpp>
|
||||||
|
|
||||||
|
using ::lt::test::Case;
|
||||||
|
using ::lt::test::Suite;
|
||||||
|
|
||||||
|
Suite suite = [] {
|
||||||
|
Case { "no format" } = [] {
|
||||||
|
lt::log::trace("trace");
|
||||||
|
lt::log::debug("debug");
|
||||||
|
lt::log::info("info");
|
||||||
|
lt::log::warn("warn");
|
||||||
|
lt::log::error("error");
|
||||||
|
lt::log::critical("critical");
|
||||||
|
};
|
||||||
|
|
||||||
|
Case { "formatted" } = [] {
|
||||||
|
lt::log::trace("trace {}", 69);
|
||||||
|
lt::log::debug("debug {}", 69);
|
||||||
|
lt::log::info("info {}", 69);
|
||||||
|
lt::log::warn("warn {}", 69);
|
||||||
|
lt::log::error("error {}", 69);
|
||||||
|
lt::log::critical("critical {}", 69);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -1,10 +1,17 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <format>
|
#include <cstdint>
|
||||||
#include <print>
|
#include <filesystem>
|
||||||
|
#include <fmt/chrono.h>
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include <source_location>
|
||||||
|
#include <thread>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace lt::log {
|
||||||
|
|
||||||
/** Severity of a log message. */
|
/** Severity of a log message. */
|
||||||
enum class LogLvl : uint8_t
|
enum class Level : uint8_t
|
||||||
{
|
{
|
||||||
/** Lowest and most vebose log level, for tracing execution paths and events */
|
/** Lowest and most vebose log level, for tracing execution paths and events */
|
||||||
trace = 0,
|
trace = 0,
|
||||||
|
|
@ -28,62 +35,152 @@ enum class LogLvl : uint8_t
|
||||||
off = 6,
|
off = 6,
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Simple console logger */
|
namespace details {
|
||||||
class Logger
|
|
||||||
|
inline auto thread_hash_id() noexcept -> std::uint64_t
|
||||||
{
|
{
|
||||||
public:
|
return static_cast<std::uint64_t>(std::hash<std::thread::id> {}(std::this_thread::get_id()));
|
||||||
void static show_imgui_window();
|
}
|
||||||
|
|
||||||
template<typename... Args>
|
} // namespace details
|
||||||
void static log(LogLvl lvl, std::format_string<Args...> fmt, Args &&...args)
|
|
||||||
|
template<typename... Args>
|
||||||
|
struct [[maybe_unused]] print
|
||||||
|
{
|
||||||
|
[[maybe_unused]] print(
|
||||||
|
Level level,
|
||||||
|
const std::source_location &location,
|
||||||
|
std::format_string<Args...> format,
|
||||||
|
Args &&...arguments
|
||||||
|
) noexcept
|
||||||
{
|
{
|
||||||
std::ignore = lvl;
|
constexpr auto to_string = [](Level level, auto location) {
|
||||||
std::println(fmt, std::forward<Args>(args)...);
|
// clang-format off
|
||||||
|
switch (level)
|
||||||
|
{
|
||||||
|
using enum Level;
|
||||||
|
case trace : return "\033[1;37m| trc |\033[0m";
|
||||||
|
case debug : return "\033[1;36m| dbg |\033[0m";
|
||||||
|
case info : return "\033[1;32m| inf |\033[0m";
|
||||||
|
case warn : return "\033[1;33m| wrn |\033[0m";
|
||||||
|
case error : return "\033[1;31m| err |\033[0m";
|
||||||
|
case critical: return "\033[1;41m| crt |\033[0m";
|
||||||
|
case off: return "off";
|
||||||
|
}
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
std::unreachable();
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto path = std::filesystem::path { location.file_name() };
|
||||||
|
|
||||||
|
std::println(
|
||||||
|
"{} {} ==> {}",
|
||||||
|
to_string(level, location),
|
||||||
|
std::format("{}:{}", path.filename().c_str(), location.line()),
|
||||||
|
std::format(format, std::forward<Args>(arguments)...)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void static log(LogLvl lvl, const char *message)
|
|
||||||
{
|
|
||||||
std::ignore = lvl;
|
|
||||||
std::println("{}", message);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private:
|
|
||||||
Logger() = default;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Args>
|
template<typename... Args>
|
||||||
void log_trc(std::format_string<Args...> fmt, Args &&...args)
|
print(Level, const std::source_location &, std::format_string<Args...>, Args &&...) noexcept
|
||||||
{
|
-> print<Args...>;
|
||||||
Logger::log(LogLvl::trace, fmt, std::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename... Args>
|
template<typename... Args>
|
||||||
void log_dbg(std::format_string<Args...> fmt, Args &&...args)
|
struct [[maybe_unused]] trace
|
||||||
{
|
{
|
||||||
Logger::log(LogLvl::debug, fmt, std::forward<Args>(args)...);
|
[[maybe_unused]] trace(
|
||||||
}
|
std::format_string<Args...> format,
|
||||||
|
Args &&...arguments,
|
||||||
|
const std::source_location &location = std::source_location::current()
|
||||||
|
) noexcept
|
||||||
|
{
|
||||||
|
print(Level::trace, location, format, std::forward<Args>(arguments)...);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
template<typename... Args>
|
template<typename... Args>
|
||||||
void log_inf(std::format_string<Args...> fmt, Args &&...args)
|
trace(std::format_string<Args...>, Args &&...) noexcept -> trace<Args...>;
|
||||||
{
|
|
||||||
Logger::log(LogLvl::info, fmt, std::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename... Args>
|
template<typename... Args>
|
||||||
void log_wrn(std::format_string<Args...> fmt, Args &&...args)
|
struct [[maybe_unused]] debug
|
||||||
{
|
{
|
||||||
Logger::log(LogLvl::warn, fmt, std::forward<Args>(args)...);
|
[[maybe_unused]] debug(
|
||||||
}
|
std::format_string<Args...> format,
|
||||||
|
Args &&...arguments,
|
||||||
|
const std::source_location &location = std::source_location::current()
|
||||||
|
) noexcept
|
||||||
|
{
|
||||||
|
print(Level::debug, location, format, std::forward<Args>(arguments)...);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
template<typename... Args>
|
template<typename... Args>
|
||||||
void log_err(std::format_string<Args...> fmt, Args &&...args)
|
debug(std::format_string<Args...>, Args &&...) noexcept -> debug<Args...>;
|
||||||
{
|
|
||||||
Logger::log(LogLvl::error, fmt, std::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename... Args>
|
template<typename... Args>
|
||||||
void log_crt(std::format_string<Args...> fmt, Args &&...args)
|
struct [[maybe_unused]] info
|
||||||
{
|
{
|
||||||
Logger::log(LogLvl::critical, fmt, std::forward<Args>(args)...);
|
[[maybe_unused]] info(
|
||||||
}
|
std::format_string<Args...> format,
|
||||||
|
Args &&...arguments,
|
||||||
|
const std::source_location &location = std::source_location::current()
|
||||||
|
) noexcept
|
||||||
|
{
|
||||||
|
print(Level::info, location, format, std::forward<Args>(arguments)...);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Args>
|
||||||
|
info(std::format_string<Args...>, Args &&...) noexcept -> info<Args...>;
|
||||||
|
|
||||||
|
template<typename... Args>
|
||||||
|
struct [[maybe_unused]] warn
|
||||||
|
{
|
||||||
|
[[maybe_unused]] warn(
|
||||||
|
std::format_string<Args...> format,
|
||||||
|
Args &&...arguments,
|
||||||
|
const std::source_location &location = std::source_location::current()
|
||||||
|
) noexcept
|
||||||
|
{
|
||||||
|
print(Level::warn, location, format, std::forward<Args>(arguments)...);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Args>
|
||||||
|
warn(std::format_string<Args...>, Args &&...) noexcept -> warn<Args...>;
|
||||||
|
|
||||||
|
template<typename... Args>
|
||||||
|
struct [[maybe_unused]] error
|
||||||
|
{
|
||||||
|
[[maybe_unused]] error(
|
||||||
|
std::format_string<Args...> format,
|
||||||
|
Args &&...arguments,
|
||||||
|
const std::source_location &location = std::source_location::current()
|
||||||
|
) noexcept
|
||||||
|
{
|
||||||
|
print(Level::error, location, format, std::forward<Args>(arguments)...);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Args>
|
||||||
|
error(std::format_string<Args...>, Args &&...) noexcept -> error<Args...>;
|
||||||
|
|
||||||
|
template<typename... Args>
|
||||||
|
struct [[maybe_unused]] critical
|
||||||
|
{
|
||||||
|
[[maybe_unused]] critical(
|
||||||
|
std::format_string<Args...> format,
|
||||||
|
Args &&...arguments,
|
||||||
|
const std::source_location &location = std::source_location::current()
|
||||||
|
) noexcept
|
||||||
|
{
|
||||||
|
print(Level::critical, location, format, std::forward<Args>(arguments)...);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Args>
|
||||||
|
critical(std::format_string<Args...>, Args &&...) noexcept -> critical<Args...>;
|
||||||
|
|
||||||
|
} // namespace lt::log
|
||||||
|
|
|
||||||
|
|
@ -31,25 +31,29 @@ namespace lt::math {
|
||||||
*
|
*
|
||||||
* the 1 at [z][3] is to save the Z axis into the resulting W for perspective division.
|
* the 1 at [z][3] is to save the Z axis into the resulting W for perspective division.
|
||||||
*
|
*
|
||||||
* thanks to pikuma: https://www.youtube.com/watch?v=EqNcqBdrNyI
|
* @ref Thanks to pikuma for explaining the math behind this:
|
||||||
|
* https://www.youtube.com/watch?v=EqNcqBdrNyI
|
||||||
*/
|
*/
|
||||||
template<typename T>
|
template<typename T>
|
||||||
constexpr auto perspective(T field_of_view, T aspect_ratio, T z_near, T z_far)
|
constexpr auto perspective(T field_of_view, T aspect_ratio, T z_near, T z_far)
|
||||||
{
|
{
|
||||||
const T half_fov_tan = std::tan(field_of_view / static_cast<T>(2));
|
const T half_fov_tan = std::tan(field_of_view / static_cast<T>(2));
|
||||||
|
|
||||||
auto result = mat4_impl<T> { T { 0 } };
|
auto result = mat4_impl<T>::identity();
|
||||||
|
|
||||||
result[0][0] = T { 1 } / (aspect_ratio * half_fov_tan);
|
result[0][0] = T { 1 } / (aspect_ratio * half_fov_tan);
|
||||||
|
//
|
||||||
result[1][1] = T { 1 } / (half_fov_tan);
|
result[1][1] = T { 1 } / (half_fov_tan);
|
||||||
|
//
|
||||||
result[2][2] = -(z_far + z_near) / (z_far - z_near);
|
// result[2][2] = -(z_far + z_near) / (z_far - z_near);
|
||||||
|
//
|
||||||
|
result[2][2] = z_far / (z_far - z_near);
|
||||||
|
//
|
||||||
result[2][3] = -T { 1 };
|
result[2][3] = -T { 1 };
|
||||||
|
//
|
||||||
result[3][2] = -(T { 2 } * z_far * z_near) / (z_far - z_near);
|
// result[3][2] = -(T { 2 } * z_far * z_near) / (z_far - z_near);
|
||||||
|
result[3][2] = -(z_far * z_near) / (z_far - z_near);
|
||||||
|
//
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
16
modules/math/public/components/transform.hpp
Normal file
16
modules/math/public/components/transform.hpp
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <math/vec3.hpp>
|
||||||
|
|
||||||
|
namespace lt::math::components {
|
||||||
|
|
||||||
|
struct Transform
|
||||||
|
{
|
||||||
|
math::vec3 translation;
|
||||||
|
|
||||||
|
math::vec3 scale;
|
||||||
|
|
||||||
|
math::vec3 rotation;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace lt::math::components
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue