Compare commits
	
		
			179 commits
		
	
	
		
			ci/libx11_
			...
			main
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 14c2512202 | |||
| 599f28fe73 | |||
| 0a1bda9573 | |||
| 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 | 
					 304 changed files with 10025 additions and 8695 deletions
				
			
		
							
								
								
									
										5
									
								
								.clangd
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								.clangd
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
CompileFlags:
 | 
			
		||||
  DriverMode: cl
 | 
			
		||||
  Add:
 | 
			
		||||
    - /EHsc
 | 
			
		||||
    - /std:c++latest
 | 
			
		||||
							
								
								
									
										37
									
								
								.drone.yml
									
										
									
									
									
								
							
							
						
						
									
										37
									
								
								.drone.yml
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -25,13 +25,13 @@ trigger:
 | 
			
		|||
 | 
			
		||||
steps:
 | 
			
		||||
- name: unit tests
 | 
			
		||||
  image: amd64_gcc_unit_tests:latest
 | 
			
		||||
  image: ci:latest
 | 
			
		||||
  pull: if-not-exists
 | 
			
		||||
  commands:
 | 
			
		||||
    - ./tools/ci/amd64/gcc/unit_tests.sh
 | 
			
		||||
 | 
			
		||||
- name: valgrind
 | 
			
		||||
  image: amd64_gcc_valgrind:latest
 | 
			
		||||
  image: ci:latest
 | 
			
		||||
  pull: if-not-exists
 | 
			
		||||
  commands:
 | 
			
		||||
    - ./tools/ci/amd64/gcc/valgrind.sh
 | 
			
		||||
| 
						 | 
				
			
			@ -46,7 +46,7 @@ trigger:
 | 
			
		|||
 | 
			
		||||
steps:
 | 
			
		||||
- name: code coverage
 | 
			
		||||
  image: amd64_clang_coverage:latest
 | 
			
		||||
  image: ci:latest
 | 
			
		||||
  pull: if-not-exists
 | 
			
		||||
  environment:
 | 
			
		||||
    CODECOV_TOKEN:
 | 
			
		||||
| 
						 | 
				
			
			@ -55,13 +55,13 @@ steps:
 | 
			
		|||
    - ./tools/ci/amd64/clang/coverage.sh
 | 
			
		||||
 | 
			
		||||
- name: leak sanitizer
 | 
			
		||||
  image: amd64_clang_lsan:latest
 | 
			
		||||
  image: ci:latest
 | 
			
		||||
  pull: if-not-exists
 | 
			
		||||
  commands:
 | 
			
		||||
    - ./tools/ci/amd64/clang/lsan.sh
 | 
			
		||||
 | 
			
		||||
- name: memory sanitizer
 | 
			
		||||
  image: amd64_clang_msan:latest
 | 
			
		||||
  image: ci:latest
 | 
			
		||||
  pull: if-not-exists
 | 
			
		||||
  commands:
 | 
			
		||||
    - ./tools/ci/amd64/clang/msan.sh
 | 
			
		||||
| 
						 | 
				
			
			@ -76,18 +76,36 @@ trigger:
 | 
			
		|||
 | 
			
		||||
steps:
 | 
			
		||||
- name: clang tidy
 | 
			
		||||
  image: clang_tidy:latest
 | 
			
		||||
  image: ci:latest
 | 
			
		||||
  pull: if-not-exists
 | 
			
		||||
  privileged: true
 | 
			
		||||
  commands:
 | 
			
		||||
    - ./tools/ci/static_analysis/clang_tidy.sh
 | 
			
		||||
 | 
			
		||||
- name: shell check
 | 
			
		||||
  image: ci:latest
 | 
			
		||||
  pull: if-not-exists
 | 
			
		||||
  commands:
 | 
			
		||||
    - ./tools/ci/static_analysis/shell_check.sh
 | 
			
		||||
 | 
			
		||||
- name: clang format
 | 
			
		||||
  image: clang_format:latest
 | 
			
		||||
  image: ci:latest
 | 
			
		||||
  pull: if-not-exists
 | 
			
		||||
  commands:
 | 
			
		||||
    - ./tools/ci/static_analysis/clang_format.sh
 | 
			
		||||
 | 
			
		||||
- name: cmake format
 | 
			
		||||
  image: ci:latest
 | 
			
		||||
  pull: if-not-exists
 | 
			
		||||
  commands:
 | 
			
		||||
    - ./tools/ci/static_analysis/cmake_format.sh
 | 
			
		||||
 | 
			
		||||
- name: shell format
 | 
			
		||||
  image: ci:latest
 | 
			
		||||
  pull: if-not-exists
 | 
			
		||||
  commands:
 | 
			
		||||
    - ./tools/ci/static_analysis/shell_format.sh
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
kind: pipeline
 | 
			
		||||
type: docker 
 | 
			
		||||
| 
						 | 
				
			
			@ -103,11 +121,7 @@ steps:
 | 
			
		|||
  image: documentation:latest
 | 
			
		||||
  pull: if-not-exists
 | 
			
		||||
  commands:
 | 
			
		||||
    - pwd
 | 
			
		||||
    - cd docs
 | 
			
		||||
    - mkdir generated
 | 
			
		||||
    - touch generated/changelogs.rst
 | 
			
		||||
    - touch generated/api.rst
 | 
			
		||||
    - sphinx-build -M html . .
 | 
			
		||||
 | 
			
		||||
    - rm -rf /light_docs_dev/*
 | 
			
		||||
| 
						 | 
				
			
			@ -137,3 +151,4 @@ steps:
 | 
			
		|||
 | 
			
		||||
    - rm -rf /light_docs/*
 | 
			
		||||
    - mv ./html/* /light_docs/
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,42 +1,8 @@
 | 
			
		|||
cmake_minimum_required(VERSION 3.14)
 | 
			
		||||
project(Light)
 | 
			
		||||
set(CMAKE_CXX_STANDARD 23)
 | 
			
		||||
set(CMAKE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake)
 | 
			
		||||
 | 
			
		||||
include(CheckCXXSourceCompiles)
 | 
			
		||||
include(${CMAKE_DIR}/functions.cmake)
 | 
			
		||||
include(${CMAKE_DIR}/definitions.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 ()
 | 
			
		||||
include(${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake/functions.cmake)
 | 
			
		||||
include(${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake/definitions.cmake)
 | 
			
		||||
include(${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake/options.cmake)
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
See docs.light7734.com for a comprehensive project documentation
 | 
			
		||||
 | 
			
		||||
<!---FUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUCK
 | 
			
		||||
MEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
 | 
			
		||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!!!!!!!!-->
 | 
			
		||||
###### “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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										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
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										2
									
								
								docs/.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								docs/.gitignore
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -1,3 +1,5 @@
 | 
			
		|||
_build/
 | 
			
		||||
generated/
 | 
			
		||||
html/
 | 
			
		||||
xml/
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										86
									
								
								docs/Doxyfile
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								docs/Doxyfile
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,86 @@
 | 
			
		|||
TARGET = ./
 | 
			
		||||
INPUT = "../modules"
 | 
			
		||||
RECURSIVE = YES
 | 
			
		||||
 | 
			
		||||
PROJECT_NAME           = "Light"
 | 
			
		||||
JAVADOC_AUTOBRIEF      = YES
 | 
			
		||||
JAVADOC_BANNER         = YES
 | 
			
		||||
GENERATE_XML           = YES
 | 
			
		||||
 | 
			
		||||
EXTRACT_PRIVATE = NO
 | 
			
		||||
EXTRACT_STATIC = NO
 | 
			
		||||
EXTRACT_LOCAL_CLASSES = NO
 | 
			
		||||
HIDE_UNDOC_RELATIONS = YES
 | 
			
		||||
HAVE_DOT = NO
 | 
			
		||||
 | 
			
		||||
GENERATE_TODOLIST      = NO
 | 
			
		||||
GENERATE_HTML          = NO
 | 
			
		||||
GENERATE_DOCSET        = NO
 | 
			
		||||
GENERATE_HTMLHELP      = NO
 | 
			
		||||
GENERATE_CHI           = NO
 | 
			
		||||
GENERATE_QHP           = NO
 | 
			
		||||
GENERATE_ECLIPSEHELP   = NO
 | 
			
		||||
GENERATE_TREEVIEW      = NO
 | 
			
		||||
GENERATE_LATEX         = NO
 | 
			
		||||
GENERATE_RTF           = NO
 | 
			
		||||
GENERATE_MAN           = NO
 | 
			
		||||
GENERATE_DOCBOOK       = NO
 | 
			
		||||
GENERATE_AUTOGEN_DEF   = NO
 | 
			
		||||
GENERATE_SQLITE3       = NO
 | 
			
		||||
GENERATE_PERLMOD       = NO
 | 
			
		||||
GENERATE_TAGFILE       = NO
 | 
			
		||||
GENERATE_LEGEND        = NO
 | 
			
		||||
GENERATE_TESTLIST      = NO
 | 
			
		||||
GENERATE_BUGLIST       = NO
 | 
			
		||||
GENERATE_DEPRECATEDLIST= NO
 | 
			
		||||
 | 
			
		||||
FILE_PATTERNS          = *.c \
 | 
			
		||||
                         *.cc \
 | 
			
		||||
                         *.cxx \
 | 
			
		||||
                         *.cxxm \
 | 
			
		||||
                         *.cpp \
 | 
			
		||||
                         *.cppm \
 | 
			
		||||
                         *.ccm \
 | 
			
		||||
                         *.c++ \
 | 
			
		||||
                         *.c++m \
 | 
			
		||||
                         *.java \
 | 
			
		||||
                         *.ii \
 | 
			
		||||
                         *.ixx \
 | 
			
		||||
                         *.ipp \
 | 
			
		||||
                         *.i++ \
 | 
			
		||||
                         *.inl \
 | 
			
		||||
                         *.idl \
 | 
			
		||||
                         *.ddl \
 | 
			
		||||
                         *.odl \
 | 
			
		||||
                         *.h \
 | 
			
		||||
                         *.hh \
 | 
			
		||||
                         *.hxx \
 | 
			
		||||
                         *.hpp \
 | 
			
		||||
                         *.h++ \
 | 
			
		||||
                         *.l \
 | 
			
		||||
                         *.cs \
 | 
			
		||||
                         *.d \
 | 
			
		||||
                         *.php \
 | 
			
		||||
                         *.php4 \
 | 
			
		||||
                         *.php5 \
 | 
			
		||||
                         *.phtml \
 | 
			
		||||
                         *.inc \
 | 
			
		||||
                         *.m \
 | 
			
		||||
                         *.markdown \
 | 
			
		||||
                         *.md \
 | 
			
		||||
                         *.mm \
 | 
			
		||||
                         *.dox \
 | 
			
		||||
                         *.py \
 | 
			
		||||
                         *.pyw \
 | 
			
		||||
                         *.f90 \
 | 
			
		||||
                         *.f95 \
 | 
			
		||||
                         *.f03 \
 | 
			
		||||
                         *.f08 \
 | 
			
		||||
                         *.f18 \
 | 
			
		||||
                         *.f \
 | 
			
		||||
                         *.for \
 | 
			
		||||
                         *.vhd \
 | 
			
		||||
                         *.vhdl \
 | 
			
		||||
                         *.ucf \
 | 
			
		||||
                         *.qsf \
 | 
			
		||||
                         *.ice
 | 
			
		||||
							
								
								
									
										17
									
								
								docs/api/app.rst
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								docs/api/app.rst
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,17 @@
 | 
			
		|||
Application
 | 
			
		||||
===================================================================================================
 | 
			
		||||
.. toctree::
 | 
			
		||||
   :maxdepth: 3
 | 
			
		||||
   :caption: App
 | 
			
		||||
 | 
			
		||||
Functions
 | 
			
		||||
---------------------------------------------------------------------------------------------------
 | 
			
		||||
.. doxygenfunction:: main
 | 
			
		||||
 | 
			
		||||
Classes
 | 
			
		||||
---------------------------------------------------------------------------------------------------
 | 
			
		||||
.. doxygenclass:: lt::app::ISystem
 | 
			
		||||
 | 
			
		||||
.. doxygenstruct:: lt::app::TickInfo
 | 
			
		||||
 | 
			
		||||
.. doxygenstruct:: lt::app::TickResult
 | 
			
		||||
							
								
								
									
										13
									
								
								docs/api/renderer.rst
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								docs/api/renderer.rst
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,13 @@
 | 
			
		|||
Renderer
 | 
			
		||||
===================================================================================================
 | 
			
		||||
.. toctree::
 | 
			
		||||
   :maxdepth: 3
 | 
			
		||||
   :caption: App
 | 
			
		||||
 | 
			
		||||
Classes
 | 
			
		||||
---------------------------------------------------------------------------------------------------
 | 
			
		||||
.. doxygenenum:: lt::renderer::Api
 | 
			
		||||
 | 
			
		||||
.. doxygenclass:: lt::renderer::System
 | 
			
		||||
 | 
			
		||||
.. doxygenstruct:: lt::renderer::components::Sprite
 | 
			
		||||
							
								
								
									
										14
									
								
								docs/conf.py
									
										
									
									
									
								
							
							
						
						
									
										14
									
								
								docs/conf.py
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -13,13 +13,21 @@ author = 'light7734'
 | 
			
		|||
# -- General configuration ---------------------------------------------------
 | 
			
		||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
 | 
			
		||||
 | 
			
		||||
extensions = []
 | 
			
		||||
extensions = ['breathe']
 | 
			
		||||
 | 
			
		||||
breathe_projects = {"Light": "./xml"}
 | 
			
		||||
breathe_default_project = "Light"
 | 
			
		||||
breathe_default_members = ()
 | 
			
		||||
 | 
			
		||||
# Tell sphinx what the primary language being documented is.
 | 
			
		||||
primary_domain = 'cpp'
 | 
			
		||||
 | 
			
		||||
# Tell sphinx what the pygments highlight language should be.
 | 
			
		||||
highlight_language = 'cpp'
 | 
			
		||||
 | 
			
		||||
templates_path = ['_templates']
 | 
			
		||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# -- Options for HTML output -------------------------------------------------
 | 
			
		||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,68 +0,0 @@
 | 
			
		|||
from git import Repo
 | 
			
		||||
import re
 | 
			
		||||
 | 
			
		||||
repo = Repo(search_parent_directories=True)
 | 
			
		||||
assert not repo.bare
 | 
			
		||||
 | 
			
		||||
file_path = "generated/changelog.rst"
 | 
			
		||||
 | 
			
		||||
messages = []
 | 
			
		||||
short_shas = []
 | 
			
		||||
hex_shas = []
 | 
			
		||||
logs = []
 | 
			
		||||
 | 
			
		||||
remote_url = "https://git.light7734.com/light7734/light/commit"
 | 
			
		||||
def format_log(commit_type, message, major, minor, patch, short_sha, hex_sha):
 | 
			
		||||
    href = f"{remote_url}/{hex_sha}"
 | 
			
		||||
    version = f"{major}.{minor}.{patch}-kitten+{short_sha}";
 | 
			
		||||
    link = f"`{version} <{remote_url}/{hex_sha}>`__"
 | 
			
		||||
    return f"| **{message}** ({link})"
 | 
			
		||||
 | 
			
		||||
for commit in repo.iter_commits():
 | 
			
		||||
    messages.append(commit.summary)
 | 
			
		||||
    short_shas.append(repo.git.rev_parse(commit.hexsha, short=5))
 | 
			
		||||
    hex_shas.append(commit.hexsha)
 | 
			
		||||
 | 
			
		||||
ver_major = 0
 | 
			
		||||
ver_minor = 0
 | 
			
		||||
ver_patch = 0
 | 
			
		||||
 | 
			
		||||
idx = len(messages)
 | 
			
		||||
for message in reversed(messages):
 | 
			
		||||
    idx = idx - 1;
 | 
			
		||||
 | 
			
		||||
    commit_type = re.match("^(feat|fix|refactor|perf|build|asset|test|chore|ci|docs)", message)
 | 
			
		||||
    if not commit_type:
 | 
			
		||||
        continue
 | 
			
		||||
 | 
			
		||||
    match commit_type.group(0):
 | 
			
		||||
        case "feat":
 | 
			
		||||
            ver_minor = ver_minor + 1
 | 
			
		||||
            ver_patch = 0
 | 
			
		||||
 | 
			
		||||
        case "fix":
 | 
			
		||||
            ver_patch = ver_patch + 1
 | 
			
		||||
 | 
			
		||||
        case "refactor":
 | 
			
		||||
            ver_patch = ver_patch + 1
 | 
			
		||||
 | 
			
		||||
        case "perf":
 | 
			
		||||
            ver_patch = ver_patch + 1
 | 
			
		||||
 | 
			
		||||
        case "build":
 | 
			
		||||
            ver_patch = ver_patch + 1
 | 
			
		||||
 | 
			
		||||
        case "asset":
 | 
			
		||||
            ver_patch = ver_patch + 1
 | 
			
		||||
 | 
			
		||||
    logs.append(format_log(commit_type, message, ver_major, ver_minor, ver_patch, short_shas[idx], hex_shas[idx]))
 | 
			
		||||
 | 
			
		||||
with open(file_path, "w") as f:
 | 
			
		||||
    f.write(".. changelogs\n\n\n")
 | 
			
		||||
    f.write("Changelogs\n")
 | 
			
		||||
    f.write("==================================================\n\n")
 | 
			
		||||
 | 
			
		||||
    f.write("KITTEN\n")
 | 
			
		||||
    f.write("--------------------------------------------------\n\n")
 | 
			
		||||
    for log in reversed(logs):
 | 
			
		||||
          f.write(log + '\n')
 | 
			
		||||
| 
						 | 
				
			
			@ -23,10 +23,10 @@
 | 
			
		|||
   guidelines/conventions.rst
 | 
			
		||||
 | 
			
		||||
.. toctree::
 | 
			
		||||
   :maxdepth: 2
 | 
			
		||||
   :caption: Generated Docs
 | 
			
		||||
   :maxdepth: 3
 | 
			
		||||
   :caption: API
 | 
			
		||||
 | 
			
		||||
   generated/api.rst
 | 
			
		||||
   generated/changelog.rst
 | 
			
		||||
   api/app.rst
 | 
			
		||||
   api/renderer.rst
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,35 +0,0 @@
 | 
			
		|||
@ECHO OFF
 | 
			
		||||
 | 
			
		||||
pushd %~dp0
 | 
			
		||||
 | 
			
		||||
REM Command file for Sphinx documentation
 | 
			
		||||
 | 
			
		||||
if "%SPHINXBUILD%" == "" (
 | 
			
		||||
	set SPHINXBUILD=sphinx-build
 | 
			
		||||
)
 | 
			
		||||
set SOURCEDIR=.
 | 
			
		||||
set BUILDDIR=_build
 | 
			
		||||
 | 
			
		||||
%SPHINXBUILD% >NUL 2>NUL
 | 
			
		||||
if errorlevel 9009 (
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
 | 
			
		||||
	echo.installed, then set the SPHINXBUILD environment variable to point
 | 
			
		||||
	echo.to the full path of the 'sphinx-build' executable. Alternatively you
 | 
			
		||||
	echo.may add the Sphinx directory to PATH.
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.If you don't have Sphinx installed, grab it from
 | 
			
		||||
	echo.https://www.sphinx-doc.org/
 | 
			
		||||
	exit /b 1
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "" goto help
 | 
			
		||||
 | 
			
		||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
 | 
			
		||||
goto end
 | 
			
		||||
 | 
			
		||||
:help
 | 
			
		||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
 | 
			
		||||
 | 
			
		||||
:end
 | 
			
		||||
popd
 | 
			
		||||
| 
						 | 
				
			
			@ -1,24 +1,26 @@
 | 
			
		|||
# engine
 | 
			
		||||
add_subdirectory(./base)
 | 
			
		||||
add_subdirectory(./std)
 | 
			
		||||
add_subdirectory(./bitwise)
 | 
			
		||||
add_subdirectory(./env)
 | 
			
		||||
add_subdirectory(./memory)
 | 
			
		||||
add_subdirectory(./time)
 | 
			
		||||
add_subdirectory(./logger)
 | 
			
		||||
add_subdirectory(./debug)
 | 
			
		||||
add_subdirectory(./math)
 | 
			
		||||
# 
 | 
			
		||||
#
 | 
			
		||||
add_subdirectory(./asset_baker)
 | 
			
		||||
add_subdirectory(./asset_parser)
 | 
			
		||||
# add_subdirectory(./asset_manager)
 | 
			
		||||
# 
 | 
			
		||||
add_subdirectory(./assets)
 | 
			
		||||
#
 | 
			
		||||
add_subdirectory(./camera)
 | 
			
		||||
add_subdirectory(./input)
 | 
			
		||||
# add_subdirectory(./ui)
 | 
			
		||||
# 
 | 
			
		||||
#
 | 
			
		||||
add_subdirectory(./surface)
 | 
			
		||||
# add_subdirectory(./renderer)
 | 
			
		||||
add_subdirectory(./renderer)
 | 
			
		||||
add_subdirectory(./ecs)
 | 
			
		||||
# 
 | 
			
		||||
#
 | 
			
		||||
add_subdirectory(./app)
 | 
			
		||||
 | 
			
		||||
# apps 
 | 
			
		||||
# apps
 | 
			
		||||
add_subdirectory(./mirror)
 | 
			
		||||
add_subdirectory(test)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,2 +1,5 @@
 | 
			
		|||
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/system.hpp>
 | 
			
		||||
#include <memory/reference.hpp>
 | 
			
		||||
 | 
			
		||||
namespace lt::app {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -9,10 +10,16 @@ void Application::game_loop()
 | 
			
		|||
	{
 | 
			
		||||
		for (auto &system : m_systems)
 | 
			
		||||
		{
 | 
			
		||||
			if (system->tick())
 | 
			
		||||
			{
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
			const auto &last_tick = system->get_last_tick_result();
 | 
			
		||||
			const auto now = std::chrono::steady_clock::now();
 | 
			
		||||
 | 
			
		||||
			system->tick(
 | 
			
		||||
			    TickInfo {
 | 
			
		||||
			        .delta_time = now - last_tick.end_time,
 | 
			
		||||
			        .budget = std::chrono::milliseconds { 10 },
 | 
			
		||||
			        .start_time = now,
 | 
			
		||||
			    }
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		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));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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));
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,10 +1,13 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <memory/reference.hpp>
 | 
			
		||||
#include <memory/scope.hpp>
 | 
			
		||||
 | 
			
		||||
namespace lt::app {
 | 
			
		||||
 | 
			
		||||
class ISystem;
 | 
			
		||||
 | 
			
		||||
extern Scope<class Application> create_application();
 | 
			
		||||
extern memory::Scope<class Application> create_application();
 | 
			
		||||
 | 
			
		||||
/** The main application class.
 | 
			
		||||
 * Think of this like an aggregate of systems, you register systems through this interface.
 | 
			
		||||
| 
						 | 
				
			
			@ -25,19 +28,19 @@ public:
 | 
			
		|||
 | 
			
		||||
	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:
 | 
			
		||||
	Application() = default;
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
#include <app/application.hpp>
 | 
			
		||||
#include <logger/logger.hpp>
 | 
			
		||||
#include <memory/scope.hpp>
 | 
			
		||||
 | 
			
		||||
auto main(int argc, char *argv[]) -> int32_t
 | 
			
		||||
try
 | 
			
		||||
| 
						 | 
				
			
			@ -8,8 +10,7 @@ try
 | 
			
		|||
	std::ignore = argc;
 | 
			
		||||
	std::ignore = argv;
 | 
			
		||||
 | 
			
		||||
	auto application = lt::Scope<lt::app::Application> {};
 | 
			
		||||
 | 
			
		||||
	auto application = lt::memory::Scope<lt::app::Application> {};
 | 
			
		||||
	application = lt::app::create_application();
 | 
			
		||||
	if (!application)
 | 
			
		||||
	{
 | 
			
		||||
| 
						 | 
				
			
			@ -21,7 +22,7 @@ try
 | 
			
		|||
}
 | 
			
		||||
catch (const std::exception &exp)
 | 
			
		||||
{
 | 
			
		||||
	log_crt("Terminating due to uncaught exception:");
 | 
			
		||||
	log_crt("\texception.what(): {}", exp.what());
 | 
			
		||||
	lt::log::critical("Terminating due to uncaught exception:");
 | 
			
		||||
	lt::log::critical("\texception.what(): {}", exp.what());
 | 
			
		||||
	return EXIT_FAILURE;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,90 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <chrono>
 | 
			
		||||
#include <logger/logger.hpp>
 | 
			
		||||
 | 
			
		||||
namespace lt::app {
 | 
			
		||||
 | 
			
		||||
/** Information required to tick a system.
 | 
			
		||||
 * @note May be used across an entire application-frame (consisting of multiple systems ticking)
 | 
			
		||||
 */
 | 
			
		||||
struct TickInfo
 | 
			
		||||
{
 | 
			
		||||
	using Timepoint_T = std::chrono::time_point<std::chrono::steady_clock>;
 | 
			
		||||
 | 
			
		||||
	using Duration_T = std::chrono::duration<double>;
 | 
			
		||||
 | 
			
		||||
	/** Duration since previous tick's end_time to current tick's start_time. */
 | 
			
		||||
	Duration_T delta_time {};
 | 
			
		||||
 | 
			
		||||
	/** Maximum duration the system is expected to finish ticking in.
 | 
			
		||||
	 *
 | 
			
		||||
	 * if end_time - start_time > budget -> the system exceeded its ticking budget.
 | 
			
		||||
	 * else end_time - start_time < budget -> the system ticked properly.
 | 
			
		||||
	 *
 | 
			
		||||
	 * In other words, end_time is expected to be less than start_time + budget.
 | 
			
		||||
	 */
 | 
			
		||||
	Duration_T budget {};
 | 
			
		||||
 | 
			
		||||
	/** Exact time which ticking started. */
 | 
			
		||||
	Timepoint_T start_time;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** Information about how a system's tick performed */
 | 
			
		||||
struct TickResult
 | 
			
		||||
{
 | 
			
		||||
	using Timepoint_T = std::chrono::time_point<std::chrono::steady_clock>;
 | 
			
		||||
 | 
			
		||||
	using Duration_T = std::chrono::duration<double>;
 | 
			
		||||
 | 
			
		||||
	/** The info supplied to the system for ticking. */
 | 
			
		||||
	TickInfo info;
 | 
			
		||||
 | 
			
		||||
	/** Equivalent to end_time - info.start_time. */
 | 
			
		||||
	Duration_T duration {};
 | 
			
		||||
 | 
			
		||||
	/** Exact time which ticking ended. */
 | 
			
		||||
	Timepoint_T end_time;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
struct SystemDiagnosis
 | 
			
		||||
{
 | 
			
		||||
	enum class Severity : uint8_t
 | 
			
		||||
	{
 | 
			
		||||
		verbose,
 | 
			
		||||
		info,
 | 
			
		||||
		warning,
 | 
			
		||||
		error,
 | 
			
		||||
		fatal,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	std::string message;
 | 
			
		||||
 | 
			
		||||
	std::string code;
 | 
			
		||||
 | 
			
		||||
	Severity severity;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class SystemStats
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
	void push_diagnosis(SystemDiagnosis &&diagnosis)
 | 
			
		||||
	{
 | 
			
		||||
		auto diag = m_diagnosis.emplace_back(std::move(diagnosis));
 | 
			
		||||
 | 
			
		||||
		log::debug("message: {}", diag.message);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] auto empty_diagnosis() const -> bool
 | 
			
		||||
	{
 | 
			
		||||
		return m_diagnosis.empty();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	std::vector<SystemDiagnosis> m_diagnosis;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ISystem
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
| 
						 | 
				
			
			@ -21,7 +104,9 @@ public:
 | 
			
		|||
 | 
			
		||||
	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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,10 +1,6 @@
 | 
			
		|||
add_executable_module(
 | 
			
		||||
    asset_baker entrypoint/baker.cpp
 | 
			
		||||
)
 | 
			
		||||
add_library_module(libasset_baker bakers.cpp)
 | 
			
		||||
target_link_libraries(libasset_baker PUBLIC assets logger lt_debug tbb)
 | 
			
		||||
add_test_module(libasset_baker bakers.test.cpp)
 | 
			
		||||
 | 
			
		||||
target_link_libraries(
 | 
			
		||||
    asset_baker
 | 
			
		||||
    PRIVATE asset_parser
 | 
			
		||||
    PRIVATE stb::stb
 | 
			
		||||
    PRIVATE logger
 | 
			
		||||
)
 | 
			
		||||
add_executable_module(asset_baker entrypoint/baker.cpp)
 | 
			
		||||
target_link_libraries(asset_baker PRIVATE libasset_baker)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										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_parser/assets/text.hpp>
 | 
			
		||||
#include <asset_parser/assets/texture.hpp>
 | 
			
		||||
#include <asset_parser/parser.hpp>
 | 
			
		||||
#include <filesystem>
 | 
			
		||||
#include <assets/shader.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
 | 
			
		||||
try
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -81,20 +19,24 @@ try
 | 
			
		|||
		}
 | 
			
		||||
 | 
			
		||||
		const auto &in_path = directory_iterator.path();
 | 
			
		||||
		const auto out_path = std::format("{}.asset", in_path.c_str());
 | 
			
		||||
 | 
			
		||||
		auto out_path = in_path;
 | 
			
		||||
		out_path.replace_extension(".asset");
 | 
			
		||||
 | 
			
		||||
		try_packing_texture(in_path, out_path);
 | 
			
		||||
		try_packing_text(in_path, out_path);
 | 
			
		||||
		if (in_path.extension() == ".vert")
 | 
			
		||||
		{
 | 
			
		||||
			bake_shader(in_path, out_path, lt::assets::ShaderAsset::Type::vertex);
 | 
			
		||||
		}
 | 
			
		||||
		else if (in_path.extension() == ".frag")
 | 
			
		||||
		{
 | 
			
		||||
			bake_shader(in_path, out_path, lt::assets::ShaderAsset::Type::fragment);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return EXIT_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
catch (const std::exception &exp)
 | 
			
		||||
{
 | 
			
		||||
	log_crt("Terminating due to uncaught exception:");
 | 
			
		||||
	log_crt("\texception.what: {}:", exp.what());
 | 
			
		||||
	lt::log::critical("Terminating due to uncaught exception:");
 | 
			
		||||
	lt::log::critical("\texception.what: {}:", exp.what());
 | 
			
		||||
 | 
			
		||||
	return EXIT_FAILURE;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,184 +1,65 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <asset_parser/assets/text.hpp>
 | 
			
		||||
#include <asset_parser/assets/texture.hpp>
 | 
			
		||||
#include <filesystem>
 | 
			
		||||
#include <assets/shader.hpp>
 | 
			
		||||
#include <logger/logger.hpp>
 | 
			
		||||
#include <string_view>
 | 
			
		||||
#include <unordered_set>
 | 
			
		||||
 | 
			
		||||
#define STB_IMAGE_IMPLEMENTATION
 | 
			
		||||
#include <stb_image.h>
 | 
			
		||||
 | 
			
		||||
namespace lt {
 | 
			
		||||
 | 
			
		||||
class Loader
 | 
			
		||||
inline void bake_shader(
 | 
			
		||||
    const std::filesystem::path &in_path,
 | 
			
		||||
    const std::filesystem::path &out_path,
 | 
			
		||||
    lt::assets::ShaderAsset::Type type
 | 
			
		||||
)
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
	[[nodiscard]] virtual auto get_name() const -> std::string_view = 0;
 | 
			
		||||
	using lt::assets::ShaderAsset;
 | 
			
		||||
	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;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class TextureLoader: public Loader
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
	TextureLoader() = default;
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] virtual auto load(std::filesystem::path file_path) const
 | 
			
		||||
	    -> Assets::TextureAsset::PackageData
 | 
			
		||||
	    = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class StbLoader: public TextureLoader
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
	StbLoader() = default;
 | 
			
		||||
 | 
			
		||||
	void load(std::filesystem::path path);
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] static auto get_supported_extensions() -> std::unordered_set<std::string_view>
 | 
			
		||||
	{
 | 
			
		||||
		return { ".png" };
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] auto get_name() const -> std::string_view override
 | 
			
		||||
	{
 | 
			
		||||
		return "StbLoader";
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] auto load(std::filesystem::path file_path) const
 | 
			
		||||
	    -> Assets::TextureAsset::PackageData override
 | 
			
		||||
	{
 | 
			
		||||
		auto width = int {};
 | 
			
		||||
		auto height = int {};
 | 
			
		||||
		auto channels = int {};
 | 
			
		||||
 | 
			
		||||
		auto *pixels = stbi_load(file_path.string().c_str(), &width, &height, &channels, 4);
 | 
			
		||||
		if (!pixels)
 | 
			
		||||
		{
 | 
			
		||||
			throw std::runtime_error {
 | 
			
		||||
				std::format("Failed to load image file at: {} using stbi_load", file_path.string()),
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const auto metadata = Assets::Asset::Metadata {
 | 
			
		||||
			.type = Assets::Asset::Type::Texture,
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		const auto texture_metadata = Assets::TextureAsset::Metadata {
 | 
			
		||||
            .format = Assets::TextureAsset::Format::RGBA8,
 | 
			
		||||
            .num_components = static_cast<uint32_t>(channels),
 | 
			
		||||
            .pixel_size = {
 | 
			
		||||
                static_cast<uint32_t>(width),
 | 
			
		||||
                static_cast<uint32_t>(height),
 | 
			
		||||
                {},
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
		auto pixels_blob = Assets::Blob {};
 | 
			
		||||
		pixels_blob.resize(static_cast<size_t>(width) * height * channels);
 | 
			
		||||
 | 
			
		||||
		// TODO(Light): figure out if it's possible to directly populate a blob with stbi functions
 | 
			
		||||
		memcpy(pixels_blob.data(), pixels, pixels_blob.size());
 | 
			
		||||
		stbi_image_free(pixels);
 | 
			
		||||
 | 
			
		||||
		return Assets::TextureAsset::PackageData {
 | 
			
		||||
			.metadata = metadata,
 | 
			
		||||
			.texture_metadata = texture_metadata,
 | 
			
		||||
			.pixels = std::move(pixels_blob),
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class TextureLoaderFactory
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
	static auto create(std::string_view file_extension) -> std::unique_ptr<TextureLoader>
 | 
			
		||||
	{
 | 
			
		||||
		if (StbLoader::get_supported_extensions().contains(file_extension))
 | 
			
		||||
		{
 | 
			
		||||
			return std::make_unique<StbLoader>();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return {};
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class TextLoader: Loader
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
	[[nodiscard]] static auto get_supported_extensions() -> std::unordered_set<std::string_view>
 | 
			
		||||
	{
 | 
			
		||||
		return { ".glsl", ".txt", ".hlsl" };
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] auto get_name() const -> std::string_view override
 | 
			
		||||
	{
 | 
			
		||||
		return "TextLoader";
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] auto load(const std::filesystem::path &file_path) const
 | 
			
		||||
	    -> Assets::TextAsset::PackageData
 | 
			
		||||
	{
 | 
			
		||||
		auto stream = std::ifstream { file_path, std::ios::binary };
 | 
			
		||||
		if (!stream.good())
 | 
			
		||||
		{
 | 
			
		||||
			throw std::runtime_error {
 | 
			
		||||
				std::format(
 | 
			
		||||
				    "Failed to open ifstream for text loading of file: {}",
 | 
			
		||||
				    file_path.string()
 | 
			
		||||
				),
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		auto file_size = std::filesystem::file_size(file_path);
 | 
			
		||||
 | 
			
		||||
		auto text_blob = Assets::Blob(file_size);
 | 
			
		||||
 | 
			
		||||
		stream.read((char *)(text_blob.data()), static_cast<long>(file_size)); // NOLINT
 | 
			
		||||
 | 
			
		||||
		const auto metadata = Assets::Asset::Metadata {
 | 
			
		||||
			.type = Assets::Asset::Type::Text,
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		const auto text_metadata = Assets::TextAsset::Metadata {
 | 
			
		||||
			.lines = {},
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		return Assets::TextAsset::PackageData {
 | 
			
		||||
			.metadata = metadata,
 | 
			
		||||
			.text_metadata = {},
 | 
			
		||||
			.text_blob = std::move(text_blob),
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class TextLoaderFactory
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
	static auto create(std::string_view file_extension) -> std::unique_ptr<TextLoader>
 | 
			
		||||
	{
 | 
			
		||||
		if (TextLoader::get_supported_extensions().contains(file_extension))
 | 
			
		||||
		{
 | 
			
		||||
			return std::make_unique<TextLoader>();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return {};
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace lt
 | 
			
		||||
	ShaderAsset::pack(
 | 
			
		||||
	    out_path,
 | 
			
		||||
	    lt::assets::AssetMetadata {
 | 
			
		||||
	        .version = lt::assets::current_version,
 | 
			
		||||
	        .type = ShaderAsset::asset_type_identifier,
 | 
			
		||||
	    },
 | 
			
		||||
	    ShaderAsset::Metadata {
 | 
			
		||||
	        .type = type,
 | 
			
		||||
	    },
 | 
			
		||||
	    std::move(bytes)
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 <lz4.h>
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
target_link_libraries(lt_debug PUBLIC logger)
 | 
			
		||||
target_precompile_headers(lt_debug PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/private/pch.hpp)
 | 
			
		||||
target_precompile_headers(lt_debug PUBLIC
 | 
			
		||||
                          ${CMAKE_CURRENT_SOURCE_DIR}/private/pch.hpp)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,7 +15,7 @@ void Instrumentor::end_session_impl()
 | 
			
		|||
{
 | 
			
		||||
	if (m_current_session_count == 0u)
 | 
			
		||||
	{
 | 
			
		||||
		log_wrn("0 profiling for the ended session");
 | 
			
		||||
		log::warn("0 profiling for the ended session");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	m_current_session_count = 0u;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,6 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <format>
 | 
			
		||||
#include <logger/logger.hpp>
 | 
			
		||||
#include <source_location>
 | 
			
		||||
 | 
			
		||||
namespace lt {
 | 
			
		||||
| 
						 | 
				
			
			@ -10,7 +9,7 @@ template<typename Expression_T, typename... Args_T>
 | 
			
		|||
struct ensure
 | 
			
		||||
{
 | 
			
		||||
	ensure(
 | 
			
		||||
	    Expression_T expression,
 | 
			
		||||
	    const Expression_T &expression,
 | 
			
		||||
	    std::format_string<Args_T...> fmt,
 | 
			
		||||
	    Args_T &&...args,
 | 
			
		||||
	    const std::source_location &location = std::source_location::current()
 | 
			
		||||
| 
						 | 
				
			
			@ -28,7 +27,6 @@ struct ensure
 | 
			
		|||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
template<typename Expression_T, typename... Args_T>
 | 
			
		||||
ensure(Expression_T, std::format_string<Args_T...>, Args_T &&...)
 | 
			
		||||
    -> ensure<Expression_T, Args_T...>;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
add_library_module(ecs entity.cpp scene.cpp uuid.cpp )
 | 
			
		||||
target_link_libraries(ecs 
 | 
			
		||||
    PUBLIC logger lt_debug EnTT::EnTT camera math
 | 
			
		||||
)
 | 
			
		||||
add_library_module(ecs sparse_set.cpp)
 | 
			
		||||
target_link_libraries(ecs PUBLIC logger lt_debug memory)
 | 
			
		||||
 | 
			
		||||
add_test_module(ecs sparse_set.test.cpp registry.test.cpp)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
 | 
			
		||||
#include <ecs/components/uuid.hpp>
 | 
			
		||||
#include <ecs/scene.hpp>
 | 
			
		||||
#include <entt/entt.hpp>
 | 
			
		||||
#include <ecs/registry.hpp>
 | 
			
		||||
#include <memory/reference.hpp>
 | 
			
		||||
 | 
			
		||||
namespace lt {
 | 
			
		||||
namespace lt::ecs {
 | 
			
		||||
 | 
			
		||||
/** High-level entity convenience wrapper */
 | 
			
		||||
class Entity
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
	Entity(entt::entity handle = entt::null, Scene *scene = nullptr);
 | 
			
		||||
 | 
			
		||||
	template<typename t, typename... Args>
 | 
			
		||||
	auto add_component(Args &&...args) -> t &
 | 
			
		||||
	Entity(memory::Ref<Registry> registry, EntityId identifier)
 | 
			
		||||
	    : m_registry(std::move(registry))
 | 
			
		||||
	    , m_identifier(identifier)
 | 
			
		||||
	{
 | 
			
		||||
		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>
 | 
			
		||||
	auto get_component() -> t &
 | 
			
		||||
	template<typename 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>
 | 
			
		||||
	auto has_component() -> bool
 | 
			
		||||
	template<typename Component_T>
 | 
			
		||||
	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>
 | 
			
		||||
	void remove_component()
 | 
			
		||||
	template<typename Component_T>
 | 
			
		||||
	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;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	operator uint32_t()
 | 
			
		||||
	{
 | 
			
		||||
		return (uint32_t)m_handle;
 | 
			
		||||
		return m_identifier;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
target_link_libraries(input PUBLIC surface math  logger)
 | 
			
		||||
target_link_libraries(input PUBLIC surface math logger tbb)
 | 
			
		||||
 | 
			
		||||
add_test_module(input system.test.cpp)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
#include <input/components.hpp>
 | 
			
		||||
#include <input/system.hpp>
 | 
			
		||||
#include <memory/reference.hpp>
 | 
			
		||||
 | 
			
		||||
namespace lt::input {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -9,22 +10,23 @@ struct overloads: Ts...
 | 
			
		|||
	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");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
auto System::tick() -> bool
 | 
			
		||||
void System::tick(app::TickInfo tick)
 | 
			
		||||
{
 | 
			
		||||
	m_registry->view<surface::SurfaceComponent>().each([&](const entt::entity,
 | 
			
		||||
	                                                       surface::SurfaceComponent &surface) {
 | 
			
		||||
	for (auto &[entity, surface] : m_registry->view<surface::SurfaceComponent>())
 | 
			
		||||
	{
 | 
			
		||||
		for (const auto &event : surface.peek_events())
 | 
			
		||||
		{
 | 
			
		||||
			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,
 | 
			
		||||
		// make a list of "dirty" actions to reset
 | 
			
		||||
		// 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;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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()
 | 
			
		||||
| 
						 | 
				
			
			@ -94,7 +101,7 @@ void System::on_key_press(const lt::surface::KeyPressedEvent &event)
 | 
			
		|||
{
 | 
			
		||||
	if (event.get_key() > m_keys.size())
 | 
			
		||||
	{
 | 
			
		||||
		log_dbg(
 | 
			
		||||
		log::debug(
 | 
			
		||||
		    "Key code larger than key container size, implement platform-dependant "
 | 
			
		||||
		    "key-code-mapping!"
 | 
			
		||||
		);
 | 
			
		||||
| 
						 | 
				
			
			@ -109,7 +116,7 @@ void System::on_key_release(const lt::surface::KeyReleasedEvent &event)
 | 
			
		|||
{
 | 
			
		||||
	if (event.get_key() > m_keys.size())
 | 
			
		||||
	{
 | 
			
		||||
		log_dbg(
 | 
			
		||||
		log::debug(
 | 
			
		||||
		    "Key code larger than key container size, implement platform-dependant "
 | 
			
		||||
		    "key-code-mapping!"
 | 
			
		||||
		);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
Some files were not shown because too many files have changed in this diff Show more
		Loading…
	
	Add table
		
		Reference in a new issue