Compare commits
	
		
			173 commits
		
	
	
		
			ci/libx11_
			...
			main
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | 
					 289 changed files with 9694 additions and 8524 deletions
				
			
		
							
								
								
									
										5
									
								
								.clangd
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								.clangd
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | ||||||
|  | CompileFlags: | ||||||
|  |   DriverMode: cl | ||||||
|  |   Add: | ||||||
|  |     - /EHsc | ||||||
|  |     - /std:c++latest | ||||||
							
								
								
									
										33
									
								
								.drone.yml
									
										
									
									
									
								
							
							
						
						
									
										33
									
								
								.drone.yml
									
										
									
									
									
								
							|  | @ -25,13 +25,13 @@ trigger: | ||||||
| 
 | 
 | ||||||
| steps: | steps: | ||||||
| - name: unit tests | - name: unit tests | ||||||
|   image: amd64_gcc_unit_tests:latest |   image: ci:latest | ||||||
|   pull: if-not-exists |   pull: if-not-exists | ||||||
|   commands: |   commands: | ||||||
|     - ./tools/ci/amd64/gcc/unit_tests.sh |     - ./tools/ci/amd64/gcc/unit_tests.sh | ||||||
| 
 | 
 | ||||||
| - name: valgrind | - name: valgrind | ||||||
|   image: amd64_gcc_valgrind:latest |   image: ci:latest | ||||||
|   pull: if-not-exists |   pull: if-not-exists | ||||||
|   commands: |   commands: | ||||||
|     - ./tools/ci/amd64/gcc/valgrind.sh |     - ./tools/ci/amd64/gcc/valgrind.sh | ||||||
|  | @ -46,7 +46,7 @@ trigger: | ||||||
| 
 | 
 | ||||||
| steps: | steps: | ||||||
| - name: code coverage | - name: code coverage | ||||||
|   image: amd64_clang_coverage:latest |   image: ci:latest | ||||||
|   pull: if-not-exists |   pull: if-not-exists | ||||||
|   environment: |   environment: | ||||||
|     CODECOV_TOKEN: |     CODECOV_TOKEN: | ||||||
|  | @ -55,13 +55,13 @@ steps: | ||||||
|     - ./tools/ci/amd64/clang/coverage.sh |     - ./tools/ci/amd64/clang/coverage.sh | ||||||
| 
 | 
 | ||||||
| - name: leak sanitizer | - name: leak sanitizer | ||||||
|   image: amd64_clang_lsan:latest |   image: ci:latest | ||||||
|   pull: if-not-exists |   pull: if-not-exists | ||||||
|   commands: |   commands: | ||||||
|     - ./tools/ci/amd64/clang/lsan.sh |     - ./tools/ci/amd64/clang/lsan.sh | ||||||
| 
 | 
 | ||||||
| - name: memory sanitizer | - name: memory sanitizer | ||||||
|   image: amd64_clang_msan:latest |   image: ci:latest | ||||||
|   pull: if-not-exists |   pull: if-not-exists | ||||||
|   commands: |   commands: | ||||||
|     - ./tools/ci/amd64/clang/msan.sh |     - ./tools/ci/amd64/clang/msan.sh | ||||||
|  | @ -76,18 +76,36 @@ trigger: | ||||||
| 
 | 
 | ||||||
| steps: | steps: | ||||||
| - name: clang tidy | - name: clang tidy | ||||||
|   image: clang_tidy:latest |   image: ci:latest | ||||||
|   pull: if-not-exists |   pull: if-not-exists | ||||||
|   privileged: true |   privileged: true | ||||||
|   commands: |   commands: | ||||||
|     - ./tools/ci/static_analysis/clang_tidy.sh |     - ./tools/ci/static_analysis/clang_tidy.sh | ||||||
| 
 | 
 | ||||||
|  | - name: shell check | ||||||
|  |   image: ci:latest | ||||||
|  |   pull: if-not-exists | ||||||
|  |   commands: | ||||||
|  |     - ./tools/ci/static_analysis/shell_check.sh | ||||||
|  | 
 | ||||||
| - name: clang format | - name: clang format | ||||||
|   image: clang_format:latest |   image: ci:latest | ||||||
|   pull: if-not-exists |   pull: if-not-exists | ||||||
|   commands: |   commands: | ||||||
|     - ./tools/ci/static_analysis/clang_format.sh |     - ./tools/ci/static_analysis/clang_format.sh | ||||||
| 
 | 
 | ||||||
|  | - name: cmake format | ||||||
|  |   image: ci:latest | ||||||
|  |   pull: if-not-exists | ||||||
|  |   commands: | ||||||
|  |     - ./tools/ci/static_analysis/cmake_format.sh | ||||||
|  | 
 | ||||||
|  | - name: shell format | ||||||
|  |   image: ci:latest | ||||||
|  |   pull: if-not-exists | ||||||
|  |   commands: | ||||||
|  |     - ./tools/ci/static_analysis/shell_format.sh | ||||||
|  | 
 | ||||||
| --- | --- | ||||||
| kind: pipeline | kind: pipeline | ||||||
| type: docker  | type: docker  | ||||||
|  | @ -137,3 +155,4 @@ steps: | ||||||
| 
 | 
 | ||||||
|     - rm -rf /light_docs/* |     - rm -rf /light_docs/* | ||||||
|     - mv ./html/* /light_docs/ |     - mv ./html/* /light_docs/ | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | @ -1,42 +1,8 @@ | ||||||
| cmake_minimum_required(VERSION 3.14) | cmake_minimum_required(VERSION 3.14) | ||||||
| project(Light) | project(Light) | ||||||
| set(CMAKE_CXX_STANDARD 23) |  | ||||||
| set(CMAKE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake) |  | ||||||
| 
 | 
 | ||||||
| include(CheckCXXSourceCompiles) | include(${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake/functions.cmake) | ||||||
| include(${CMAKE_DIR}/functions.cmake) | include(${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake/definitions.cmake) | ||||||
| include(${CMAKE_DIR}/definitions.cmake) | include(${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake/options.cmake) | ||||||
| include(${CMAKE_DIR}/dependencies.cmake) |  | ||||||
| 
 |  | ||||||
| add_option(ENABLE_UNIT_TESTS "Enables the building of the unit test modules") |  | ||||||
| add_option(ENABLE_FUZZ_TESTS "Enables the building of the fuzz test modules") |  | ||||||
| 
 |  | ||||||
| add_option(ENABLE_STATIC_ANALYSIS "Makes clang-tidy checks mandatory for compilation") |  | ||||||
| if (ENABLE_STATIC_ANALYSIS) |  | ||||||
|     set(CMAKE_CXX_CLANG_TIDY "clang-tidy;--warnings-as-errors=*;--allow-no-checks") |  | ||||||
| endif () |  | ||||||
| 
 |  | ||||||
| add_option(ENABLE_LLVM_COVERAGE "Enables the code coverage instrumentation for clang") |  | ||||||
| if(ENABLE_LLVM_COVERAGE) |  | ||||||
|     if (NOT "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") |  | ||||||
|         message(FATAL_ERROR "ENABLE_LLVM_COVERAGE only supports the clang compiler") |  | ||||||
|     endif () |  | ||||||
| 
 |  | ||||||
|     # Check for libc++ |  | ||||||
|     check_cxx_source_compiles(" |  | ||||||
|         #include <string> |  | ||||||
|         #ifdef _LIBCPP_VERSION |  | ||||||
|         int main() { return 0; } |  | ||||||
|         #else |  | ||||||
|         #error Not using libc++ |  | ||||||
|         #endif |  | ||||||
|     " USING_LIBCXX) |  | ||||||
|     if(NOT USING_LIBCXX) |  | ||||||
|         message(FATAL_ERROR "ENABLE_LLVM_COVERAGE requires libc++, please compile with -stdlib=libc++") |  | ||||||
|     endif() |  | ||||||
| 
 |  | ||||||
|     add_compile_options(-fprofile-instr-generate -fcoverage-mapping) |  | ||||||
|     add_link_options(-fprofile-instr-generate -fcoverage-mapping) |  | ||||||
| endif () |  | ||||||
| 
 | 
 | ||||||
| add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/modules) | add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/modules) | ||||||
|  |  | ||||||
|  | @ -1,128 +0,0 @@ | ||||||
| # Contributor Covenant Code of Conduct |  | ||||||
| 
 |  | ||||||
| ## Our Pledge |  | ||||||
| 
 |  | ||||||
| We as members, contributors, and leaders pledge to make participation in our |  | ||||||
| community a harassment-free experience for everyone, regardless of age, body |  | ||||||
| size, visible or invisible disability, ethnicity, sex characteristics, gender |  | ||||||
| identity and expression, level of experience, education, socio-economic status, |  | ||||||
| nationality, personal appearance, race, religion, or sexual identity |  | ||||||
| and orientation. |  | ||||||
| 
 |  | ||||||
| We pledge to act and interact in ways that contribute to an open, welcoming, |  | ||||||
| diverse, inclusive, and healthy community. |  | ||||||
| 
 |  | ||||||
| ## Our Standards |  | ||||||
| 
 |  | ||||||
| Examples of behavior that contributes to a positive environment for our |  | ||||||
| community include: |  | ||||||
| 
 |  | ||||||
| * Demonstrating empathy and kindness toward other people |  | ||||||
| * Being respectful of differing opinions, viewpoints, and experiences |  | ||||||
| * Giving and gracefully accepting constructive feedback |  | ||||||
| * Accepting responsibility and apologizing to those affected by our mistakes, |  | ||||||
|   and learning from the experience |  | ||||||
| * Focusing on what is best not just for us as individuals, but for the |  | ||||||
|   overall community |  | ||||||
| 
 |  | ||||||
| Examples of unacceptable behavior include: |  | ||||||
| 
 |  | ||||||
| * The use of sexualized language or imagery, and sexual attention or |  | ||||||
|   advances of any kind |  | ||||||
| * Trolling, insulting or derogatory comments, and personal or political attacks |  | ||||||
| * Public or private harassment |  | ||||||
| * Publishing others' private information, such as a physical or email |  | ||||||
|   address, without their explicit permission |  | ||||||
| * Other conduct which could reasonably be considered inappropriate in a |  | ||||||
|   professional setting |  | ||||||
| 
 |  | ||||||
| ## Enforcement Responsibilities |  | ||||||
| 
 |  | ||||||
| Community leaders are responsible for clarifying and enforcing our standards of |  | ||||||
| acceptable behavior and will take appropriate and fair corrective action in |  | ||||||
| response to any behavior that they deem inappropriate, threatening, offensive, |  | ||||||
| or harmful. |  | ||||||
| 
 |  | ||||||
| Community leaders have the right and responsibility to remove, edit, or reject |  | ||||||
| comments, commits, code, wiki edits, issues, and other contributions that are |  | ||||||
| not aligned to this Code of Conduct, and will communicate reasons for moderation |  | ||||||
| decisions when appropriate. |  | ||||||
| 
 |  | ||||||
| ## Scope |  | ||||||
| 
 |  | ||||||
| This Code of Conduct applies within all community spaces, and also applies when |  | ||||||
| an individual is officially representing the community in public spaces. |  | ||||||
| Examples of representing our community include using an official e-mail address, |  | ||||||
| posting via an official social media account, or acting as an appointed |  | ||||||
| representative at an online or offline event. |  | ||||||
| 
 |  | ||||||
| ## Enforcement |  | ||||||
| 
 |  | ||||||
| Instances of abusive, harassing, or otherwise unacceptable behavior may be |  | ||||||
| reported to the community leaders responsible for enforcement at |  | ||||||
| Discord: Light7734#4652. |  | ||||||
| All complaints will be reviewed and investigated promptly and fairly. |  | ||||||
| 
 |  | ||||||
| All community leaders are obligated to respect the privacy and security of the |  | ||||||
| reporter of any incident. |  | ||||||
| 
 |  | ||||||
| ## Enforcement Guidelines |  | ||||||
| 
 |  | ||||||
| Community leaders will follow these Community Impact Guidelines in determining |  | ||||||
| the consequences for any action they deem in violation of this Code of Conduct: |  | ||||||
| 
 |  | ||||||
| ### 1. Correction |  | ||||||
| 
 |  | ||||||
| **Community Impact**: Use of inappropriate language or other behavior deemed |  | ||||||
| unprofessional or unwelcome in the community. |  | ||||||
| 
 |  | ||||||
| **Consequence**: A private, written warning from community leaders, providing |  | ||||||
| clarity around the nature of the violation and an explanation of why the |  | ||||||
| behavior was inappropriate. A public apology may be requested. |  | ||||||
| 
 |  | ||||||
| ### 2. Warning |  | ||||||
| 
 |  | ||||||
| **Community Impact**: A violation through a single incident or series |  | ||||||
| of actions. |  | ||||||
| 
 |  | ||||||
| **Consequence**: A warning with consequences for continued behavior. No |  | ||||||
| interaction with the people involved, including unsolicited interaction with |  | ||||||
| those enforcing the Code of Conduct, for a specified period of time. This |  | ||||||
| includes avoiding interactions in community spaces as well as external channels |  | ||||||
| like social media. Violating these terms may lead to a temporary or |  | ||||||
| permanent ban. |  | ||||||
| 
 |  | ||||||
| ### 3. Temporary Ban |  | ||||||
| 
 |  | ||||||
| **Community Impact**: A serious violation of community standards, including |  | ||||||
| sustained inappropriate behavior. |  | ||||||
| 
 |  | ||||||
| **Consequence**: A temporary ban from any sort of interaction or public |  | ||||||
| communication with the community for a specified period of time. No public or |  | ||||||
| private interaction with the people involved, including unsolicited interaction |  | ||||||
| with those enforcing the Code of Conduct, is allowed during this period. |  | ||||||
| Violating these terms may lead to a permanent ban. |  | ||||||
| 
 |  | ||||||
| ### 4. Permanent Ban |  | ||||||
| 
 |  | ||||||
| **Community Impact**: Demonstrating a pattern of violation of community |  | ||||||
| standards, including sustained inappropriate behavior,  harassment of an |  | ||||||
| individual, or aggression toward or disparagement of classes of individuals. |  | ||||||
| 
 |  | ||||||
| **Consequence**: A permanent ban from any sort of public interaction within |  | ||||||
| the community. |  | ||||||
| 
 |  | ||||||
| ## Attribution |  | ||||||
| 
 |  | ||||||
| This Code of Conduct is adapted from the [Contributor Covenant][homepage], |  | ||||||
| version 2.0, available at |  | ||||||
| https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. |  | ||||||
| 
 |  | ||||||
| Community Impact Guidelines were inspired by [Mozilla's code of conduct |  | ||||||
| enforcement ladder](https://github.com/mozilla/diversity). |  | ||||||
| 
 |  | ||||||
| [homepage]: https://www.contributor-covenant.org |  | ||||||
| 
 |  | ||||||
| For answers to common questions about this code of conduct, see the FAQ at |  | ||||||
| https://www.contributor-covenant.org/faq. Translations are available at |  | ||||||
| https://www.contributor-covenant.org/translations. |  | ||||||
|  | @ -1,6 +1,4 @@ | ||||||
| # Light | # Light | ||||||
| See docs.light7734.com for a comprehensive project documentation | See docs.light7734.com for a comprehensive project documentation | ||||||
| 
 | 
 | ||||||
| <!---FUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUCK | ###### “No great thing comes into being all at once, any more than a cluster of grapes or a fig. If you tell me, 'I want a fig,' I will answer that it needs time. Let it flower first, then put forth its fruit and then ripen. I say then, if the fig tree's fruit is not brought to perfection suddenly in a single hour, would you expect to gather the fruit of a person’s mind so soon and so easily? I tell you, you must not expect it.” —Epictetus, Discourses 1.15.7-8 | ||||||
| MEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE |  | ||||||
| AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!!!!!!!!--> |  | ||||||
|  |  | ||||||
							
								
								
									
										63
									
								
								conanfile.py
									
										
									
									
									
								
							
							
						
						
									
										63
									
								
								conanfile.py
									
										
									
									
									
								
							|  | @ -1,63 +0,0 @@ | ||||||
| from conan import ConanFile |  | ||||||
| from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout |  | ||||||
| import shutil |  | ||||||
| import os |  | ||||||
| import git |  | ||||||
| 
 |  | ||||||
| class LightRecipe(ConanFile): |  | ||||||
|     name = "Light Engine" |  | ||||||
| 
 |  | ||||||
|     settings = "os", "compiler", "build_type", "arch" |  | ||||||
|     generators = "CMakeDeps" |  | ||||||
| 
 |  | ||||||
|     options = { |  | ||||||
|         "enable_unit_tests": [True, False], |  | ||||||
|         "enable_fuzz_tests": [True, False], |  | ||||||
|         "enable_llvm_coverage": [True, False], |  | ||||||
|         "enable_static_analysis": [True, False], |  | ||||||
|         "use_mold": [True, False], |  | ||||||
|         "export_compile_commands": [True, False], |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     default_options = { |  | ||||||
|         "enable_unit_tests": True, |  | ||||||
|         "enable_fuzz_tests": False, |  | ||||||
|         "enable_llvm_coverage": False, |  | ||||||
|         "enable_static_analysis": False, |  | ||||||
|         "use_mold": False, |  | ||||||
|         "export_compile_commands": True, |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     def requirements(self): |  | ||||||
|         self.requires("imgui/1.92.0-docking") |  | ||||||
|         self.requires("entt/3.15.0") |  | ||||||
|         self.requires("stb/cci.20240531") |  | ||||||
|         self.requires("yaml-cpp/0.8.0") |  | ||||||
|         self.requires("lz4/1.10.0") |  | ||||||
| 
 |  | ||||||
|     def layout(self): |  | ||||||
|         cmake_layout(self) |  | ||||||
| 
 |  | ||||||
|     def generate(self): |  | ||||||
|         tc = CMakeToolchain(self) |  | ||||||
| 
 |  | ||||||
|         tc.variables["CMAKE_BUILD_TYPE"] = self.settings.build_type |  | ||||||
| 
 |  | ||||||
|         if self.options.use_mold: |  | ||||||
|             tc.cache_variables["CMAKE_LINKER_TYPE"] = "MOLD" |  | ||||||
| 
 |  | ||||||
|         tc.cache_variables["CMAKE_EXPORT_COMPILE_COMMANDS"] = self.options.export_compile_commands |  | ||||||
|         tc.cache_variables["ENABLE_UNIT_TESTS"] = self.options.enable_unit_tests |  | ||||||
|         tc.cache_variables["ENABLE_FUZZ_TESTS"] = self.options.enable_fuzz_tests |  | ||||||
|         tc.cache_variables["ENABLE_LLVM_COVERAGE"] = self.options.enable_llvm_coverage |  | ||||||
|         tc.cache_variables["ENABLE_STATIC_ANALYSIS"] = self.options.enable_static_analysis |  | ||||||
| 
 |  | ||||||
|         repo = git.Repo(search_parent_directories=True) |  | ||||||
|         tc.cache_variables["GIT_HASH"] = repo.head.object.hexsha |  | ||||||
| 
 |  | ||||||
|         tc.generate() |  | ||||||
| 
 |  | ||||||
|     def build(self): |  | ||||||
|         cmake = CMake(self) |  | ||||||
|         cmake.configure() |  | ||||||
|         cmake.build() |  | ||||||
										
											Binary file not shown.
										
									
								
							|  | @ -1,10 +0,0 @@ | ||||||
| #version 440 core |  | ||||||
| 
 |  | ||||||
| in vec4 vso_FragmentColor; |  | ||||||
| 
 |  | ||||||
| out vec4 fso_FragmentColor; |  | ||||||
| 
 |  | ||||||
| void main() |  | ||||||
| { |  | ||||||
| 	fso_FragmentColor = vso_FragmentColor; |  | ||||||
| } |  | ||||||
										
											Binary file not shown.
										
									
								
							|  | @ -1,17 +0,0 @@ | ||||||
| #version 440 core |  | ||||||
| 
 |  | ||||||
| layout(location = 0) in vec4 a_Position; |  | ||||||
| layout(location = 1) in vec4 a_Color; |  | ||||||
| 
 |  | ||||||
| layout(std140, binding = 0) uniform ub_ViewProjection |  | ||||||
| { |  | ||||||
| 	mat4 viewProjection; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| layout(location = 0) out vec4 vso_FragmentColor; |  | ||||||
| 
 |  | ||||||
| void main() |  | ||||||
| { |  | ||||||
| 	gl_Position = viewProjection * a_Position; |  | ||||||
| 	vso_FragmentColor = a_Color; |  | ||||||
| } |  | ||||||
										
											Binary file not shown.
										
									
								
							|  | @ -1,12 +0,0 @@ | ||||||
| #version 450 core |  | ||||||
| 
 |  | ||||||
| in vec2 vso_TexCoord; |  | ||||||
| 
 |  | ||||||
| uniform sampler2D u_Texture; |  | ||||||
| 
 |  | ||||||
| out vec4 fso_FragmentColor; |  | ||||||
| 
 |  | ||||||
| void main() |  | ||||||
| { |  | ||||||
| 	fso_FragmentColor = texture(u_Texture, vso_TexCoord); |  | ||||||
| } |  | ||||||
										
											Binary file not shown.
										
									
								
							|  | @ -1,19 +0,0 @@ | ||||||
| #version 450 core |  | ||||||
| 
 |  | ||||||
| layout(location = 0) in vec4 a_Position; |  | ||||||
| layout(location = 1) in vec2 a_TexCoord; |  | ||||||
| 
 |  | ||||||
| layout(std140, binding = 0) uniform ub_ViewProjection |  | ||||||
| { |  | ||||||
| 	mat4 u_ViewProjection; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| layout(location = 0) out vec2 vso_TexCoord; |  | ||||||
| 
 |  | ||||||
| void main() |  | ||||||
| { |  | ||||||
| 	gl_Position = u_ViewProjection * a_Position; |  | ||||||
| 
 |  | ||||||
| 	vso_TexCoord = a_TexCoord; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
										
											Binary file not shown.
										
									
								
							|  | @ -1,14 +0,0 @@ | ||||||
| #version 450 core |  | ||||||
| 
 |  | ||||||
| in vec4 vso_Tint; |  | ||||||
| in vec2 vso_TexCoord; |  | ||||||
| 
 |  | ||||||
| uniform sampler2D u_Texture; |  | ||||||
| 
 |  | ||||||
| out vec4 fso_FragmentColor; |  | ||||||
| 
 |  | ||||||
| void main() |  | ||||||
| { |  | ||||||
| 	fso_FragmentColor = texture(u_Texture, vso_TexCoord) * vso_Tint; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
										
											Binary file not shown.
										
									
								
							|  | @ -1,21 +0,0 @@ | ||||||
| #version 450 core |  | ||||||
| 
 |  | ||||||
| layout(location = 0) in vec4 a_Position; |  | ||||||
| layout(location = 1) in vec4 a_Tint; |  | ||||||
| layout(location = 2) in vec2 a_TexCoord; |  | ||||||
| 
 |  | ||||||
| layout(std140, binding = 0) uniform ub_ViewProjection |  | ||||||
| { |  | ||||||
| 	mat4 u_ViewProjection; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| out vec4 vso_Tint; |  | ||||||
| out vec2 vso_TexCoord;  |  | ||||||
| 
 |  | ||||||
| void main() |  | ||||||
| { |  | ||||||
| 	gl_Position = u_ViewProjection * a_Position; |  | ||||||
| 
 |  | ||||||
| 	vso_Tint = a_Tint; |  | ||||||
| 	vso_TexCoord = a_TexCoord; |  | ||||||
| } |  | ||||||
							
								
								
									
										1
									
								
								data/test_assets/dummytext
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								data/test_assets/dummytext
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | ||||||
|  | The quick brown fox jumps over the lazy dog | ||||||
							
								
								
									
										10
									
								
								data/test_assets/triangle.frag
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								data/test_assets/triangle.frag
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | ||||||
|  | #version 450 core | ||||||
|  | 
 | ||||||
|  | layout(location = 0) in vec3 in_frag_color; | ||||||
|  | 
 | ||||||
|  | layout(location = 0) out vec4 out_frag_color; | ||||||
|  | 
 | ||||||
|  | void main() | ||||||
|  | { | ||||||
|  | 	out_frag_color =  vec4(in_frag_color, 1.0); | ||||||
|  | } | ||||||
							
								
								
									
										
											BIN
										
									
								
								data/test_assets/triangle.frag.asset
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								data/test_assets/triangle.frag.asset
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										26
									
								
								data/test_assets/triangle.vert
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								data/test_assets/triangle.vert
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,26 @@ | ||||||
|  | #version 450 core | ||||||
|  | 
 | ||||||
|  | layout(push_constant ) uniform pc { | ||||||
|  |  mat4 view_projection; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | vec3 positions[3] = vec3[]( | ||||||
|  |     vec3(0.0, -0.5, 0.5), | ||||||
|  |     vec3(0.5, 0.5, 0.5), | ||||||
|  |     vec3(-0.5, 0.5, 0.5) | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | vec3 colors[3] = vec3[]( | ||||||
|  |     vec3(0.0, 0.0, 0.0), | ||||||
|  |     vec3(0.0, 0.0, 0.0), | ||||||
|  |     vec3(0.0, 0.0, 0.0) | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | layout(location = 0) out vec3 out_frag_color; | ||||||
|  | 
 | ||||||
|  | void main()  | ||||||
|  | { | ||||||
|  |     gl_Position = view_projection * vec4(positions[gl_VertexIndex], 1.0); | ||||||
|  |     out_frag_color = colors[gl_VertexIndex]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
							
								
								
									
										
											BIN
										
									
								
								data/test_assets/triangle.vert.asset
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								data/test_assets/triangle.vert.asset
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							|  | @ -1,48 +0,0 @@ | ||||||
| [Window][Dockspace] |  | ||||||
| Pos=0,0 |  | ||||||
| Size=1595,720 |  | ||||||
| Collapsed=0 |  | ||||||
| 
 |  | ||||||
| [Window][Debug##Default] |  | ||||||
| ViewportPos=2078,721 |  | ||||||
| ViewportId=0x9F5F46A1 |  | ||||||
| Size=848,1408 |  | ||||||
| Collapsed=0 |  | ||||||
| 
 |  | ||||||
| [Window][Dear ImGui Demo] |  | ||||||
| Pos=836,24 |  | ||||||
| Size=759,696 |  | ||||||
| Collapsed=0 |  | ||||||
| DockId=0x00000003,1 |  | ||||||
| 
 |  | ||||||
| [Window][Hierarchy] |  | ||||||
| Pos=0,24 |  | ||||||
| Size=184,696 |  | ||||||
| Collapsed=0 |  | ||||||
| DockId=0x00000001,0 |  | ||||||
| 
 |  | ||||||
| [Window][Properties] |  | ||||||
| Pos=836,24 |  | ||||||
| Size=759,696 |  | ||||||
| Collapsed=0 |  | ||||||
| DockId=0x00000003,0 |  | ||||||
| 
 |  | ||||||
| [Window][Game] |  | ||||||
| Pos=186,24 |  | ||||||
| Size=648,696 |  | ||||||
| Collapsed=0 |  | ||||||
| DockId=0x00000002,0 |  | ||||||
| 
 |  | ||||||
| [Window][Content Browser] |  | ||||||
| ViewportPos=1359,621 |  | ||||||
| ViewportId=0x371352B7 |  | ||||||
| Size=1274,1296 |  | ||||||
| Collapsed=0 |  | ||||||
| 
 |  | ||||||
| [Docking][Data] |  | ||||||
| DockSpace     ID=0x1ED03EE2 Window=0x5B816B74 Pos=516,375 Size=1595,696 Split=X |  | ||||||
|   DockNode    ID=0x00000006 Parent=0x1ED03EE2 SizeRef=834,696 Split=X |  | ||||||
|     DockNode  ID=0x00000001 Parent=0x00000006 SizeRef=184,696 Selected=0x29EABFBD |  | ||||||
|     DockNode  ID=0x00000002 Parent=0x00000006 SizeRef=648,696 CentralNode=1 Selected=0x26816F31 |  | ||||||
|   DockNode    ID=0x00000003 Parent=0x1ED03EE2 SizeRef=759,696 Selected=0x199AB496 |  | ||||||
| 
 |  | ||||||
|  | @ -1,20 +1,22 @@ | ||||||
| # engine | # engine | ||||||
| add_subdirectory(./base) | add_subdirectory(./std) | ||||||
|  | add_subdirectory(./bitwise) | ||||||
|  | add_subdirectory(./env) | ||||||
|  | add_subdirectory(./memory) | ||||||
| add_subdirectory(./time) | add_subdirectory(./time) | ||||||
| add_subdirectory(./logger) | add_subdirectory(./logger) | ||||||
| add_subdirectory(./debug) | add_subdirectory(./debug) | ||||||
| add_subdirectory(./math) | add_subdirectory(./math) | ||||||
| # | # | ||||||
| add_subdirectory(./asset_baker) | add_subdirectory(./asset_baker) | ||||||
| add_subdirectory(./asset_parser) | add_subdirectory(./assets) | ||||||
| # add_subdirectory(./asset_manager) |  | ||||||
| # | # | ||||||
| add_subdirectory(./camera) | add_subdirectory(./camera) | ||||||
| add_subdirectory(./input) | add_subdirectory(./input) | ||||||
| # add_subdirectory(./ui) | # add_subdirectory(./ui) | ||||||
| # | # | ||||||
| add_subdirectory(./surface) | add_subdirectory(./surface) | ||||||
| # add_subdirectory(./renderer) | add_subdirectory(./renderer) | ||||||
| add_subdirectory(./ecs) | add_subdirectory(./ecs) | ||||||
| # | # | ||||||
| add_subdirectory(./app) | add_subdirectory(./app) | ||||||
|  |  | ||||||
|  | @ -1,2 +1,5 @@ | ||||||
| add_library_module(app application.cpp) | add_library_module(app application.cpp) | ||||||
| target_link_libraries(app PRIVATE lt_debug) | target_link_libraries( | ||||||
|  |     app | ||||||
|  |     PUBLIC memory | ||||||
|  |     PRIVATE lt_debug) | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| #include <app/application.hpp> | #include <app/application.hpp> | ||||||
| #include <app/system.hpp> | #include <app/system.hpp> | ||||||
|  | #include <memory/reference.hpp> | ||||||
| 
 | 
 | ||||||
| namespace lt::app { | namespace lt::app { | ||||||
| 
 | 
 | ||||||
|  | @ -9,10 +10,16 @@ void Application::game_loop() | ||||||
| 	{ | 	{ | ||||||
| 		for (auto &system : m_systems) | 		for (auto &system : m_systems) | ||||||
| 		{ | 		{ | ||||||
| 			if (system->tick()) | 			const auto &last_tick = system->get_last_tick_result(); | ||||||
| 			{ | 			const auto now = std::chrono::steady_clock::now(); | ||||||
| 				return; | 
 | ||||||
|  | 			system->tick( | ||||||
|  | 			    TickInfo { | ||||||
|  | 			        .delta_time = now - last_tick.end_time, | ||||||
|  | 			        .budget = std::chrono::milliseconds { 10 }, | ||||||
|  | 			        .start_time = now, | ||||||
| 			    } | 			    } | ||||||
|  | 			); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		for (auto &system : m_systems_to_be_registered) | 		for (auto &system : m_systems_to_be_registered) | ||||||
|  | @ -35,12 +42,12 @@ void Application::game_loop() | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Application::register_system(Ref<app::ISystem> system) | void Application::register_system(memory::Ref<app::ISystem> system) | ||||||
| { | { | ||||||
| 	m_systems.emplace_back(std::move(system)); | 	m_systems.emplace_back(std::move(system)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Application::unregister_system(Ref<app::ISystem> system) | void Application::unregister_system(memory::Ref<app::ISystem> system) | ||||||
| { | { | ||||||
| 	m_systems_to_be_unregistered.emplace_back(std::move(system)); | 	m_systems_to_be_unregistered.emplace_back(std::move(system)); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,10 +1,13 @@ | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
|  | #include <memory/reference.hpp> | ||||||
|  | #include <memory/scope.hpp> | ||||||
|  | 
 | ||||||
| namespace lt::app { | namespace lt::app { | ||||||
| 
 | 
 | ||||||
| class ISystem; | class ISystem; | ||||||
| 
 | 
 | ||||||
| extern Scope<class Application> create_application(); | extern memory::Scope<class Application> create_application(); | ||||||
| 
 | 
 | ||||||
| /** The main application class.
 | /** The main application class.
 | ||||||
|  * Think of this like an aggregate of systems, you register systems through this interface. |  * Think of this like an aggregate of systems, you register systems through this interface. | ||||||
|  | @ -25,19 +28,19 @@ public: | ||||||
| 
 | 
 | ||||||
| 	void game_loop(); | 	void game_loop(); | ||||||
| 
 | 
 | ||||||
| 	void register_system(Ref<app::ISystem> system); | 	void register_system(memory::Ref<app::ISystem> system); | ||||||
| 
 | 
 | ||||||
| 	void unregister_system(Ref<app::ISystem> system); | 	void unregister_system(memory::Ref<app::ISystem> system); | ||||||
| 
 | 
 | ||||||
| protected: | protected: | ||||||
| 	Application() = default; | 	Application() = default; | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
| 	std::vector<Ref<app::ISystem>> m_systems; | 	std::vector<memory::Ref<app::ISystem>> m_systems; | ||||||
| 
 | 
 | ||||||
| 	std::vector<Ref<app::ISystem>> m_systems_to_be_unregistered; | 	std::vector<memory::Ref<app::ISystem>> m_systems_to_be_unregistered; | ||||||
| 
 | 
 | ||||||
| 	std::vector<Ref<app::ISystem>> m_systems_to_be_registered; | 	std::vector<memory::Ref<app::ISystem>> m_systems_to_be_registered; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include <app/application.hpp> | #include <app/application.hpp> | ||||||
|  | #include <memory/scope.hpp> | ||||||
| 
 | 
 | ||||||
| auto main(int argc, char *argv[]) -> int32_t | auto main(int argc, char *argv[]) -> int32_t | ||||||
| try | try | ||||||
|  | @ -8,8 +9,7 @@ try | ||||||
| 	std::ignore = argc; | 	std::ignore = argc; | ||||||
| 	std::ignore = argv; | 	std::ignore = argv; | ||||||
| 
 | 
 | ||||||
| 	auto application = lt::Scope<lt::app::Application> {}; | 	auto application = lt::memory::Scope<lt::app::Application> {}; | ||||||
| 
 |  | ||||||
| 	application = lt::app::create_application(); | 	application = lt::app::create_application(); | ||||||
| 	if (!application) | 	if (!application) | ||||||
| 	{ | 	{ | ||||||
|  |  | ||||||
|  | @ -1,7 +1,89 @@ | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
|  | #include <chrono> | ||||||
|  | 
 | ||||||
| namespace lt::app { | namespace lt::app { | ||||||
| 
 | 
 | ||||||
|  | /** Information required to tick a system.
 | ||||||
|  |  * @note May be used across an entire application-frame (consisting of multiple systems ticking) | ||||||
|  |  */ | ||||||
|  | struct TickInfo | ||||||
|  | { | ||||||
|  | 	using Timepoint_T = std::chrono::time_point<std::chrono::steady_clock>; | ||||||
|  | 
 | ||||||
|  | 	using Duration_T = std::chrono::duration<double>; | ||||||
|  | 
 | ||||||
|  | 	/** Duration since previous tick's end_time to current tick's start_time. */ | ||||||
|  | 	Duration_T delta_time {}; | ||||||
|  | 
 | ||||||
|  | 	/** Maximum duration the system is expected to finish ticking in.
 | ||||||
|  | 	 * | ||||||
|  | 	 * if end_time - start_time > budget -> the system exceeded its ticking budget. | ||||||
|  | 	 * else end_time - start_time < budget -> the system ticked properly. | ||||||
|  | 	 * | ||||||
|  | 	 * In other words, end_time is expected to be less than start_time + budget. | ||||||
|  | 	 */ | ||||||
|  | 	Duration_T budget {}; | ||||||
|  | 
 | ||||||
|  | 	/** Exact time which ticking started. */ | ||||||
|  | 	Timepoint_T start_time; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** Information about how a system's tick performed */ | ||||||
|  | struct TickResult | ||||||
|  | { | ||||||
|  | 	using Timepoint_T = std::chrono::time_point<std::chrono::steady_clock>; | ||||||
|  | 
 | ||||||
|  | 	using Duration_T = std::chrono::duration<double>; | ||||||
|  | 
 | ||||||
|  | 	/** The info supplied to the system for ticking. */ | ||||||
|  | 	TickInfo info; | ||||||
|  | 
 | ||||||
|  | 	/** Equivalent to end_time - info.start_time. */ | ||||||
|  | 	Duration_T duration {}; | ||||||
|  | 
 | ||||||
|  | 	/** Exact time which ticking ended. */ | ||||||
|  | 	Timepoint_T end_time; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | struct SystemDiagnosis | ||||||
|  | { | ||||||
|  | 	enum class Severity : uint8_t | ||||||
|  | 	{ | ||||||
|  | 		verbose, | ||||||
|  | 		info, | ||||||
|  | 		warning, | ||||||
|  | 		error, | ||||||
|  | 		fatal, | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	std::string message; | ||||||
|  | 
 | ||||||
|  | 	std::string code; | ||||||
|  | 
 | ||||||
|  | 	Severity severity; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | class SystemStats | ||||||
|  | { | ||||||
|  | public: | ||||||
|  | 	void push_diagnosis(SystemDiagnosis &&diagnosis) | ||||||
|  | 	{ | ||||||
|  | 		auto diag = m_diagnosis.emplace_back(std::move(diagnosis)); | ||||||
|  | 
 | ||||||
|  | 		log_dbg("message: {}", diag.message); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	[[nodiscard]] auto empty_diagnosis() const -> bool | ||||||
|  | 	{ | ||||||
|  | 		return m_diagnosis.empty(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  | 	std::vector<SystemDiagnosis> m_diagnosis; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| class ISystem | class ISystem | ||||||
| { | { | ||||||
| public: | public: | ||||||
|  | @ -21,7 +103,9 @@ public: | ||||||
| 
 | 
 | ||||||
| 	virtual void on_unregister() = 0; | 	virtual void on_unregister() = 0; | ||||||
| 
 | 
 | ||||||
| 	virtual auto tick() -> bool = 0; | 	virtual void tick(TickInfo tick) = 0; | ||||||
|  | 
 | ||||||
|  | 	[[nodiscard]] virtual auto get_last_tick_result() const -> const TickResult & = 0; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } // namespace lt::app
 | } // namespace lt::app
 | ||||||
|  |  | ||||||
|  | @ -1,10 +1,6 @@ | ||||||
| add_executable_module( | add_library_module(libasset_baker bakers.cpp) | ||||||
|     asset_baker entrypoint/baker.cpp | target_link_libraries(libasset_baker PUBLIC assets logger lt_debug tbb) | ||||||
| ) | add_test_module(libasset_baker bakers.test.cpp) | ||||||
| 
 | 
 | ||||||
| target_link_libraries( | add_executable_module(asset_baker entrypoint/baker.cpp) | ||||||
|     asset_baker | target_link_libraries(asset_baker PRIVATE libasset_baker) | ||||||
|     PRIVATE asset_parser |  | ||||||
|     PRIVATE stb::stb |  | ||||||
|     PRIVATE logger |  | ||||||
| ) |  | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								modules/asset_baker/private/bakers.test.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								modules/asset_baker/private/bakers.test.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,2 @@ | ||||||
|  | #include <asset_baker/bakers.hpp> | ||||||
|  | #include <test/test.hpp> | ||||||
|  | @ -1,68 +1,5 @@ | ||||||
| #include <asset_baker/bakers.hpp> | #include <asset_baker/bakers.hpp> | ||||||
| #include <asset_parser/assets/text.hpp> | #include <assets/shader.hpp> | ||||||
| #include <asset_parser/assets/texture.hpp> |  | ||||||
| #include <asset_parser/parser.hpp> |  | ||||||
| #include <filesystem> |  | ||||||
| #include <logger/logger.hpp> |  | ||||||
| 
 |  | ||||||
| void try_packing_texture( |  | ||||||
|     const std::filesystem::path &in_path, |  | ||||||
|     const std::filesystem::path &out_path |  | ||||||
| ) |  | ||||||
| { |  | ||||||
| 	auto texture_loader = lt::TextureLoaderFactory::create(in_path.extension().string()); |  | ||||||
| 	if (!texture_loader) |  | ||||||
| 	{ |  | ||||||
| 		// Don't log anything; this is expected.
 |  | ||||||
| 		return; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	try |  | ||||||
| 	{ |  | ||||||
| 		Assets::TextureAsset::pack(texture_loader->load(in_path), out_path); |  | ||||||
| 
 |  | ||||||
| 		log_inf("Packed a texture asset:"); |  | ||||||
| 		log_inf("\tloader  : {}", texture_loader->get_name()); |  | ||||||
| 		log_inf("\tin  path: {}", in_path.string()); |  | ||||||
| 		log_inf("\tout path: {}", out_path.string()); |  | ||||||
| 	} |  | ||||||
| 	catch (const std::exception &exp) |  | ||||||
| 	{ |  | ||||||
| 		log_err("Failed to pack texture asset:"); |  | ||||||
| 		log_err("\tloader  : {}", texture_loader->get_name()); |  | ||||||
| 		log_err("\tin path : {}", in_path.string()); |  | ||||||
| 		log_err("\tout path: {}", out_path.string()); |  | ||||||
| 		log_err("\texp.what: {}", exp.what()); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void try_packing_text(const std::filesystem::path &in_path, const std::filesystem::path &out_path) |  | ||||||
| { |  | ||||||
| 	auto text_loader = lt::TextLoaderFactory::create(in_path.extension().string()); |  | ||||||
| 	if (!text_loader) |  | ||||||
| 	{ |  | ||||||
| 		// Don't log anything; this is expected.
 |  | ||||||
| 		return; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	try |  | ||||||
| 	{ |  | ||||||
| 		Assets::TextAsset::pack(text_loader->load(in_path), out_path); |  | ||||||
| 
 |  | ||||||
| 		log_inf("Packed a text asset:"); |  | ||||||
| 		log_inf("\tloader  : {}", text_loader->get_name()); |  | ||||||
| 		log_inf("\tin  path: {}", in_path.string()); |  | ||||||
| 		log_inf("\tout path: {}", out_path.string()); |  | ||||||
| 	} |  | ||||||
| 	catch (const std::exception &exp) |  | ||||||
| 	{ |  | ||||||
| 		log_err("Failed to pack a text asset:"); |  | ||||||
| 		log_err("\tloader  : {}", text_loader->get_name()); |  | ||||||
| 		log_err("\tin path : {}", in_path.string()); |  | ||||||
| 		log_err("\tout path: {}", out_path.string()); |  | ||||||
| 		log_err("\texp.what: {}", exp.what()); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| auto main(int argc, char *argv[]) -> int32_t | auto main(int argc, char *argv[]) -> int32_t | ||||||
| try | try | ||||||
|  | @ -81,12 +18,16 @@ try | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		const auto &in_path = directory_iterator.path(); | 		const auto &in_path = directory_iterator.path(); | ||||||
|  | 		const auto out_path = std::format("{}.asset", in_path.c_str()); | ||||||
| 
 | 
 | ||||||
| 		auto out_path = in_path; | 		if (in_path.extension() == ".vert") | ||||||
| 		out_path.replace_extension(".asset"); | 		{ | ||||||
| 
 | 			bake_shader(in_path, out_path, lt::assets::ShaderAsset::Type::vertex); | ||||||
| 		try_packing_texture(in_path, out_path); | 		} | ||||||
| 		try_packing_text(in_path, out_path); | 		else if (in_path.extension() == ".frag") | ||||||
|  | 		{ | ||||||
|  | 			bake_shader(in_path, out_path, lt::assets::ShaderAsset::Type::fragment); | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return EXIT_SUCCESS; | 	return EXIT_SUCCESS; | ||||||
|  |  | ||||||
|  | @ -1,184 +1,64 @@ | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include <asset_parser/assets/text.hpp> | #include <assets/shader.hpp> | ||||||
| #include <asset_parser/assets/texture.hpp> |  | ||||||
| #include <filesystem> |  | ||||||
| #include <logger/logger.hpp> |  | ||||||
| #include <string_view> |  | ||||||
| #include <unordered_set> |  | ||||||
| 
 | 
 | ||||||
| #define STB_IMAGE_IMPLEMENTATION | inline void bake_shader( | ||||||
| #include <stb_image.h> |     const std::filesystem::path &in_path, | ||||||
| 
 |     const std::filesystem::path &out_path, | ||||||
| namespace lt { |     lt::assets::ShaderAsset::Type type | ||||||
| 
 | ) | ||||||
| class Loader |  | ||||||
| { | { | ||||||
| public: | 	using lt::assets::ShaderAsset; | ||||||
| 	[[nodiscard]] virtual auto get_name() const -> std::string_view = 0; | 	using enum lt::assets::ShaderAsset::Type; | ||||||
| 
 | 
 | ||||||
| 	Loader() = default; | 	auto glsl_path = in_path.string(); | ||||||
|  | 	auto spv_path = std::format("{}.spv", glsl_path); | ||||||
|  | 	log_trc( | ||||||
|  | 	    "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)
 | ||||||
| 	Loader(const Loader &) = delete; | 	system( | ||||||
| 
 |  | ||||||
| 	auto operator=(Loader &&) -> Loader & = default; |  | ||||||
| 
 |  | ||||||
| 	auto operator=(const Loader &) -> Loader & = delete; |  | ||||||
| 
 |  | ||||||
| 	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( | 	    std::format( | ||||||
| 				    "Failed to open ifstream for text loading of file: {}", | 	        "glslc --target-env=vulkan1.4 -std=450core -fshader-stage={} {} -o {}", | ||||||
| 				    file_path.string() | 	        type == vertex ? "vert" : "frag", | ||||||
| 				), | 	        glsl_path, | ||||||
| 			}; | 	        spv_path | ||||||
|  | 	    ) | ||||||
|  | 	        .c_str() | ||||||
|  | 	); | ||||||
|  | 
 | ||||||
|  | 	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 | ||||||
|  | 	); | ||||||
|  | 
 | ||||||
|  | 	stream.seekg(0, std::ios::end); | ||||||
|  | 	const auto size = stream.tellg(); | ||||||
|  | 
 | ||||||
|  | 	auto bytes = std::vector<std::byte>(size); | ||||||
|  | 	stream.seekg(0, std::ios::beg); | ||||||
|  | 	stream.read((char *)bytes.data(), size); // NOLINT
 | ||||||
|  | 	log_dbg("BYTES: {}", bytes.size()); | ||||||
|  | 	stream.close(); | ||||||
|  | 	std::filesystem::remove(spv_path); | ||||||
|  | 
 | ||||||
|  | 	ShaderAsset::pack( | ||||||
|  | 	    out_path, | ||||||
|  | 	    lt::assets::AssetMetadata { | ||||||
|  | 	        .version = lt::assets::current_version, | ||||||
|  | 	        .type = ShaderAsset::asset_type_identifier, | ||||||
|  | 	    }, | ||||||
|  | 	    ShaderAsset::Metadata { | ||||||
|  | 	        .type = type, | ||||||
|  | 	    }, | ||||||
|  | 	    std::move(bytes) | ||||||
|  | 	); | ||||||
| } | } | ||||||
| 
 |  | ||||||
| 		auto file_size = std::filesystem::file_size(file_path); |  | ||||||
| 
 |  | ||||||
| 		auto text_blob = Assets::Blob(file_size); |  | ||||||
| 
 |  | ||||||
| 		stream.read((char *)(text_blob.data()), static_cast<long>(file_size)); // NOLINT
 |  | ||||||
| 
 |  | ||||||
| 		const auto metadata = Assets::Asset::Metadata { |  | ||||||
| 			.type = Assets::Asset::Type::Text, |  | ||||||
| 		}; |  | ||||||
| 
 |  | ||||||
| 		const auto text_metadata = Assets::TextAsset::Metadata { |  | ||||||
| 			.lines = {}, |  | ||||||
| 		}; |  | ||||||
| 
 |  | ||||||
| 		return Assets::TextAsset::PackageData { |  | ||||||
| 			.metadata = metadata, |  | ||||||
| 			.text_metadata = {}, |  | ||||||
| 			.text_blob = std::move(text_blob), |  | ||||||
| 		}; |  | ||||||
| 	} |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| class TextLoaderFactory |  | ||||||
| { |  | ||||||
| public: |  | ||||||
| 	static auto create(std::string_view file_extension) -> std::unique_ptr<TextLoader> |  | ||||||
| 	{ |  | ||||||
| 		if (TextLoader::get_supported_extensions().contains(file_extension)) |  | ||||||
| 		{ |  | ||||||
| 			return std::make_unique<TextLoader>(); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		return {}; |  | ||||||
| 	} |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| } // namespace lt
 |  | ||||||
|  |  | ||||||
|  | @ -1,9 +0,0 @@ | ||||||
| add_library_module(asset_manager  |  | ||||||
|     asset_manager.cpp |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| target_link_libraries( |  | ||||||
|   asset_manager |  | ||||||
|   PUBLIC asset_parser |  | ||||||
|   PRIVATE logger |  | ||||||
| ) |  | ||||||
|  | @ -1,92 +0,0 @@ | ||||||
| #include <asset_manager/asset_manager.hpp> |  | ||||||
| #include <asset_parser/assets/text.hpp> |  | ||||||
| #include <asset_parser/assets/texture.hpp> |  | ||||||
| #include <logger/logger.hpp> |  | ||||||
| #include <renderer/graphics_context.hpp> |  | ||||||
| #include <renderer/shader.hpp> |  | ||||||
| #include <renderer/texture.hpp> |  | ||||||
| 
 |  | ||||||
| namespace lt { |  | ||||||
| 
 |  | ||||||
| /* static */ auto AssetManager::instance() -> AssetManager & |  | ||||||
| { |  | ||||||
| 	static auto instance = AssetManager {}; |  | ||||||
| 	return instance; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void AssetManager::load_shader_impl( |  | ||||||
|     const std::string &name, |  | ||||||
|     const std::filesystem::path &vertex_path, |  | ||||||
|     const std::filesystem::path &pixel_path |  | ||||||
| ) |  | ||||||
| { |  | ||||||
| 	try |  | ||||||
| 	{ |  | ||||||
| 		log_trc("Loading shader:"); |  | ||||||
| 		log_trc("\tname       : {}", name); |  | ||||||
| 		log_trc("\tvertex path: {}", vertex_path.string()); |  | ||||||
| 		log_trc("\tpixel path : {}", pixel_path.string()); |  | ||||||
| 
 |  | ||||||
| 		m_shaders[name] = Ref<Shader>(Shader::create( |  | ||||||
| 		    get_or_load_text_asset(vertex_path.string()), |  | ||||||
| 		    get_or_load_text_asset(pixel_path), |  | ||||||
| 		    GraphicsContext::get_shared_context() |  | ||||||
| 		)); |  | ||||||
| 	} |  | ||||||
| 	catch (const std::exception &exp) |  | ||||||
| 	{ |  | ||||||
| 		log_err("Failed to load shader:"); |  | ||||||
| 		log_err("\tname       : {}", name); |  | ||||||
| 		log_err("\tvertex path: {}", vertex_path.string()); |  | ||||||
| 		log_err("\tpixel path : {}", pixel_path.string()); |  | ||||||
| 		log_err("\texception  : {}", exp.what()); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void AssetManager::load_texture_impl(const std::string &name, const std::filesystem::path &path) |  | ||||||
| { |  | ||||||
| 	try |  | ||||||
| 	{ |  | ||||||
| 		log_trc("Loading texture:"); |  | ||||||
| 		log_trc("\tname: {}", name); |  | ||||||
| 		log_trc("\tpath: {}", path.string()); |  | ||||||
| 
 |  | ||||||
| 		m_textures[name] = Ref<Texture>( |  | ||||||
| 		    Texture::create(get_or_load_texture_asset(path), GraphicsContext::get_shared_context()) |  | ||||||
| 		); |  | ||||||
| 	} |  | ||||||
| 	catch (const std::exception &exp) |  | ||||||
| 	{ |  | ||||||
| 		log_err("Failed to load texture:"); |  | ||||||
| 		log_err("\tname     : {}", name); |  | ||||||
| 		log_err("\tpath     : {}", path.string()); |  | ||||||
| 		log_err("\texception: {}", exp.what()); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| auto AssetManager::get_or_load_text_asset(const std::filesystem::path &path) |  | ||||||
|     -> Ref<Assets::TextAsset> |  | ||||||
| { |  | ||||||
| 	const auto key = std::filesystem::canonical(path).string(); |  | ||||||
| 	if (!m_text_assets.contains(key)) |  | ||||||
| 	{ |  | ||||||
| 		m_text_assets.emplace(key, create_ref<Assets::TextAsset>(path)); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return m_text_assets[key]; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| auto AssetManager::get_or_load_texture_asset(const std::filesystem::path &path) |  | ||||||
|     -> Ref<Assets::TextureAsset> |  | ||||||
| { |  | ||||||
| 	const auto key = std::filesystem::canonical(path).string(); |  | ||||||
| 	if (!m_texture_assets.contains(key)) |  | ||||||
| 	{ |  | ||||||
| 		m_texture_assets.emplace(key, create_ref<Assets::TextureAsset>(path)); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return m_texture_assets[key]; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| } // namespace lt
 |  | ||||||
|  | @ -1,78 +0,0 @@ | ||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <filesystem> |  | ||||||
| 
 |  | ||||||
| namespace Assets { |  | ||||||
| 
 |  | ||||||
| class TextAsset; |  | ||||||
| 
 |  | ||||||
| class TextureAsset; |  | ||||||
| 
 |  | ||||||
| } // namespace Assets
 |  | ||||||
| 
 |  | ||||||
| namespace lt { |  | ||||||
| 
 |  | ||||||
| class Shader; |  | ||||||
| class Texture; |  | ||||||
| 
 |  | ||||||
| /**
 |  | ||||||
|  * Asset is the data on the disk. |  | ||||||
|  * Resource is the data on the gpu/cpu |  | ||||||
|  * |  | ||||||
|  * eg. TextureAsset is the file on the disk |  | ||||||
|  * eg. Texture is the representation of it in the GPU |  | ||||||
|  */ |  | ||||||
| class AssetManager |  | ||||||
| { |  | ||||||
| public: |  | ||||||
| 	static void load_shader( |  | ||||||
| 	    const std::string &name, |  | ||||||
| 	    const std::filesystem::path &vertex_path, |  | ||||||
| 	    const std::filesystem::path &pixel_path |  | ||||||
| 	) |  | ||||||
| 	{ |  | ||||||
| 		instance().load_shader_impl(name, vertex_path, pixel_path); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	static void load_texture(const std::string &name, const std::filesystem::path &path) |  | ||||||
| 	{ |  | ||||||
| 		instance().load_texture_impl(name, path); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	static auto get_shader(const std::string &name) -> Ref<Shader> |  | ||||||
| 	{ |  | ||||||
| 		return instance().m_shaders[name]; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	static auto get_texture(const std::string &name) -> Ref<Texture> |  | ||||||
| 	{ |  | ||||||
| 		return instance().m_textures[name]; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| private: |  | ||||||
| 	AssetManager() = default; |  | ||||||
| 
 |  | ||||||
| 	static auto instance() -> AssetManager &; |  | ||||||
| 
 |  | ||||||
| 	void load_shader_impl( |  | ||||||
| 	    const std::string &name, |  | ||||||
| 	    const std::filesystem::path &vertex_path, |  | ||||||
| 	    const std::filesystem::path &pixel_path |  | ||||||
| 	); |  | ||||||
| 
 |  | ||||||
| 	void load_texture_impl(const std::string &name, const std::filesystem::path &path); |  | ||||||
| 
 |  | ||||||
| 	auto get_or_load_text_asset(const std::filesystem::path &path) -> Ref<Assets::TextAsset>; |  | ||||||
| 
 |  | ||||||
| 	auto get_or_load_texture_asset(const std::filesystem::path &path) -> Ref<Assets::TextureAsset>; |  | ||||||
| 
 |  | ||||||
| 	std::unordered_map<std::string, Ref<Assets::TextAsset>> m_text_assets; |  | ||||||
| 
 |  | ||||||
| 	std::unordered_map<std::string, Ref<Assets::TextureAsset>> m_texture_assets; |  | ||||||
| 
 |  | ||||||
| 	std::unordered_map<std::string, Ref<Shader>> m_shaders; |  | ||||||
| 
 |  | ||||||
| 	std::unordered_map<std::string, Ref<Texture>> m_textures; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| } // namespace lt
 |  | ||||||
|  | @ -1,11 +0,0 @@ | ||||||
| add_library_module(asset_parser  |  | ||||||
|     parser.cpp |  | ||||||
|     assets/texture.cpp |  | ||||||
|     assets/text.cpp |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| target_link_libraries( |  | ||||||
|     asset_parser |  | ||||||
|     PRIVATE LZ4::lz4_static  |  | ||||||
|     PRIVATE logger |  | ||||||
| ) |  | ||||||
|  | @ -1,5 +1,4 @@ | ||||||
| #include <asset_parser/assets/text.hpp> | #include <asset_parser/assets/text.hpp> | ||||||
| #include <lz4.h> |  | ||||||
| 
 | 
 | ||||||
| namespace Assets { | namespace Assets { | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,165 +0,0 @@ | ||||||
| #include <asset_parser/assets/texture.hpp> |  | ||||||
| #include <lz4.h> |  | ||||||
| 
 |  | ||||||
| namespace Assets { |  | ||||||
| 
 |  | ||||||
| /* static */ void TextureAsset::pack(const PackageData &data, const std::filesystem::path &out_path) |  | ||||||
| { |  | ||||||
| 	const auto &[metadata, texture_metadata, pixels] = data; |  | ||||||
| 
 |  | ||||||
| 	auto stream = std::ofstream { out_path, std::ios::binary | std::ios::trunc }; |  | ||||||
| 	if (!stream.is_open()) |  | ||||||
| 	{ |  | ||||||
| 		throw std::runtime_error { |  | ||||||
| 			std::format("Failed to open ofstream for packing texture at: {}", out_path.string()) |  | ||||||
| 		}; |  | ||||||
| 	} |  | ||||||
| 	stream.seekp(0); |  | ||||||
| 
 |  | ||||||
| 	// NOLINTBEGIN(cppcoreguidelines-pro-type-cstyle-cast)
 |  | ||||||
| 	stream.write((char *)¤t_version, sizeof(current_version)); |  | ||||||
| 
 |  | ||||||
| 	stream.write((char *)&metadata, sizeof(metadata)); |  | ||||||
| 	stream.write((char *)&texture_metadata, sizeof(texture_metadata)); |  | ||||||
| 
 |  | ||||||
| 	constexpr auto number_of_blobs = uint32_t { 1 }; |  | ||||||
| 	stream.write((char *)&number_of_blobs, sizeof(number_of_blobs)); |  | ||||||
| 
 |  | ||||||
| 	auto pixels_metadata = BlobMetadata { |  | ||||||
| 		.tag = BlobMetadata::Tag::color, |  | ||||||
| 		.offset = static_cast<size_t>(stream.tellp()) + sizeof(BlobMetadata), |  | ||||||
| 		.compression_type = CompressionType::None, |  | ||||||
| 		.compressed_size = pixels.size(), |  | ||||||
| 		.uncompressed_size = pixels.size(), |  | ||||||
| 	}; |  | ||||||
| 
 |  | ||||||
| 	stream.write((char *)&pixels_metadata, sizeof(pixels_metadata)); |  | ||||||
| 	stream.write((char *)&pixels[0], static_cast<long>(pixels.size())); |  | ||||||
| 	// NOLINTEND(cppcoreguidelines-pro-type-cstyle-cast)
 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| TextureAsset::TextureAsset(const std::filesystem::path &path) |  | ||||||
| { |  | ||||||
| 	m_stream = std::ifstream { path, std::ios::binary }; |  | ||||||
| 	if (!m_stream.is_open()) |  | ||||||
| 	{ |  | ||||||
| 		throw std::runtime_error { |  | ||||||
| 			std::format("Failed to open ifstream for loading texture asset at: {}", path.string()) |  | ||||||
| 		}; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// NOLINTBEGIN(cppcoreguidelines-pro-type-cstyle-cast)
 |  | ||||||
| 	m_stream.read((char *)&version, sizeof(version)); |  | ||||||
| 	m_stream.read((char *)&m_asset_metadata, sizeof(m_asset_metadata)); |  | ||||||
| 	m_stream.read((char *)&m_metadata, sizeof(m_metadata)); |  | ||||||
| 
 |  | ||||||
| 	auto num_blobs = uint32_t {}; |  | ||||||
| 	m_stream.read((char *)&num_blobs, sizeof(num_blobs)); |  | ||||||
| 	if (num_blobs != 1) |  | ||||||
| 	{ |  | ||||||
| 		throw std::runtime_error { |  | ||||||
| 			std::format("Failed to load texture asset: invalid number of blobs: {}", num_blobs) |  | ||||||
| 		}; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	m_stream.read((char *)&m_pixel_blob_metadata, sizeof(m_pixel_blob_metadata)); |  | ||||||
| 	if (m_pixel_blob_metadata.tag != BlobMetadata::Tag::color) |  | ||||||
| 	{ |  | ||||||
| 		throw std::runtime_error { |  | ||||||
| 			std::format( |  | ||||||
| 			    "Failed to load texture asset: invalid blob tag, expected {}, got {}", |  | ||||||
| 			    std::to_underlying(BlobMetadata::Tag::color), |  | ||||||
| 			    std::to_underlying(m_pixel_blob_metadata.tag) |  | ||||||
| 			), |  | ||||||
| 		}; |  | ||||||
| 	} |  | ||||||
| 	// NOLINTEND(cppcoreguidelines-pro-type-cstyle-cast)
 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void TextureAsset::unpack_blob( |  | ||||||
|     BlobMetadata::Tag tag, |  | ||||||
|     std::byte *destination, |  | ||||||
|     size_t destination_capacity |  | ||||||
| ) |  | ||||||
| { |  | ||||||
| 	if (tag != BlobMetadata::Tag::color) |  | ||||||
| 	{ |  | ||||||
| 		throw std::runtime_error { |  | ||||||
| 			std::format("Invalid tag for unpack_blob of TextureAsset: {}", std::to_underlying(tag)) |  | ||||||
| 		}; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	m_stream.seekg(static_cast<long>(m_pixel_blob_metadata.offset)); |  | ||||||
| 	switch (m_pixel_blob_metadata.compression_type) |  | ||||||
| 	{ |  | ||||||
| 	case Assets::CompressionType::None: |  | ||||||
| 		if (m_pixel_blob_metadata.uncompressed_size != m_pixel_blob_metadata.compressed_size) |  | ||||||
| 		{ |  | ||||||
| 			throw std::runtime_error( |  | ||||||
| 			    "Failed to unpack blob from TextureAsset: " |  | ||||||
| 			    "compressed/uncompressed size mismatch for no compression " |  | ||||||
| 			    "type" |  | ||||||
| 			); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if (m_pixel_blob_metadata.uncompressed_size > destination_capacity) |  | ||||||
| 		{ |  | ||||||
| 			throw std::runtime_error( |  | ||||||
| 			    "Failed to unpack blob from TextureAsset: " |  | ||||||
| 			    "uncompressed_size > destination_capacity, unpacking " |  | ||||||
| 			    "would result in segfault" |  | ||||||
| 			); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if (!m_stream.is_open()) |  | ||||||
| 		{ |  | ||||||
| 			throw std::runtime_error( |  | ||||||
| 			    "Failed to unpack blob from TextureAsset: ifstream is " |  | ||||||
| 			    "closed" |  | ||||||
| 			); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		m_stream.read( |  | ||||||
| 		    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
 |  | ||||||
| 		    (char *)destination, |  | ||||||
| 		    static_cast<long>(m_pixel_blob_metadata.uncompressed_size) |  | ||||||
| 		); |  | ||||||
| 
 |  | ||||||
| 		return; |  | ||||||
| 
 |  | ||||||
| 	default: |  | ||||||
| 		throw std::runtime_error( |  | ||||||
| 		    std::format( |  | ||||||
| 		        "Failed to unpack blob from TextureAsset: unsupported " |  | ||||||
| 		        "compression type: {}", |  | ||||||
| 		        std::to_underlying(m_pixel_blob_metadata.compression_type) |  | ||||||
| 		    ) |  | ||||||
| 		); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| [[nodiscard]] auto TextureAsset::get_asset_metadata() const -> const Asset::Metadata & |  | ||||||
| { |  | ||||||
| 	return m_asset_metadata; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| [[nodiscard]] auto TextureAsset::get_metadata() const -> const Metadata & |  | ||||||
| { |  | ||||||
| 	return m_metadata; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| [[nodiscard]] auto TextureAsset::get_blob_metadata(BlobMetadata::Tag tag) const |  | ||||||
|     -> const BlobMetadata & |  | ||||||
| { |  | ||||||
| 	if (tag != BlobMetadata::Tag::color) |  | ||||||
| 	{ |  | ||||||
| 		throw std::runtime_error { std::format( |  | ||||||
| 			"Invalid tag for get_blob_metadata of TextureAsset: {}", |  | ||||||
| 			std::to_underlying(tag) |  | ||||||
| 		) }; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return m_pixel_blob_metadata; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| } // namespace Assets
 |  | ||||||
|  | @ -1,62 +0,0 @@ | ||||||
| #include <asset_parser/parser.hpp> |  | ||||||
| #include <format> |  | ||||||
| #include <fstream> |  | ||||||
| #include <utility> |  | ||||||
| 
 |  | ||||||
| namespace Assets { |  | ||||||
| 
 |  | ||||||
| // void Asset::unpack(std::byte *destination)
 |  | ||||||
| // {
 |  | ||||||
| // 	if (!m_stream.is_open())
 |  | ||||||
| // 	{
 |  | ||||||
| // 		throw std::logic_error {
 |  | ||||||
| // 			"Failed to unpack asset: "
 |  | ||||||
| // 			"ifstream is closed",
 |  | ||||||
| // 		};
 |  | ||||||
| // 	}
 |  | ||||||
| //
 |  | ||||||
| // 	switch (m_metadata.blob_compression_type)
 |  | ||||||
| // 	{
 |  | ||||||
| // 	case CompressionType::None:
 |  | ||||||
| // 		if (m_metadata.packed_size != m_metadata.unpacked_size)
 |  | ||||||
| // 		{
 |  | ||||||
| // 			throw std::logic_error {
 |  | ||||||
| // 				"Failed to unpack asset: "
 |  | ||||||
| // 				"compression type set to none but packed/unpacked sizes differ",
 |  | ||||||
| // 			};
 |  | ||||||
| // 		}
 |  | ||||||
| //
 |  | ||||||
| // 		m_stream.read(
 |  | ||||||
| // 		    std::bit_cast<char *>(destination),
 |  | ||||||
| // 		    static_cast<long>(m_metadata.packed_size)
 |  | ||||||
| // 		);
 |  | ||||||
| // 		m_stream.close();
 |  | ||||||
| //
 |  | ||||||
| // 	case CompressionType::LZ4:
 |  | ||||||
| // 		m_stream.close();
 |  | ||||||
| // 		throw std::logic_error {
 |  | ||||||
| // 			"Failed to unpack asset: "
 |  | ||||||
| // 			"LZ4 compression is not implemented yet",
 |  | ||||||
| // 		};
 |  | ||||||
| //
 |  | ||||||
| //
 |  | ||||||
| // 	case CompressionType::LZ4HC:
 |  | ||||||
| // 		m_stream.close();
 |  | ||||||
| // 		throw std::logic_error {
 |  | ||||||
| // 			"Failed to unpack asset: "
 |  | ||||||
| // 			"LZ4HC compression is not implemented yet",
 |  | ||||||
| // 		};
 |  | ||||||
| //
 |  | ||||||
| // 	default:
 |  | ||||||
| // 		m_stream.close();
 |  | ||||||
| // 		throw std::logic_error {
 |  | ||||||
| // 			std::format(
 |  | ||||||
| // 			    "Failed to unpack asset: "
 |  | ||||||
| // 			    "Compression type was not recognized: {}",
 |  | ||||||
| // 			    std::to_underlying(m_metadata.blob_compression_type)
 |  | ||||||
| // 			),
 |  | ||||||
| // 		};
 |  | ||||||
| // 	}
 |  | ||||||
| // }
 |  | ||||||
| 
 |  | ||||||
| } // namespace Assets
 |  | ||||||
|  | @ -1,58 +0,0 @@ | ||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <asset_parser/compressors/compressors.hpp> |  | ||||||
| #include <asset_parser/parser.hpp> |  | ||||||
| #include <cstdint> |  | ||||||
| #include <filesystem> |  | ||||||
| #include <fstream> |  | ||||||
| #include <logger/logger.hpp> |  | ||||||
| 
 |  | ||||||
| namespace Assets { |  | ||||||
| 
 |  | ||||||
| class TextAsset: public Asset |  | ||||||
| { |  | ||||||
| public: |  | ||||||
| 	struct Metadata |  | ||||||
| 	{ |  | ||||||
| 		uint32_t lines {}; |  | ||||||
| 	}; |  | ||||||
| 
 |  | ||||||
| 	/** Data required to pack a text asset */ |  | ||||||
| 	struct PackageData |  | ||||||
| 	{ |  | ||||||
| 		Asset::Metadata metadata; |  | ||||||
| 
 |  | ||||||
| 		Metadata text_metadata; |  | ||||||
| 
 |  | ||||||
| 		Blob text_blob; |  | ||||||
| 	}; |  | ||||||
| 
 |  | ||||||
| 	static void pack(const PackageData &data, const std::filesystem::path &out_path); |  | ||||||
| 
 |  | ||||||
| 	TextAsset(const std::filesystem::path &path); |  | ||||||
| 
 |  | ||||||
| 	void unpack_blob( |  | ||||||
| 	    BlobMetadata::Tag tag, |  | ||||||
| 	    std::byte *destination, |  | ||||||
| 	    size_t destination_capacity |  | ||||||
| 	) const; |  | ||||||
| 
 |  | ||||||
| 	[[nodiscard]] auto get_asset_metadata() const -> const Asset::Metadata &; |  | ||||||
| 
 |  | ||||||
| 	[[nodiscard]] auto get_metadata() const -> const Metadata &; |  | ||||||
| 
 |  | ||||||
| 	[[nodiscard]] auto get_blob_metadata(BlobMetadata::Tag tag) const -> const BlobMetadata &; |  | ||||||
| 
 |  | ||||||
| private: |  | ||||||
| 	uint32_t version {}; |  | ||||||
| 
 |  | ||||||
| 	Asset::Metadata m_asset_metadata {}; |  | ||||||
| 
 |  | ||||||
| 	Metadata m_metadata {}; |  | ||||||
| 
 |  | ||||||
| 	BlobMetadata m_text_blob_metadata {}; |  | ||||||
| 
 |  | ||||||
| 	mutable std::ifstream m_stream; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| } // namespace Assets
 |  | ||||||
|  | @ -1,64 +0,0 @@ | ||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <asset_parser/compressors/compressors.hpp> |  | ||||||
| #include <asset_parser/parser.hpp> |  | ||||||
| #include <cstdint> |  | ||||||
| #include <filesystem> |  | ||||||
| #include <fstream> |  | ||||||
| #include <logger/logger.hpp> |  | ||||||
| 
 |  | ||||||
| namespace Assets { |  | ||||||
| 
 |  | ||||||
| class TextureAsset: public Asset |  | ||||||
| { |  | ||||||
| public: |  | ||||||
| 	enum class Format : uint32_t // NOLINT(performance-enum-size)
 |  | ||||||
| 	{ |  | ||||||
| 		None = 0, |  | ||||||
| 		RGBA8, |  | ||||||
| 	}; |  | ||||||
| 
 |  | ||||||
| 	struct Metadata |  | ||||||
| 	{ |  | ||||||
| 		Format format; |  | ||||||
| 
 |  | ||||||
| 		uint32_t num_components; |  | ||||||
| 
 |  | ||||||
| 		std::array<uint32_t, 3> pixel_size; |  | ||||||
| 	}; |  | ||||||
| 
 |  | ||||||
| 	/** Data required to pack a texture asset */ |  | ||||||
| 	struct PackageData |  | ||||||
| 	{ |  | ||||||
| 		Asset::Metadata metadata; |  | ||||||
| 
 |  | ||||||
| 		Metadata texture_metadata; |  | ||||||
| 
 |  | ||||||
| 		Blob pixels; |  | ||||||
| 	}; |  | ||||||
| 
 |  | ||||||
| 	static void pack(const PackageData &data, const std::filesystem::path &out_path); |  | ||||||
| 
 |  | ||||||
| 	TextureAsset(const std::filesystem::path &path); |  | ||||||
| 
 |  | ||||||
| 	void unpack_blob(BlobMetadata::Tag tag, std::byte *destination, size_t destination_capacity); |  | ||||||
| 
 |  | ||||||
| 	[[nodiscard]] auto get_asset_metadata() const -> const Asset::Metadata &; |  | ||||||
| 
 |  | ||||||
| 	[[nodiscard]] auto get_metadata() const -> const Metadata &; |  | ||||||
| 
 |  | ||||||
| 	[[nodiscard]] auto get_blob_metadata(BlobMetadata::Tag tag) const -> const BlobMetadata &; |  | ||||||
| 
 |  | ||||||
| private: |  | ||||||
| 	uint32_t version {}; |  | ||||||
| 
 |  | ||||||
| 	Asset::Metadata m_asset_metadata {}; |  | ||||||
| 
 |  | ||||||
| 	Metadata m_metadata {}; |  | ||||||
| 
 |  | ||||||
| 	BlobMetadata m_pixel_blob_metadata {}; |  | ||||||
| 
 |  | ||||||
| 	std::ifstream m_stream; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| } // namespace Assets
 |  | ||||||
|  | @ -1,14 +0,0 @@ | ||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <cstdint> |  | ||||||
| 
 |  | ||||||
| namespace Assets { |  | ||||||
| 
 |  | ||||||
| enum class CompressionType : uint32_t // NOLINT(performance-enum-size)
 |  | ||||||
| { |  | ||||||
| 	None, |  | ||||||
| 	LZ4, |  | ||||||
| 	LZ4HC, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -1,68 +0,0 @@ | ||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <asset_parser/compressors/compressors.hpp> |  | ||||||
| #include <cstdint> |  | ||||||
| #include <filesystem> |  | ||||||
| #include <fstream> |  | ||||||
| #include <logger/logger.hpp> |  | ||||||
| #include <utility> |  | ||||||
| #include <vector> |  | ||||||
| 
 |  | ||||||
| namespace Assets { |  | ||||||
| 
 |  | ||||||
| constexpr auto current_version = uint32_t { 1 }; |  | ||||||
| 
 |  | ||||||
| struct BlobMetadata |  | ||||||
| { |  | ||||||
| 	enum class Tag : uint8_t |  | ||||||
| 	{ |  | ||||||
| 		text, |  | ||||||
| 		color, |  | ||||||
| 		depth, |  | ||||||
| 		vertices, |  | ||||||
| 		indices, |  | ||||||
| 	}; |  | ||||||
| 
 |  | ||||||
| 	Tag tag; |  | ||||||
| 
 |  | ||||||
| 	size_t offset; |  | ||||||
| 
 |  | ||||||
| 	CompressionType compression_type; |  | ||||||
| 
 |  | ||||||
| 	size_t compressed_size; |  | ||||||
| 
 |  | ||||||
| 	size_t uncompressed_size; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| using Blob = std::vector<std::byte>; |  | ||||||
| 
 |  | ||||||
| class Asset |  | ||||||
| { |  | ||||||
| public: |  | ||||||
| 	enum class Type : uint32_t // NOLINT(performance-enum-size)
 |  | ||||||
| 	{ |  | ||||||
| 		None, |  | ||||||
| 		Texture, |  | ||||||
| 		Text, |  | ||||||
| 		Mesh, |  | ||||||
| 		Material, |  | ||||||
| 	}; |  | ||||||
| 
 |  | ||||||
| 	struct Metadata |  | ||||||
| 	{ |  | ||||||
| 		Type type; |  | ||||||
| 	}; |  | ||||||
| 
 |  | ||||||
| 	Asset() = default; |  | ||||||
| 
 |  | ||||||
| 	/** Directly unpacks from disk to the destination.
 |  | ||||||
| 	 * |  | ||||||
| 	 * @note The destination MUST have at least blob_metadata.unpacked_size bytes available for |  | ||||||
| 	 * writing, otherwise segfault could occur! |  | ||||||
| 	 */ |  | ||||||
| 	void unpack_blob(BlobMetadata::Tag blob_tag, std::byte *destination); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| } // namespace Assets
 |  | ||||||
							
								
								
									
										5
									
								
								modules/assets/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								modules/assets/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | ||||||
|  | add_library_module(assets shader.cpp) | ||||||
|  | 
 | ||||||
|  | target_link_libraries(assets PUBLIC logger lt_debug) | ||||||
|  | 
 | ||||||
|  | add_test_module(assets shader.test.cpp) | ||||||
							
								
								
									
										148
									
								
								modules/assets/private/shader.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								modules/assets/private/shader.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,148 @@ | ||||||
|  | #include <assets/shader.hpp> | ||||||
|  | 
 | ||||||
|  | namespace lt::assets { | ||||||
|  | 
 | ||||||
|  | constexpr auto total_metadata_size =         //
 | ||||||
|  |     sizeof(AssetMetadata::type)              //
 | ||||||
|  |     + sizeof(AssetMetadata::version)         //
 | ||||||
|  |     + sizeof(ShaderAsset::Metadata::type)    //
 | ||||||
|  |     + sizeof(BlobMetadata::tag)              //
 | ||||||
|  |     + sizeof(BlobMetadata::offset)           //
 | ||||||
|  |     + sizeof(BlobMetadata::compression_type) //
 | ||||||
|  |     + sizeof(BlobMetadata::compressed_size)  //
 | ||||||
|  |     + sizeof(BlobMetadata::uncompressed_size); | ||||||
|  | 
 | ||||||
|  | ShaderAsset::ShaderAsset(const std::filesystem::path &path): m_stream(path) | ||||||
|  | { | ||||||
|  | 	ensure(m_stream.is_open(), "Failed to open shader asset at: {}", path.string()); | ||||||
|  | 	const auto read = [this](auto &field) { | ||||||
|  | 		m_stream.read(std::bit_cast<char *>(&field), sizeof(field)); | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	m_stream.seekg(0, std::ifstream::end); | ||||||
|  | 	const auto file_size = static_cast<size_t>(m_stream.tellg()); | ||||||
|  | 	ensure( | ||||||
|  | 	    file_size > total_metadata_size, | ||||||
|  | 	    "Failed to open shader asset at: {}, file smaller than metadata: {} < {}", | ||||||
|  | 	    path.string(), | ||||||
|  | 	    total_metadata_size, | ||||||
|  | 	    file_size | ||||||
|  | 	); | ||||||
|  | 
 | ||||||
|  | 	m_stream.seekg(0, std::ifstream::beg); | ||||||
|  | 	read(m_asset_metadata.type); | ||||||
|  | 	read(m_asset_metadata.version); | ||||||
|  | 	read(m_metadata.type); | ||||||
|  | 	read(m_code_blob_metadata.tag); | ||||||
|  | 	read(m_code_blob_metadata.offset); | ||||||
|  | 	read(m_code_blob_metadata.compression_type); | ||||||
|  | 	read(m_code_blob_metadata.compressed_size); | ||||||
|  | 	read(m_code_blob_metadata.uncompressed_size); | ||||||
|  | 
 | ||||||
|  | 	ensure( | ||||||
|  | 	    m_asset_metadata.type == asset_type_identifier, | ||||||
|  | 	    "Failed to open shader asset at: {}, incorrect asset type: {} != {}", | ||||||
|  | 	    path.string(), | ||||||
|  | 	    m_asset_metadata.type, | ||||||
|  | 	    asset_type_identifier | ||||||
|  | 	); | ||||||
|  | 
 | ||||||
|  | 	ensure( | ||||||
|  | 	    m_asset_metadata.version == current_version, | ||||||
|  | 	    "Failed to open shader asset at: {}, version mismatch: {} != {}", | ||||||
|  | 	    path.string(), | ||||||
|  | 	    m_asset_metadata.version, | ||||||
|  | 	    current_version | ||||||
|  | 	); | ||||||
|  | 
 | ||||||
|  | 	ensure( | ||||||
|  | 	    std::to_underlying(m_metadata.type) <= std::to_underlying(Type::compute), | ||||||
|  | 	    "Failed to open shader asset at: {}, invalid shader type: {}", | ||||||
|  | 	    path.string(), | ||||||
|  | 	    std::to_underlying(m_metadata.type) | ||||||
|  | 	); | ||||||
|  | 
 | ||||||
|  | 	ensure( | ||||||
|  | 	    m_code_blob_metadata.tag == std::to_underlying(BlobTag::code), | ||||||
|  | 	    "Failed to open shader asset at: {}, invalid blob tag: {}", | ||||||
|  | 	    path.string(), | ||||||
|  | 	    m_code_blob_metadata.tag | ||||||
|  | 	); | ||||||
|  | 
 | ||||||
|  | 	ensure( | ||||||
|  | 	    m_code_blob_metadata.offset + m_code_blob_metadata.compressed_size <= file_size, | ||||||
|  | 	    "Failed to open shader asset at: {}, file smaller than blob: {} > {} + {}", | ||||||
|  | 	    path.string(), | ||||||
|  | 	    file_size, | ||||||
|  | 	    m_code_blob_metadata.offset, | ||||||
|  | 	    m_code_blob_metadata.compressed_size | ||||||
|  | 	); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* static */ void ShaderAsset::pack( | ||||||
|  |     const std::filesystem::path &destination, | ||||||
|  |     AssetMetadata asset_metadata, | ||||||
|  |     Metadata metadata, | ||||||
|  |     Blob code_blob | ||||||
|  | ) | ||||||
|  | { | ||||||
|  | 	auto stream = std::ofstream { | ||||||
|  | 		destination, | ||||||
|  | 		std::ios::binary | std::ios::trunc, | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	const auto code_blob_metadata = BlobMetadata { | ||||||
|  | 		.tag = std::to_underlying(BlobTag::code), | ||||||
|  | 		.offset = total_metadata_size, | ||||||
|  | 		.compression_type = CompressionType::none, | ||||||
|  | 		.compressed_size = code_blob.size(), | ||||||
|  | 		.uncompressed_size = code_blob.size(), | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	ensure(stream.is_open(), "Failed to pack shader asset to {}", destination.string()); | ||||||
|  | 	const auto write = [&stream](auto &field) { | ||||||
|  | 		stream.write(std::bit_cast<char *>(&field), sizeof(field)); | ||||||
|  | 	}; | ||||||
|  | 	write(asset_metadata.type); | ||||||
|  | 	write(asset_metadata.version); | ||||||
|  | 	write(metadata.type); | ||||||
|  | 	write(code_blob_metadata.tag); | ||||||
|  | 	write(code_blob_metadata.offset); | ||||||
|  | 	write(code_blob_metadata.compression_type); | ||||||
|  | 	write(code_blob_metadata.compressed_size); | ||||||
|  | 	write(code_blob_metadata.uncompressed_size); | ||||||
|  | 	stream.write(std::bit_cast<char *>(code_blob.data()), static_cast<long long>(code_blob.size())); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void ShaderAsset::unpack_to(BlobTag tag, std::span<std::byte> destination) const | ||||||
|  | { | ||||||
|  | 	ensure(tag == BlobTag::code, "Invalid blob tag for shader asset: {}", std::to_underlying(tag)); | ||||||
|  | 
 | ||||||
|  | 	ensure( | ||||||
|  | 	    destination.size() >= m_code_blob_metadata.uncompressed_size, | ||||||
|  | 	    "Failed to unpack shader blob {} to destination ({}) of size {} since it's smaller " | ||||||
|  | 	    "than the blobl's uncompressed size: {}", | ||||||
|  | 	    std::to_underlying(tag), | ||||||
|  | 	    std::bit_cast<size_t>(destination.data()), | ||||||
|  | 	    destination.size(), | ||||||
|  | 	    m_code_blob_metadata.uncompressed_size | ||||||
|  | 	); | ||||||
|  | 
 | ||||||
|  | 	m_stream.seekg(static_cast<long long>(m_code_blob_metadata.offset)); | ||||||
|  | 	m_stream.read( | ||||||
|  | 	    std::bit_cast<char *>(destination.data()), | ||||||
|  | 	    static_cast<long long>(m_code_blob_metadata.uncompressed_size) | ||||||
|  | 	); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | [[nodiscard]] auto ShaderAsset::unpack(BlobTag tag) const -> Blob | ||||||
|  | { | ||||||
|  | 	ensure(tag == BlobTag::code, "Invalid blob tag for shader asset: {}", std::to_underlying(tag)); | ||||||
|  | 
 | ||||||
|  | 	auto blob = Blob(m_code_blob_metadata.uncompressed_size); | ||||||
|  | 	unpack_to(tag, blob); | ||||||
|  | 
 | ||||||
|  | 	return blob; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace lt::assets
 | ||||||
							
								
								
									
										94
									
								
								modules/assets/private/shader.test.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								modules/assets/private/shader.test.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,94 @@ | ||||||
|  | #include <assets/shader.hpp> | ||||||
|  | #include <ranges> | ||||||
|  | #include <test/test.hpp> | ||||||
|  | 
 | ||||||
|  | using ::lt::assets::AssetMetadata; | ||||||
|  | using ::lt::assets::BlobMetadata; | ||||||
|  | using ::lt::assets::ShaderAsset; | ||||||
|  | using ::lt::test::Case; | ||||||
|  | using ::lt::test::expect_eq; | ||||||
|  | using ::lt::test::expect_throw; | ||||||
|  | using ::lt::test::expect_true; | ||||||
|  | using ::lt::test::Suite; | ||||||
|  | 
 | ||||||
|  | const auto test_data_path = std::filesystem::path { "./data/test_assets" }; | ||||||
|  | const auto tmp_path = std::filesystem::path { "/tmp/lt_assets_tests/" }; | ||||||
|  | 
 | ||||||
|  | Suite raii = "shader_raii"_suite = [] { | ||||||
|  | 	std::filesystem::current_path(test_data_path); | ||||||
|  | 	std::filesystem::create_directories(tmp_path); | ||||||
|  | 
 | ||||||
|  | 	Case { "happy path won't throw" } = [] { | ||||||
|  | 
 | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	Case { "many won't freeze/throw" } = [] { | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	Case { "unhappy path throws" } = [] { | ||||||
|  | 		expect_throw([] { ShaderAsset { "random_path" }; }); | ||||||
|  | 	}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | // NOLINTNEXTLINE(cppcoreguidelines-interfaces-global-init)
 | ||||||
|  | Suite packing = "shader_pack"_suite = [] { | ||||||
|  | 	Case { "" } = [] { | ||||||
|  | 		const auto out_path = tmp_path / "shader_packing"; | ||||||
|  | 		auto dummy_blob = lt::assets::Blob {}; | ||||||
|  | 		for (auto idx : std::views::iota(0, 255)) | ||||||
|  | 		{ | ||||||
|  | 			dummy_blob.emplace_back(static_cast<std::byte>(idx)); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		const auto expected_size =                    //
 | ||||||
|  | 		    sizeof(AssetMetadata::type)               //
 | ||||||
|  | 		    + sizeof(AssetMetadata::version)          //
 | ||||||
|  | 		    + sizeof(ShaderAsset::Metadata::type)     //
 | ||||||
|  | 		    + sizeof(BlobMetadata::tag)               //
 | ||||||
|  | 		    + sizeof(BlobMetadata::offset)            //
 | ||||||
|  | 		    + sizeof(BlobMetadata::compression_type)  //
 | ||||||
|  | 		    + sizeof(BlobMetadata::compressed_size)   //
 | ||||||
|  | 		    + sizeof(BlobMetadata::uncompressed_size) //
 | ||||||
|  | 		    + dummy_blob.size(); | ||||||
|  | 
 | ||||||
|  | 		ShaderAsset::pack( | ||||||
|  | 		    out_path, | ||||||
|  | 		    lt::assets::AssetMetadata { | ||||||
|  | 		        .version = lt::assets::current_version, | ||||||
|  | 		        .type = ShaderAsset::asset_type_identifier, | ||||||
|  | 		    }, | ||||||
|  | 		    ShaderAsset::Metadata { | ||||||
|  | 		        .type = ShaderAsset::Type::vertex, | ||||||
|  | 		    }, | ||||||
|  | 		    std::move(dummy_blob) | ||||||
|  | 		); | ||||||
|  | 
 | ||||||
|  | 		auto stream = std::ifstream { | ||||||
|  | 			out_path, | ||||||
|  | 			std::ios::binary, | ||||||
|  | 		}; | ||||||
|  | 		expect_true(stream.is_open()); | ||||||
|  | 
 | ||||||
|  | 		stream.seekg(0, std::ios::end); | ||||||
|  | 		const auto file_size = static_cast<size_t>(stream.tellg()); | ||||||
|  | 		expect_eq(file_size, expected_size); | ||||||
|  | 		stream.close(); | ||||||
|  | 
 | ||||||
|  | 		auto shader_asset = ShaderAsset { out_path }; | ||||||
|  | 
 | ||||||
|  | 		const auto &asset_metadata = shader_asset.get_asset_metadata(); | ||||||
|  | 		expect_eq(asset_metadata.type, ShaderAsset::asset_type_identifier); | ||||||
|  | 		expect_eq(asset_metadata.version, lt::assets::current_version); | ||||||
|  | 
 | ||||||
|  | 		const auto &metadata = shader_asset.get_metadata(); | ||||||
|  | 		expect_eq(metadata.type, ShaderAsset::Type::vertex); | ||||||
|  | 
 | ||||||
|  | 		auto blob = shader_asset.unpack(ShaderAsset::BlobTag::code); | ||||||
|  | 		expect_eq(blob.size(), 255); | ||||||
|  | 
 | ||||||
|  | 		for (auto idx : std::views::iota(0, 255)) | ||||||
|  | 		{ | ||||||
|  | 			expect_eq(blob[idx], static_cast<std::byte>(idx)); | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | }; | ||||||
							
								
								
									
										3
									
								
								modules/assets/public/compressors/lz4.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								modules/assets/public/compressors/lz4.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | // TO BE DOOO
 | ||||||
							
								
								
									
										42
									
								
								modules/assets/public/metadata.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								modules/assets/public/metadata.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,42 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | namespace lt::assets { | ||||||
|  | 
 | ||||||
|  | using Type_T = std::array<const char, 16>; | ||||||
|  | 
 | ||||||
|  | using Tag_T = uint8_t; | ||||||
|  | 
 | ||||||
|  | using Version = uint8_t; | ||||||
|  | 
 | ||||||
|  | using Blob = std::vector<std::byte>; | ||||||
|  | 
 | ||||||
|  | constexpr auto current_version = Version { 1u }; | ||||||
|  | 
 | ||||||
|  | enum class CompressionType : uint8_t | ||||||
|  | { | ||||||
|  | 	none, | ||||||
|  | 	lz4, | ||||||
|  | 	lz4_hc, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | struct AssetMetadata | ||||||
|  | { | ||||||
|  | 	Version version; | ||||||
|  | 
 | ||||||
|  | 	Type_T type; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | struct BlobMetadata | ||||||
|  | { | ||||||
|  | 	Tag_T tag; | ||||||
|  | 
 | ||||||
|  | 	size_t offset; | ||||||
|  | 
 | ||||||
|  | 	CompressionType compression_type; | ||||||
|  | 
 | ||||||
|  | 	size_t compressed_size; | ||||||
|  | 
 | ||||||
|  | 	size_t uncompressed_size; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace lt::assets
 | ||||||
							
								
								
									
										74
									
								
								modules/assets/public/shader.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								modules/assets/public/shader.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,74 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <assets/metadata.hpp> | ||||||
|  | 
 | ||||||
|  | namespace lt::assets { | ||||||
|  | 
 | ||||||
|  | class ShaderAsset | ||||||
|  | { | ||||||
|  | public: | ||||||
|  | 	static constexpr auto asset_type_identifier = Type_T { "SHADER_________" }; | ||||||
|  | 
 | ||||||
|  | 	enum class BlobTag : Tag_T | ||||||
|  | 	{ | ||||||
|  | 		code, | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	enum class Type : uint8_t | ||||||
|  | 	{ | ||||||
|  | 		vertex, | ||||||
|  | 		fragment, | ||||||
|  | 		geometry, | ||||||
|  | 		compute, | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	struct Metadata | ||||||
|  | 	{ | ||||||
|  | 		Type type; | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	static void pack( | ||||||
|  | 	    const std::filesystem::path &destination, | ||||||
|  | 	    AssetMetadata asset_metadata, | ||||||
|  | 	    Metadata metadata, | ||||||
|  | 	    Blob code_blob | ||||||
|  | 	); | ||||||
|  | 
 | ||||||
|  | 	ShaderAsset(const std::filesystem::path &path); | ||||||
|  | 
 | ||||||
|  | 	void unpack_to(BlobTag tag, std::span<std::byte> destination) const; | ||||||
|  | 
 | ||||||
|  | 	[[nodiscard]] auto unpack(BlobTag tag) const -> Blob; | ||||||
|  | 
 | ||||||
|  | 	[[nodiscard]] auto get_asset_metadata() const -> const AssetMetadata & | ||||||
|  | 	{ | ||||||
|  | 		return m_asset_metadata; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	[[nodiscard]] auto get_metadata() const -> const Metadata & | ||||||
|  | 	{ | ||||||
|  | 		return m_metadata; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	[[nodiscard]] auto get_blob_metadata(BlobTag tag) const -> const BlobMetadata & | ||||||
|  | 	{ | ||||||
|  | 		ensure( | ||||||
|  | 		    tag == BlobTag::code, | ||||||
|  | 		    "Invalid blob tag for shader asset: {}", | ||||||
|  | 		    std::to_underlying(tag) | ||||||
|  | 		); | ||||||
|  | 
 | ||||||
|  | 		return m_code_blob_metadata; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  | 	AssetMetadata m_asset_metadata {}; | ||||||
|  | 
 | ||||||
|  | 	Metadata m_metadata {}; | ||||||
|  | 
 | ||||||
|  | 	BlobMetadata m_code_blob_metadata {}; | ||||||
|  | 
 | ||||||
|  | 	mutable std::ifstream m_stream; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace lt::assets
 | ||||||
|  | @ -1,2 +0,0 @@ | ||||||
| add_library_module(base) |  | ||||||
| target_precompile_headers(base INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/private/pch.hpp) |  | ||||||
|  | @ -1,36 +0,0 @@ | ||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <base/base.hpp> |  | ||||||
| 
 |  | ||||||
| /* windows */ |  | ||||||
| #ifdef _WIN32 |  | ||||||
| 	#define NOMINMAX |  | ||||||
| 	#include <Windows.h> |  | ||||||
| 	#undef NOMINMAX |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
| /** Stdlib */ |  | ||||||
| #include <algorithm> |  | ||||||
| #include <array> |  | ||||||
| #include <atomic> |  | ||||||
| #include <bitset> |  | ||||||
| #include <filesystem> |  | ||||||
| #include <fstream> |  | ||||||
| #include <functional> |  | ||||||
| #include <iostream> |  | ||||||
| #include <list> |  | ||||||
| #include <map> |  | ||||||
| #include <math.h> |  | ||||||
| #include <memory> |  | ||||||
| #include <set> |  | ||||||
| #include <span> |  | ||||||
| #include <sstream> |  | ||||||
| #include <string> |  | ||||||
| #include <string_view> |  | ||||||
| #include <thread> |  | ||||||
| #include <time.h> |  | ||||||
| #include <tuple> |  | ||||||
| #include <unordered_map> |  | ||||||
| #include <unordered_set> |  | ||||||
| #include <utility> |  | ||||||
| #include <vector> |  | ||||||
|  | @ -1,91 +0,0 @@ | ||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <memory> |  | ||||||
| 
 |  | ||||||
| namespace lt { |  | ||||||
| 
 |  | ||||||
| // Ref (Ref)
 |  | ||||||
| template<typename t> |  | ||||||
| using Ref = std::shared_ptr<t>; |  | ||||||
| 
 |  | ||||||
| template<typename t, typename... Args> |  | ||||||
| constexpr Ref<t> create_ref(Args &&...args) |  | ||||||
| { |  | ||||||
| 	return std::make_shared<t>(std::forward<Args>(args)...); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| template<typename t> |  | ||||||
| constexpr Ref<t> make_ref(t *rawPointer) |  | ||||||
| { |  | ||||||
| 	return std::shared_ptr<t>(rawPointer); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Scope (std::unique_ptr)
 |  | ||||||
| template<typename t> |  | ||||||
| using Scope = std::unique_ptr<t>; |  | ||||||
| 
 |  | ||||||
| template<typename t, typename... Args> |  | ||||||
| constexpr std::unique_ptr<t> create_scope(Args &&...args) |  | ||||||
| { |  | ||||||
| 	return std::make_unique<t>(std::forward<Args>(args)...); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| template<typename t> |  | ||||||
| constexpr std::unique_ptr<t> make_scope(t *rawPointer) |  | ||||||
| { |  | ||||||
| 	return std::unique_ptr<t>(rawPointer); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| } // namespace lt
 |  | ||||||
| 
 |  | ||||||
| #define lt_win(x) // windows
 |  | ||||||
| #define lt_lin(x) // linux
 |  | ||||||
| #define lt_mac(x) // mac
 |  | ||||||
| 
 |  | ||||||
| enum class Platform : uint8_t |  | ||||||
| { |  | ||||||
| 	windows, |  | ||||||
| 
 |  | ||||||
| 	/** Named like so because "linux" is a built-in identifier. */ |  | ||||||
| 	gnu, |  | ||||||
| 
 |  | ||||||
| 	mac, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| namespace constants { |  | ||||||
| 
 |  | ||||||
| #if defined(LIGHT_PLATFORM_WINDOWS) |  | ||||||
| 	#define lt_win(x) |  | ||||||
| constexpr auto platform = Platform::windows; |  | ||||||
| constexpr auto platform_name = "windows"; |  | ||||||
| 
 |  | ||||||
| 	#undef LIGHT_PLATFORM_WINDOWS |  | ||||||
| 
 |  | ||||||
| #elif defined(LIGHT_PLATFORM_LINUX) |  | ||||||
| 	#define lt_lin(x) x |  | ||||||
| constexpr auto platform = Platform::gnu; |  | ||||||
| constexpr auto platform_name = "linux"; |  | ||||||
| 
 |  | ||||||
| #elif defined(LIGHT_PLATFORM_MAC) |  | ||||||
| 	#define lt_mac(x) x |  | ||||||
| constexpr auto platform = Platform::mac; |  | ||||||
| constexpr auto platform_name = "mac"; |  | ||||||
| 
 |  | ||||||
| #else |  | ||||||
| 	#error "Unsupported platform: Unknown" |  | ||||||
| 
 |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| } // namespace constants
 |  | ||||||
| 
 |  | ||||||
| /* bit-wise */ |  | ||||||
| constexpr auto bit(auto x) |  | ||||||
| { |  | ||||||
| 	return 1 << x; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /* token */ |  | ||||||
| #define lt_pair_token_value_to_name(token) { token, #token } |  | ||||||
| #define lt_pair_token_name_to_value(token) { #token, token } |  | ||||||
| #define lt_token_name(token)               #token |  | ||||||
							
								
								
									
										1
									
								
								modules/bitwise/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								modules/bitwise/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | ||||||
|  | add_library_module(bitwise) | ||||||
							
								
								
									
										13
									
								
								modules/bitwise/public/operations.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								modules/bitwise/public/operations.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <cstdint> | ||||||
|  | 
 | ||||||
|  | namespace lt::bitwise { | ||||||
|  | 
 | ||||||
|  | /* bit-wise */ | ||||||
|  | constexpr auto bit(uint32_t x) -> uint32_t | ||||||
|  | { | ||||||
|  | 	return 1u << x; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace lt::bitwise
 | ||||||
|  | @ -1,3 +1,3 @@ | ||||||
| add_library_module(camera camera.cpp scene.cpp) | add_library_module(camera) | ||||||
| 
 | 
 | ||||||
| target_link_libraries(camera PUBLIC math) | target_link_libraries(camera INTERFACE math) | ||||||
|  |  | ||||||
|  | @ -1,6 +0,0 @@ | ||||||
| #include <camera/camera.hpp> |  | ||||||
| 
 |  | ||||||
| namespace lt { |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -1,83 +0,0 @@ | ||||||
| #include <camera/scene.hpp> |  | ||||||
| #include <math/algebra.hpp> |  | ||||||
| #include <math/trig.hpp> |  | ||||||
| 
 |  | ||||||
| namespace lt { |  | ||||||
| 
 |  | ||||||
| SceneCamera::SceneCamera() |  | ||||||
|     : m_orthographic_specification { .size = 1000.0f, .near_plane = -1.0f, .far_plane = 10000.0f } |  | ||||||
|     , m_perspective_specification { .vertical_fov = math::radians(45.0f), |  | ||||||
| 	                                .near_plane = 0.01f, |  | ||||||
| 	                                .far_plane = 10000.0f } |  | ||||||
|     , m_aspect_ratio(16.0f / 9.0f) |  | ||||||
| 
 |  | ||||||
| { |  | ||||||
| 	calculate_projection(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void SceneCamera::set_viewport_size(unsigned int width, unsigned int height) |  | ||||||
| { |  | ||||||
| 	m_aspect_ratio = static_cast<float>(width) / static_cast<float>(height); |  | ||||||
| 	calculate_projection(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void SceneCamera::set_projection_type(ProjectionType projection_type) |  | ||||||
| { |  | ||||||
| 	m_projection_type = projection_type; |  | ||||||
| 	calculate_projection(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void SceneCamera::set_orthographic_size(float size) |  | ||||||
| { |  | ||||||
| 	m_orthographic_specification.size = size; |  | ||||||
| 	calculate_projection(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void SceneCamera::set_orthographic_far_plane(float far_plane) |  | ||||||
| { |  | ||||||
| 	m_orthographic_specification.far_plane = far_plane; |  | ||||||
| 	calculate_projection(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void SceneCamera::set_orthographic_near_plane(float near_plane) |  | ||||||
| { |  | ||||||
| 	m_orthographic_specification.near_plane = near_plane; |  | ||||||
| 	calculate_projection(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void SceneCamera::set_perspective_vertical_fov(float vertical_fov) |  | ||||||
| { |  | ||||||
| 	m_perspective_specification.vertical_fov = vertical_fov; |  | ||||||
| 	calculate_projection(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void SceneCamera::set_perspective_far_plane(float far_plane) |  | ||||||
| { |  | ||||||
| 	m_perspective_specification.far_plane = far_plane; |  | ||||||
| 	calculate_projection(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void SceneCamera::set_perspective_near_plane(float near_plane) |  | ||||||
| { |  | ||||||
| 	m_perspective_specification.near_plane = near_plane; |  | ||||||
| 	calculate_projection(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void SceneCamera::calculate_projection() |  | ||||||
| { |  | ||||||
| 	// TODO(Light): implement ortho perspective
 |  | ||||||
| 	if (m_projection_type == ProjectionType::Orthographic) |  | ||||||
| 	{ |  | ||||||
| 		// throw std::runtime_error { "ortho perspective not supported yet" };
 |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// defaults to perspective for now...
 |  | ||||||
| 	m_projection = math::perspective( |  | ||||||
| 	    m_perspective_specification.vertical_fov, |  | ||||||
| 	    m_aspect_ratio, |  | ||||||
| 	    m_perspective_specification.near_plane, |  | ||||||
| 	    m_perspective_specification.far_plane |  | ||||||
| 	); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| } // namespace lt
 |  | ||||||
|  | @ -1,35 +0,0 @@ | ||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <math/mat4.hpp> |  | ||||||
| #include <math/vec4.hpp> |  | ||||||
| 
 |  | ||||||
| namespace lt { |  | ||||||
| 
 |  | ||||||
| class Camera |  | ||||||
| { |  | ||||||
| public: |  | ||||||
| 	Camera() = default; |  | ||||||
| 
 |  | ||||||
| 	[[nodiscard]] auto get_projection() const -> const math::mat4 & |  | ||||||
| 	{ |  | ||||||
| 		return m_projection; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	[[nodiscard]] auto get_background_color() const -> const math::vec4 & |  | ||||||
| 	{ |  | ||||||
| 		return m_background_color; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	void set_background_color(const math::vec4 &color) |  | ||||||
| 	{ |  | ||||||
| 		m_background_color = color; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| protected: |  | ||||||
| 	math::mat4 m_projection; |  | ||||||
| 
 |  | ||||||
| private: |  | ||||||
| 	math::vec4 m_background_color = math::vec4(1.0f, 0.0f, 0.0f, 1.0f); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| } // namespace lt
 |  | ||||||
|  | @ -1,29 +0,0 @@ | ||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <camera/scene.hpp> |  | ||||||
| 
 |  | ||||||
| namespace lt { |  | ||||||
| 
 |  | ||||||
| struct CameraComponent |  | ||||||
| { |  | ||||||
| 	CameraComponent() = default; |  | ||||||
| 
 |  | ||||||
| 	CameraComponent(const CameraComponent &) = default; |  | ||||||
| 
 |  | ||||||
| 	CameraComponent(SceneCamera _camera, bool _isPrimary = false) |  | ||||||
| 	    : camera(_camera) |  | ||||||
| 	    , isPrimary(_isPrimary) |  | ||||||
| 	{ |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	operator SceneCamera() const |  | ||||||
| 	{ |  | ||||||
| 		return camera; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	SceneCamera camera; |  | ||||||
| 
 |  | ||||||
| 	bool isPrimary {}; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| } // namespace lt
 |  | ||||||
							
								
								
									
										22
									
								
								modules/camera/public/components.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								modules/camera/public/components.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,22 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <math/mat4.hpp> | ||||||
|  | 
 | ||||||
|  | namespace lt::camera::components { | ||||||
|  | 
 | ||||||
|  | struct PerspectiveCamera | ||||||
|  | { | ||||||
|  | 	float vertical_fov {}; | ||||||
|  | 
 | ||||||
|  | 	float near_plane {}; | ||||||
|  | 
 | ||||||
|  | 	float far_plane {}; | ||||||
|  | 
 | ||||||
|  | 	float aspect_ratio {}; | ||||||
|  | 
 | ||||||
|  | 	math::vec4 background_color; | ||||||
|  | 
 | ||||||
|  | 	bool is_primary {}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace lt::camera::components
 | ||||||
|  | @ -1,100 +0,0 @@ | ||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <camera/camera.hpp> |  | ||||||
| 
 |  | ||||||
| namespace lt { |  | ||||||
| 
 |  | ||||||
| class SceneCamera: public Camera |  | ||||||
| { |  | ||||||
| public: |  | ||||||
| 	enum class ProjectionType |  | ||||||
| 	{ |  | ||||||
| 		Orthographic = 0, |  | ||||||
| 		Perspetcive = 1 |  | ||||||
| 	}; |  | ||||||
| 
 |  | ||||||
| 	struct OrthographicSpecification |  | ||||||
| 	{ |  | ||||||
| 		float size; |  | ||||||
| 
 |  | ||||||
| 		float near_plane; |  | ||||||
| 
 |  | ||||||
| 		float far_plane; |  | ||||||
| 	}; |  | ||||||
| 
 |  | ||||||
| 	struct PerspectiveSpecification |  | ||||||
| 	{ |  | ||||||
| 		float vertical_fov; |  | ||||||
| 
 |  | ||||||
| 		float near_plane; |  | ||||||
| 
 |  | ||||||
| 		float far_plane; |  | ||||||
| 	}; |  | ||||||
| 
 |  | ||||||
| 	SceneCamera(); |  | ||||||
| 
 |  | ||||||
| 	void set_viewport_size(unsigned int width, unsigned int height); |  | ||||||
| 
 |  | ||||||
| 	void set_projection_type(ProjectionType projection_type); |  | ||||||
| 
 |  | ||||||
| 	void set_orthographic_size(float size); |  | ||||||
| 
 |  | ||||||
| 	void set_orthographic_far_plane(float far_plane); |  | ||||||
| 
 |  | ||||||
| 	void set_orthographic_near_plane(float near_plane); |  | ||||||
| 
 |  | ||||||
| 	void set_perspective_vertical_fov(float vertical_fov); |  | ||||||
| 
 |  | ||||||
| 	void set_perspective_far_plane(float far_plane); |  | ||||||
| 
 |  | ||||||
| 	void set_perspective_near_plane(float near_plane); |  | ||||||
| 
 |  | ||||||
| 	[[nodiscard]] auto get_orthographic_size() const -> float |  | ||||||
| 	{ |  | ||||||
| 		return m_orthographic_specification.size; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	[[nodiscard]] auto get_orthographic_far_plane() const -> float |  | ||||||
| 	{ |  | ||||||
| 		return m_orthographic_specification.far_plane; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	[[nodiscard]] auto get_orthographic_near_plane() const -> float |  | ||||||
| 	{ |  | ||||||
| 		return m_orthographic_specification.near_plane; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	[[nodiscard]] auto get_perspective_vertical_fov() const -> float |  | ||||||
| 	{ |  | ||||||
| 		return m_perspective_specification.vertical_fov; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	[[nodiscard]] auto get_perspective_far_plane() const -> float |  | ||||||
| 	{ |  | ||||||
| 		return m_perspective_specification.far_plane; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	[[nodiscard]] auto get_perspective_near_plane() const -> float |  | ||||||
| 	{ |  | ||||||
| 		return m_perspective_specification.near_plane; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	[[nodiscard]] auto get_projection_type() const -> ProjectionType |  | ||||||
| 	{ |  | ||||||
| 		return m_projection_type; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| private: |  | ||||||
| 	OrthographicSpecification m_orthographic_specification; |  | ||||||
| 
 |  | ||||||
| 	PerspectiveSpecification m_perspective_specification; |  | ||||||
| 
 |  | ||||||
| 	float m_aspect_ratio; |  | ||||||
| 
 |  | ||||||
| 	ProjectionType m_projection_type { ProjectionType::Orthographic }; |  | ||||||
| 
 |  | ||||||
| 	void calculate_projection(); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| } // namespace lt
 |  | ||||||
|  | @ -1,3 +1,4 @@ | ||||||
| add_library_module(lt_debug instrumentor.cpp) | add_library_module(lt_debug instrumentor.cpp) | ||||||
| target_link_libraries(lt_debug PUBLIC logger) | target_link_libraries(lt_debug PUBLIC logger) | ||||||
| target_precompile_headers(lt_debug PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/private/pch.hpp) | target_precompile_headers(lt_debug PUBLIC | ||||||
|  |                           ${CMAKE_CURRENT_SOURCE_DIR}/private/pch.hpp) | ||||||
|  |  | ||||||
|  | @ -10,7 +10,7 @@ template<typename Expression_T, typename... Args_T> | ||||||
| struct ensure | struct ensure | ||||||
| { | { | ||||||
| 	ensure( | 	ensure( | ||||||
| 	    Expression_T expression, | 	    const Expression_T &expression, | ||||||
| 	    std::format_string<Args_T...> fmt, | 	    std::format_string<Args_T...> fmt, | ||||||
| 	    Args_T &&...args, | 	    Args_T &&...args, | ||||||
| 	    const std::source_location &location = std::source_location::current() | 	    const std::source_location &location = std::source_location::current() | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| add_library_module(ecs entity.cpp scene.cpp uuid.cpp ) | add_library_module(ecs sparse_set.cpp) | ||||||
| target_link_libraries(ecs  | target_link_libraries(ecs PUBLIC logger lt_debug memory) | ||||||
|     PUBLIC logger lt_debug EnTT::EnTT camera math | 
 | ||||||
| ) | add_test_module(ecs sparse_set.test.cpp registry.test.cpp) | ||||||
|  |  | ||||||
|  | @ -1,10 +0,0 @@ | ||||||
| #include <ecs/entity.hpp> |  | ||||||
| #include <ecs/scene.hpp> |  | ||||||
| 
 |  | ||||||
| namespace lt { |  | ||||||
| 
 |  | ||||||
| Entity::Entity(entt::entity handle, Scene *scene): m_handle(handle), m_scene(scene) |  | ||||||
| { |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| } // namespace lt
 |  | ||||||
							
								
								
									
										351
									
								
								modules/ecs/private/registry.test.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										351
									
								
								modules/ecs/private/registry.test.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,351 @@ | ||||||
|  | #include <ecs/registry.hpp> | ||||||
|  | #include <ranges> | ||||||
|  | #include <test/expects.hpp> | ||||||
|  | #include <test/test.hpp> | ||||||
|  | 
 | ||||||
|  | using lt::test::Case; | ||||||
|  | using lt::test::expect_unreachable; | ||||||
|  | using lt::test::Suite; | ||||||
|  | 
 | ||||||
|  | using lt::test::expect_eq; | ||||||
|  | 
 | ||||||
|  | using lt::test::expect_false; | ||||||
|  | using lt::test::expect_true; | ||||||
|  | 
 | ||||||
|  | using lt::ecs::EntityId; | ||||||
|  | using lt::ecs::Registry; | ||||||
|  | 
 | ||||||
|  | struct Component | ||||||
|  | { | ||||||
|  | 	int m_int {}; | ||||||
|  | 	std::string m_string; | ||||||
|  | 
 | ||||||
|  | 	[[nodiscard]] friend auto operator==(const Component &lhs, const Component &rhs) -> bool | ||||||
|  | 	{ | ||||||
|  | 		return lhs.m_int == rhs.m_int && lhs.m_string == rhs.m_string; | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  | template<> | ||||||
|  | struct std::formatter<Component> | ||||||
|  | { | ||||||
|  | 	constexpr auto parse(std::format_parse_context &context) | ||||||
|  | 	{ | ||||||
|  | 		return context.begin(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	auto format(const Component &val, std::format_context &context) const | ||||||
|  | 	{ | ||||||
|  | 		return std::format_to(context.out(), "{}, {}", val.m_int, val.m_string); | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | struct Component_B | ||||||
|  | { | ||||||
|  | 	float m_float {}; | ||||||
|  | 
 | ||||||
|  | 	[[nodiscard]] friend auto operator==(const Component_B lhs, const Component_B &rhs) -> bool | ||||||
|  | 	{ | ||||||
|  | 		return lhs.m_float == rhs.m_float; | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  | template<> | ||||||
|  | struct std::formatter<Component_B> | ||||||
|  | { | ||||||
|  | 	constexpr auto parse(std::format_parse_context &context) | ||||||
|  | 	{ | ||||||
|  | 		return context.begin(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	auto format(const Component_B &val, std::format_context &context) const | ||||||
|  | 	{ | ||||||
|  | 		return std::format_to(context.out(), "{}", val.m_float); | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | Suite raii = "raii"_suite = [] { | ||||||
|  | 	Case { "happy path won't throw" } = [] { | ||||||
|  | 		std::ignore = Registry {}; | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	Case { "many won't freeze/throw" } = [] { | ||||||
|  | 		for (auto idx : std::views::iota(0, 100'000)) | ||||||
|  | 		{ | ||||||
|  | 			std::ignore = Registry {}; | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	Case { "unhappy path throws" } = [] { | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	Case { "post construct has correct state" } = [] { | ||||||
|  | 		auto registry = Registry {}; | ||||||
|  | 		expect_eq(registry.get_entity_count(), 0); | ||||||
|  | 	}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | Suite entity_raii = "entity_raii"_suite = [] { | ||||||
|  | 	Case { "create_entity returns unique values" } = [] { | ||||||
|  | 		auto registry = Registry {}; | ||||||
|  | 		auto set = std::unordered_set<EntityId> {}; | ||||||
|  | 
 | ||||||
|  | 		for (auto idx : std::views::iota(0, 10'000)) | ||||||
|  | 		{ | ||||||
|  | 			auto entity = registry.create_entity(); | ||||||
|  | 			expect_false(set.contains(entity)); | ||||||
|  | 
 | ||||||
|  | 			set.insert(entity); | ||||||
|  | 			expect_eq(set.size(), idx + 1); | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	Case { "post create/destroy_entity has correct state" } = [] { | ||||||
|  | 		auto registry = Registry {}; | ||||||
|  | 
 | ||||||
|  | 		auto entities = std::vector<EntityId> {}; | ||||||
|  | 		for (auto idx : std::views::iota(0, 10'000)) | ||||||
|  | 		{ | ||||||
|  | 			entities.emplace_back(registry.create_entity()); | ||||||
|  | 			expect_eq(registry.get_entity_count(), idx + 1); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		for (auto idx : std::views::iota(0, 10'000)) | ||||||
|  | 		{ | ||||||
|  | 			auto entity = entities.back(); | ||||||
|  | 			registry.destroy_entity(entity); | ||||||
|  | 
 | ||||||
|  | 			entities.pop_back(); | ||||||
|  | 			expect_eq(registry.get_entity_count(), 10'000 - (idx + 1)); | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | Suite component_raii = "component_raii"_suite = [] { | ||||||
|  | 	Case { "add has correct state" } = [] { | ||||||
|  | 		auto registry = Registry {}; | ||||||
|  | 		for (auto idx : std::views::iota(0, 100'000)) | ||||||
|  | 		{ | ||||||
|  | 			auto entity = registry.create_entity(); | ||||||
|  | 			auto &component = registry.add<Component>( | ||||||
|  | 			    entity, | ||||||
|  | 			    { .m_int = idx, .m_string = std::to_string(idx) } | ||||||
|  | 			); | ||||||
|  | 
 | ||||||
|  | 			expect_eq(component.m_int, idx); | ||||||
|  | 			expect_eq(component.m_string, std::to_string(idx)); | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	Case { "remove has correct state" } = [] { | ||||||
|  | 		auto registry = Registry {}; | ||||||
|  | 		for (auto idx : std::views::iota(0, 100'000)) | ||||||
|  | 		{ | ||||||
|  | 			auto entity = registry.create_entity(); | ||||||
|  | 			auto &component = registry.add<Component>( | ||||||
|  | 			    entity, | ||||||
|  | 			    { .m_int = idx, .m_string = std::to_string(idx) } | ||||||
|  | 			); | ||||||
|  | 
 | ||||||
|  | 			expect_eq(component.m_int, idx); | ||||||
|  | 			expect_eq(component.m_string, std::to_string(idx)); | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | Suite callbacks = "callbacks"_suite = [] { | ||||||
|  | 	Case { "connecting on_construct/on_destruct won't throw" } = [] { | ||||||
|  | 		auto registry = Registry {}; | ||||||
|  | 		registry.connect_on_construct<Component>([&](Registry &, EntityId) {}); | ||||||
|  | 		registry.connect_on_destruct<Component>([&](Registry &, EntityId) {}); | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	Case { "on_construct/on_destruct won't get called on unrelated component" } = [] { | ||||||
|  | 		auto registry = Registry {}; | ||||||
|  | 		registry.connect_on_construct<Component>([&](Registry &, EntityId) { | ||||||
|  | 			expect_unreachable(); | ||||||
|  | 		}); | ||||||
|  | 		registry.connect_on_destruct<Component>([&](Registry &, EntityId) { | ||||||
|  | 			expect_unreachable(); | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 		for (auto idx : std::views::iota(0, 100'000)) | ||||||
|  | 		{ | ||||||
|  | 			registry.add<Component_B>(registry.create_entity(), {}); | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	Case { "on_construct/on_destruct gets called" } = [] { | ||||||
|  | 		auto registry = Registry {}; | ||||||
|  | 		auto all_entities = std::vector<EntityId> {}; | ||||||
|  | 		auto on_construct_called = std::vector<EntityId> {}; | ||||||
|  | 		auto on_destruct_called = std::vector<EntityId> {}; | ||||||
|  | 
 | ||||||
|  | 		registry.connect_on_construct<Component>([&](Registry &, EntityId entity) { | ||||||
|  | 			on_construct_called.emplace_back(entity); | ||||||
|  | 		}); | ||||||
|  | 		registry.connect_on_destruct<Component>([&](Registry &, EntityId entity) { | ||||||
|  | 			on_destruct_called.emplace_back(entity); | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 		expect_true(on_construct_called.empty()); | ||||||
|  | 		expect_true(on_destruct_called.empty()); | ||||||
|  | 		for (auto idx : std::views::iota(0, 100'000)) | ||||||
|  | 		{ | ||||||
|  | 			auto entity = all_entities.emplace_back(registry.create_entity()); | ||||||
|  | 			registry.add<Component>(entity, {}); | ||||||
|  | 		} | ||||||
|  | 		expect_eq(on_construct_called, all_entities); | ||||||
|  | 		expect_true(on_destruct_called.empty()); | ||||||
|  | 
 | ||||||
|  | 		for (auto &entity : all_entities) | ||||||
|  | 		{ | ||||||
|  | 			registry.remove<Component>(entity); | ||||||
|  | 		} | ||||||
|  | 		expect_eq(on_construct_called, all_entities); | ||||||
|  | 		expect_eq(on_destruct_called, all_entities); | ||||||
|  | 	}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | Suite each = "each"_suite = [] { | ||||||
|  | 	auto registry = Registry {}; | ||||||
|  | 
 | ||||||
|  | 	auto shared_entity_counter = 0u; | ||||||
|  | 
 | ||||||
|  | 	auto component_map_a = std::unordered_map<EntityId, Component> {}; | ||||||
|  | 	auto entities_a = std::vector<EntityId> {}; | ||||||
|  | 
 | ||||||
|  | 	for (auto idx : std::views::iota(0, 10'000)) | ||||||
|  | 	{ | ||||||
|  | 		auto entity = entities_a.emplace_back(registry.create_entity()); | ||||||
|  | 		auto &component = registry.add<Component>( | ||||||
|  | 		    entity, | ||||||
|  | 		    { .m_int = idx, .m_string = std::to_string(idx) } | ||||||
|  | 		); | ||||||
|  | 
 | ||||||
|  | 		component_map_a[entity] = component; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	auto component_map_b = std::unordered_map<lt::ecs::EntityId, Component_B> {}; | ||||||
|  | 	for (auto idx : std::views::iota(0, 10'000)) | ||||||
|  | 	{ | ||||||
|  | 		auto entity = EntityId {}; | ||||||
|  | 		if (idx % 3 == 0) | ||||||
|  | 		{ | ||||||
|  | 			entity = entities_a[idx]; | ||||||
|  | 			++shared_entity_counter; | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			entity = registry.create_entity(); | ||||||
|  | 		} | ||||||
|  | 		auto &component = registry.add<Component_B>( | ||||||
|  | 		    entity, | ||||||
|  | 		    { .m_float = static_cast<float>(idx) / 2.0f } | ||||||
|  | 		); | ||||||
|  | 
 | ||||||
|  | 		component_map_b[entity] = component; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	Case { "each one element" } = [&] { | ||||||
|  | 		auto counter = 0u; | ||||||
|  | 		registry.each<Component>([&](EntityId entity, Component &component) { | ||||||
|  | 			++counter; | ||||||
|  | 			expect_eq(component_map_a[entity], component); | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 		expect_eq(component_map_a.size(), counter); | ||||||
|  | 
 | ||||||
|  | 		counter = 0u; | ||||||
|  | 		registry.each<Component_B>([&](EntityId entity, Component_B &component) { | ||||||
|  | 			++counter; | ||||||
|  | 			expect_eq(component_map_b[entity], component); | ||||||
|  | 		}); | ||||||
|  | 		expect_eq(component_map_b.size(), counter); | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	Case { "each two element" } = [&] { | ||||||
|  | 		auto counter = 0u; | ||||||
|  | 		registry.each<Component, Component_B>( | ||||||
|  | 		    [&](EntityId entity, Component &component_a, Component_B &component_b) { | ||||||
|  | 			    expect_eq(component_map_a[entity], component_a); | ||||||
|  | 			    expect_eq(component_map_b[entity], component_b); | ||||||
|  | 			    ++counter; | ||||||
|  | 		    } | ||||||
|  | 		); | ||||||
|  | 
 | ||||||
|  | 		expect_eq(counter, shared_entity_counter); | ||||||
|  | 	}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | Suite views = "views"_suite = [] { | ||||||
|  | 	auto registry = Registry {}; | ||||||
|  | 
 | ||||||
|  | 	auto shared_entity_counter = 0u; | ||||||
|  | 
 | ||||||
|  | 	auto component_map_a = std::unordered_map<EntityId, Component> {}; | ||||||
|  | 	auto entities_a = std::vector<EntityId> {}; | ||||||
|  | 
 | ||||||
|  | 	for (auto idx : std::views::iota(0, 10'000)) | ||||||
|  | 	{ | ||||||
|  | 		auto entity = entities_a.emplace_back(registry.create_entity()); | ||||||
|  | 		auto &component = registry.add<Component>( | ||||||
|  | 		    entity, | ||||||
|  | 		    { .m_int = idx, .m_string = std::to_string(idx) } | ||||||
|  | 		); | ||||||
|  | 
 | ||||||
|  | 		component_map_a[entity] = component; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	auto component_map_b = std::unordered_map<EntityId, Component_B> {}; | ||||||
|  | 	for (auto idx : std::views::iota(0, 10'000)) | ||||||
|  | 	{ | ||||||
|  | 		auto entity = EntityId {}; | ||||||
|  | 		if (idx % 3 == 0) | ||||||
|  | 		{ | ||||||
|  | 			entity = entities_a[idx]; | ||||||
|  | 			++shared_entity_counter; | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			entity = registry.create_entity(); | ||||||
|  | 		} | ||||||
|  | 		auto &component = registry.add<Component_B>( | ||||||
|  | 		    entity, | ||||||
|  | 		    { .m_float = static_cast<float>(idx) / 2.0f } | ||||||
|  | 		); | ||||||
|  | 
 | ||||||
|  | 		component_map_b[entity] = component; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 	Case { "view one component" } = [&] { | ||||||
|  | 		for (const auto &[entity, component] : registry.view<Component>()) | ||||||
|  | 		{ | ||||||
|  | 			expect_eq(component_map_a[entity], component); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		for (const auto &[entity, component] : registry.view<Component_B>()) | ||||||
|  | 		{ | ||||||
|  | 			expect_eq(component_map_b[entity], component); | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	Case { "view two component" } = [&] { | ||||||
|  | 		auto counter = 0u; | ||||||
|  | 		for (const auto &[entity, component, component_b] : registry.view<Component, Component_B>()) | ||||||
|  | 		{ | ||||||
|  | 			expect_eq(component_map_a[entity], component); | ||||||
|  | 			expect_eq(component_map_b[entity], component_b); | ||||||
|  | 			++counter; | ||||||
|  | 		} | ||||||
|  | 		expect_eq(counter, shared_entity_counter); | ||||||
|  | 
 | ||||||
|  | 		counter = 0u; | ||||||
|  | 		for (const auto &[entity, component_b, component] : registry.view<Component_B, Component>()) | ||||||
|  | 		{ | ||||||
|  | 			expect_eq(component_map_b[entity], component_b); | ||||||
|  | 			expect_eq(component_map_a[entity], component); | ||||||
|  | 			++counter; | ||||||
|  | 		} | ||||||
|  | 		expect_eq(counter, shared_entity_counter); | ||||||
|  | 	}; | ||||||
|  | }; | ||||||
|  | @ -1,31 +0,0 @@ | ||||||
| #include <ecs/components.hpp> |  | ||||||
| #include <ecs/entity.hpp> |  | ||||||
| #include <ecs/scene.hpp> |  | ||||||
| 
 |  | ||||||
| namespace lt { |  | ||||||
| 
 |  | ||||||
| auto Scene::create_entity(const std::string &name, const TransformComponent &transform) -> Entity |  | ||||||
| { |  | ||||||
| 	return create_entity_with_uuid(name, UUID(), transform); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| auto Scene::get_entity_by_tag(const std::string &tag) -> Entity |  | ||||||
| { |  | ||||||
| 	return {}; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| auto Scene::create_entity_with_uuid( |  | ||||||
|     const std::string &name, |  | ||||||
|     UUID uuid, |  | ||||||
|     const TransformComponent &transform |  | ||||||
| ) -> Entity |  | ||||||
| { |  | ||||||
| 	auto entity = Entity { m_registry.create(), this }; |  | ||||||
| 	entity.add_component<TagComponent>(name); |  | ||||||
| 	entity.add_component<TransformComponent>(transform); |  | ||||||
| 	entity.add_component<UUIDComponent>(uuid); |  | ||||||
| 
 |  | ||||||
| 	return entity; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| } // namespace lt
 |  | ||||||
|  | @ -1,325 +0,0 @@ | ||||||
| #include <asset_manager/asset_manager.hpp> |  | ||||||
| #include <camera/component.hpp> |  | ||||||
| #include <ecs/components.hpp> |  | ||||||
| #include <ecs/serializer.hpp> |  | ||||||
| #include <math/vec3.hpp> |  | ||||||
| #include <math/vec4.hpp> |  | ||||||
| #include <yaml-cpp/yaml.h> |  | ||||||
| 
 |  | ||||||
| namespace YAML { |  | ||||||
| 
 |  | ||||||
| template<> |  | ||||||
| struct convert<lt::math::vec3> |  | ||||||
| { |  | ||||||
| 	static auto encode(const lt::math::vec3 &rhs) -> Node |  | ||||||
| 	{ |  | ||||||
| 		auto node = Node {}; |  | ||||||
| 		node.push_back(rhs.x); |  | ||||||
| 		node.push_back(rhs.y); |  | ||||||
| 		node.push_back(rhs.z); |  | ||||||
| 		return node; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	static auto decode(const Node &node, lt::math::vec3 &rhs) -> bool |  | ||||||
| 	{ |  | ||||||
| 		if (!node.IsSequence() || node.size() != 3) |  | ||||||
| 		{ |  | ||||||
| 			return false; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		rhs.x = node[0].as<float>(); |  | ||||||
| 		rhs.y = node[1].as<float>(); |  | ||||||
| 		rhs.z = node[2].as<float>(); |  | ||||||
| 		return true; |  | ||||||
| 	} |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| template<> |  | ||||||
| struct convert<lt::math::vec4> |  | ||||||
| { |  | ||||||
| 	static auto encode(const lt::math::vec4 &rhs) -> Node |  | ||||||
| 	{ |  | ||||||
| 		auto node = Node {}; |  | ||||||
| 		node.push_back(rhs.x); |  | ||||||
| 		node.push_back(rhs.y); |  | ||||||
| 		node.push_back(rhs.z); |  | ||||||
| 		node.push_back(rhs.w); |  | ||||||
| 		return node; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	static auto decode(const Node &node, lt::math::vec4 &rhs) -> bool |  | ||||||
| 	{ |  | ||||||
| 		if (!node.IsSequence() || node.size() != 4) |  | ||||||
| 		{ |  | ||||||
| 			return false; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		rhs.x = node[0].as<float>(); |  | ||||||
| 		rhs.y = node[1].as<float>(); |  | ||||||
| 		rhs.z = node[2].as<float>(); |  | ||||||
| 		rhs.w = node[3].as<float>(); |  | ||||||
| 		return true; |  | ||||||
| 	} |  | ||||||
| }; |  | ||||||
| } // namespace YAML
 |  | ||||||
| 
 |  | ||||||
| namespace lt { |  | ||||||
| 
 |  | ||||||
| auto operator<<(YAML::Emitter &out, const math::vec3 &v) -> YAML::Emitter & |  | ||||||
| { |  | ||||||
| 	out << YAML::Flow; |  | ||||||
| 	out << YAML::BeginSeq << v.x << v.y << v.z << YAML::EndSeq; |  | ||||||
| 	return out; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| auto operator<<(YAML::Emitter &out, const math::vec4 &v) -> YAML::Emitter & |  | ||||||
| { |  | ||||||
| 	out << YAML::Flow; |  | ||||||
| 	out << YAML::BeginSeq << v.x << v.y << v.z << v.w << YAML::EndSeq; |  | ||||||
| 	return out; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| SceneSerializer::SceneSerializer(const Ref<Scene> &scene): m_scene(scene) |  | ||||||
| { |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void SceneSerializer::serialize(const std::string &filePath) |  | ||||||
| { |  | ||||||
| 	auto out = YAML::Emitter {}; |  | ||||||
| 	out << YAML::BeginMap; // Scene
 |  | ||||||
| 	out << YAML::Key << "Scene" << YAML::Value << "Untitled"; |  | ||||||
| 
 |  | ||||||
| 	out << YAML::Key << "Entities" << YAML::Value << YAML::BeginSeq; |  | ||||||
| 	for (auto [entityID, storage] : m_scene->m_registry.storage()) |  | ||||||
| 	{ |  | ||||||
| 		auto entity = Entity { static_cast<entt::entity>(entityID), m_scene.get() }; |  | ||||||
| 		if (!entity.is_valid()) |  | ||||||
| 		{ |  | ||||||
| 			return; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		serialize_entity(out, entity); |  | ||||||
| 	}; |  | ||||||
| 	out << YAML::EndSeq; |  | ||||||
| 	out << YAML::EndMap; |  | ||||||
| 
 |  | ||||||
| 	std::filesystem::create_directories(filePath.substr(0ull, filePath.find_last_of('\\'))); |  | ||||||
| 
 |  | ||||||
| 	auto fout = std::ofstream { filePath }; |  | ||||||
| 	if (!fout.is_open()) |  | ||||||
| 	{ |  | ||||||
| 		log_trc("Failed to create fout at: {}", filePath); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	fout << out.c_str(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| auto SceneSerializer::deserialize(const std::string &file_path) -> bool |  | ||||||
| { |  | ||||||
| 	auto stream = std::ifstream { file_path }; |  | ||||||
| 	auto ss = std::stringstream {}; |  | ||||||
| 	ss << stream.rdbuf(); |  | ||||||
| 
 |  | ||||||
| 	auto data = YAML::Load(ss.str()); |  | ||||||
| 	if (!data["Scene"]) |  | ||||||
| 	{ |  | ||||||
| 		return false; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	auto sceneName = data["Scene"].as<std::string>(); |  | ||||||
| 	log_trc("Deserializing scene: '{}'", sceneName); |  | ||||||
| 
 |  | ||||||
| 	auto entities = data["Entities"]; |  | ||||||
| 	if (entities) |  | ||||||
| 	{ |  | ||||||
| 		auto texturePaths = std::unordered_set<std::string> {}; |  | ||||||
| 
 |  | ||||||
| 		for (auto entity : entities) |  | ||||||
| 		{ |  | ||||||
| 			auto uuid = entity["entity"].as<uint64_t>(); // #todo
 |  | ||||||
| 
 |  | ||||||
| 			auto name = std::string {}; |  | ||||||
| 			auto tagComponent = entity["TagComponent"]; |  | ||||||
| 			if (tagComponent) |  | ||||||
| 			{ |  | ||||||
| 				name = tagComponent["Tag"].as<std::string>(); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			log_trc("Deserialized entity '{}' : '{}'", uuid, name); |  | ||||||
| 
 |  | ||||||
| 			auto deserializedEntity = m_scene->create_entity_with_uuid(name, uuid); |  | ||||||
| 
 |  | ||||||
| 			auto gg = deserializedEntity.get_component<TagComponent>(); |  | ||||||
| 			log_trc("tag: {}", gg.tag); |  | ||||||
| 			auto transformComponent = entity["TransformComponent"]; |  | ||||||
| 			if (transformComponent) |  | ||||||
| 			{ |  | ||||||
| 				auto &entityTransforomComponent = deserializedEntity |  | ||||||
| 				                                      .get_component<TransformComponent>(); |  | ||||||
| 
 |  | ||||||
| 				entityTransforomComponent.translation = transformComponent["Translation"] |  | ||||||
| 				                                            .as<math::vec3>(); |  | ||||||
| 				entityTransforomComponent.rotation = transformComponent["Rotation"] |  | ||||||
| 				                                         .as<math::vec3>(); |  | ||||||
| 				entityTransforomComponent.scale = transformComponent["Scale"].as<math::vec3>(); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			/* #TEMPORARY SOLUTION# */ |  | ||||||
| 			auto spriteRendererComponent = entity["SpriteRendererComponent"]; |  | ||||||
| 			if (spriteRendererComponent) |  | ||||||
| 			{ |  | ||||||
| 				auto &entitySpriteRendererComponent = deserializedEntity |  | ||||||
| 				                                          .add_component<SpriteRendererComponent>(); |  | ||||||
| 				entitySpriteRendererComponent.tint = spriteRendererComponent["Tint"] |  | ||||||
| 				                                         .as<math::vec4>(); |  | ||||||
| 
 |  | ||||||
| 				auto texturePath = spriteRendererComponent["Texture"].as<std::string>(); |  | ||||||
| 
 |  | ||||||
| 				if (!texturePaths.contains(texturePath)) |  | ||||||
| 				{ |  | ||||||
| 					AssetManager::load_texture(texturePath, texturePath); |  | ||||||
| 					texturePaths.insert(texturePath); |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				entitySpriteRendererComponent.texture = AssetManager::get_texture(texturePath); |  | ||||||
| 			} |  | ||||||
| 			/* #TEMPORARY SOLUTION# */ |  | ||||||
| 
 |  | ||||||
| 			auto cameraComponent = entity["CameraComponent"]; |  | ||||||
| 			if (cameraComponent) |  | ||||||
| 			{ |  | ||||||
| 				auto &entityCameraComponent = deserializedEntity.add_component<CameraComponent>(); |  | ||||||
| 
 |  | ||||||
| 				const auto &cameraSpecifications = cameraComponent["Camera"]; |  | ||||||
| 				entityCameraComponent.camera.set_projection_type( |  | ||||||
| 				    (SceneCamera::ProjectionType)cameraSpecifications["ProjectionType"].as<int>() |  | ||||||
| 				); |  | ||||||
| 
 |  | ||||||
| 				entityCameraComponent.camera.set_orthographic_size( |  | ||||||
| 				    cameraSpecifications["OrthographicSize"].as<float>() |  | ||||||
| 				); |  | ||||||
| 				entityCameraComponent.camera.set_orthographic_near_plane( |  | ||||||
| 				    cameraSpecifications["OrthographicNearPlane"].as<float>() |  | ||||||
| 				); |  | ||||||
| 				entityCameraComponent.camera.set_orthographic_far_plane( |  | ||||||
| 				    cameraSpecifications["OrthographicFarPlane"].as<float>() |  | ||||||
| 				); |  | ||||||
| 
 |  | ||||||
| 				entityCameraComponent.camera.set_perspective_vertical_fov( |  | ||||||
| 				    cameraSpecifications["PerspectiveVerticalFOV"].as<float>() |  | ||||||
| 				); |  | ||||||
| 				entityCameraComponent.camera.set_perspective_near_plane( |  | ||||||
| 				    cameraSpecifications["PerspectiveNearPlane"].as<float>() |  | ||||||
| 				); |  | ||||||
| 				entityCameraComponent.camera.set_perspective_far_plane( |  | ||||||
| 				    cameraSpecifications["PerspectiveFarPlane"].as<float>() |  | ||||||
| 				); |  | ||||||
| 
 |  | ||||||
| 				entityCameraComponent.camera.set_background_color( |  | ||||||
| 				    cameraSpecifications["BackgroundColor"].as<math::vec4>() |  | ||||||
| 				); |  | ||||||
| 
 |  | ||||||
| 				entityCameraComponent.isPrimary = cameraComponent["IsPrimary"].as<bool>(); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		return true; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return false; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void SceneSerializer::serialize_binary(const std::string & /*filePath*/) |  | ||||||
| { |  | ||||||
| 	log_err("NO_IMPLEMENT"); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| auto SceneSerializer::deserialize_binary(const std::string & /*filePath*/) -> bool |  | ||||||
| { |  | ||||||
| 	log_err("NO_IMPLEMENT"); |  | ||||||
| 	return false; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void SceneSerializer::serialize_entity(YAML::Emitter &out, Entity entity) |  | ||||||
| { |  | ||||||
| 	out << YAML::BeginMap;                                            // entity
 |  | ||||||
| 	out << YAML::Key << "entity" << YAML::Value << entity.get_uuid(); // dummy uuid
 |  | ||||||
| 
 |  | ||||||
| 	if (entity.has_component<TagComponent>()) |  | ||||||
| 	{ |  | ||||||
| 		out << YAML::Key << "TagComponent"; |  | ||||||
| 		out << YAML::BeginMap; // tag component
 |  | ||||||
| 
 |  | ||||||
| 		auto &tagComponent = entity.get_component<TagComponent>().tag; |  | ||||||
| 		out << YAML::Key << "Tag" << YAML::Value << tagComponent; |  | ||||||
| 
 |  | ||||||
| 		out << YAML::EndMap; // tag component
 |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if (entity.has_component<TransformComponent>()) |  | ||||||
| 	{ |  | ||||||
| 		out << YAML::Key << "TransformComponent"; |  | ||||||
| 		out << YAML::BeginMap; // transform component
 |  | ||||||
| 
 |  | ||||||
| 		auto &transformComponent = entity.get_component<TransformComponent>(); |  | ||||||
| 
 |  | ||||||
| 		out << YAML::Key << "Translation" << YAML::Value << transformComponent.translation; |  | ||||||
| 		out << YAML::Key << "Rotation" << YAML::Value << transformComponent.rotation; |  | ||||||
| 		out << YAML::Key << "Scale" << YAML::Value << transformComponent.scale; |  | ||||||
| 
 |  | ||||||
| 		out << YAML::EndMap; // transform component;
 |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if (entity.has_component<SpriteRendererComponent>()) |  | ||||||
| 	{ |  | ||||||
| 		// TODO(Light): get scene serialization/de-serialization working.
 |  | ||||||
| 		// out << YAML::Key << "SpriteRendererComponent";
 |  | ||||||
| 		// out << YAML::BeginMap; // sprite renderer component;
 |  | ||||||
| 
 |  | ||||||
| 		// auto &spriteRendererComponent = entity.get_component<SpriteRendererComponent>();
 |  | ||||||
| 
 |  | ||||||
| 		// out << YAML::Key << "Texture" << YAML::Value
 |  | ||||||
| 		//     << spriteRendererComponent.texture->GetFilePath();
 |  | ||||||
| 		// out << YAML::Key << "Tint" << YAML::Value << spriteRendererComponent.tint;
 |  | ||||||
| 
 |  | ||||||
| 		// out << YAML::EndMap; // sprite renderer component
 |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// #todo:
 |  | ||||||
| 	// if(entity.has_component<NativeScriptComponent>())
 |  | ||||||
| 
 |  | ||||||
| 	if (entity.has_component<CameraComponent>()) |  | ||||||
| 	{ |  | ||||||
| 		out << YAML::Key << "CameraComponent"; |  | ||||||
| 		out << YAML::BeginMap; // camera component
 |  | ||||||
| 
 |  | ||||||
| 		auto &cameraComponent = entity.get_component<CameraComponent>(); |  | ||||||
| 
 |  | ||||||
| 		out << YAML::Key << "Camera" << YAML::Value; |  | ||||||
| 		out << YAML::BeginMap; // camera
 |  | ||||||
| 		out << YAML::Key << "OrthographicSize" << YAML::Value |  | ||||||
| 		    << cameraComponent.camera.get_orthographic_size(); |  | ||||||
| 		out << YAML::Key << "OrthographicFarPlane" << YAML::Value |  | ||||||
| 		    << cameraComponent.camera.get_orthographic_far_plane(); |  | ||||||
| 		out << YAML::Key << "OrthographicNearPlane" << YAML::Value |  | ||||||
| 		    << cameraComponent.camera.get_orthographic_near_plane(); |  | ||||||
| 		out << YAML::Key << "PerspectiveVerticalFOV" << YAML::Value |  | ||||||
| 		    << cameraComponent.camera.get_perspective_vertical_fov(); |  | ||||||
| 		out << YAML::Key << "PerspectiveFarPlane" << YAML::Value |  | ||||||
| 		    << cameraComponent.camera.get_perspective_far_plane(); |  | ||||||
| 		out << YAML::Key << "PerspectiveNearPlane" << YAML::Value |  | ||||||
| 		    << cameraComponent.camera.get_perspective_near_plane(); |  | ||||||
| 		out << YAML::Key << "ProjectionType" << YAML::Value |  | ||||||
| 		    << (int)cameraComponent.camera.get_projection_type(); |  | ||||||
| 		out << YAML::Key << "BackgroundColor" << YAML::Value |  | ||||||
| 		    << cameraComponent.camera.get_background_color(); |  | ||||||
| 		out << YAML::EndMap; // camera
 |  | ||||||
| 
 |  | ||||||
| 		out << YAML::Key << "IsPrimary" << YAML::Value << cameraComponent.isPrimary; |  | ||||||
| 
 |  | ||||||
| 		out << YAML::EndMap; // camera component
 |  | ||||||
| 	} |  | ||||||
| 	out << YAML::EndMap; // entity
 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| } // namespace lt
 |  | ||||||
							
								
								
									
										163
									
								
								modules/ecs/private/sparse_set.test.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										163
									
								
								modules/ecs/private/sparse_set.test.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,163 @@ | ||||||
|  | #include <ecs/sparse_set.hpp> | ||||||
|  | #include <ranges> | ||||||
|  | #include <test/expects.hpp> | ||||||
|  | #include <test/test.hpp> | ||||||
|  | 
 | ||||||
|  | using lt::test::Case; | ||||||
|  | using lt::test::Suite; | ||||||
|  | 
 | ||||||
|  | using lt::test::expect_eq; | ||||||
|  | using lt::test::expect_false; | ||||||
|  | using lt::test::expect_ne; | ||||||
|  | using lt::test::expect_throw; | ||||||
|  | using lt::test::expect_true; | ||||||
|  | 
 | ||||||
|  | using Set = lt::ecs::SparseSet<int>; | ||||||
|  | constexpr auto capacity = 100; | ||||||
|  | 
 | ||||||
|  | Suite raii = "raii"_suite = [] { | ||||||
|  | 	Case { "happy path won't throw" } = [] { | ||||||
|  | 		std::ignore = Set {}; | ||||||
|  | 		std::ignore = Set { Set::max_capacity }; | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	Case { "unhappy path throws" } = [] { | ||||||
|  | 		expect_throw([] { std::ignore = Set { Set::max_capacity + 1 }; }); | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	Case { "post construct has correct state" } = [&] { | ||||||
|  | 		auto set = Set { capacity }; | ||||||
|  | 		expect_eq(set.get_size(), 0); | ||||||
|  | 		expect_eq(set.get_capacity(), capacity); | ||||||
|  | 	}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | Suite element_raii = "element_raii"_suite = [] { | ||||||
|  | 	Case { "many inserts/removes won't freeze/throw" } = [] { | ||||||
|  | 		auto set = Set {}; | ||||||
|  | 		for (auto idx : std::views::iota(0, 10'000)) | ||||||
|  | 		{ | ||||||
|  | 			set.insert(idx, {}); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		for (auto idx : std::views::iota(0, 10'000)) | ||||||
|  | 		{ | ||||||
|  | 			set.remove(idx); | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	Case { "insert returns reference to inserted value" } = [] { | ||||||
|  | 		auto set = Set {}; | ||||||
|  | 		for (auto idx : std::views::iota(0, 10'000)) | ||||||
|  | 		{ | ||||||
|  | 			const auto val = Set::Dense_T { idx, {} }; | ||||||
|  | 			expect_eq(set.insert(val.first, val.second), val); | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	Case { "post insert/remove has correct state" } = [] { | ||||||
|  | 		auto set = Set {}; | ||||||
|  | 		for (auto idx : std::views::iota(0, 10'000)) | ||||||
|  | 		{ | ||||||
|  | 			set.insert(idx, idx * 2); | ||||||
|  | 			expect_eq(set.get_size(), idx + 1); | ||||||
|  | 			expect_eq(set.at(idx), Set::Dense_T { idx, idx * 2 }); | ||||||
|  | 			expect_true(set.contains(idx)); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		for (auto idx : std::views::iota(0, 10'000)) | ||||||
|  | 		{ | ||||||
|  | 			expect_eq(set.at(idx), Set::Dense_T { idx, idx * 2 }); | ||||||
|  | 			expect_true(set.contains(idx)); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		for (auto idx : std::views::iota(0, 10'000)) | ||||||
|  | 		{ | ||||||
|  | 			set.remove(idx); | ||||||
|  | 
 | ||||||
|  | 			expect_eq(set.get_size(), 10'000 - (idx + 1)); | ||||||
|  | 			expect_false(set.contains(idx)); | ||||||
|  | 			expect_throw([&] { std::ignore = set.at(idx); }); | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	Case { "removed elements won't be iterated again" } = [] { | ||||||
|  | 		auto set = Set {}; | ||||||
|  | 
 | ||||||
|  | 		for (auto idx : std::views::iota(0, 10'000)) | ||||||
|  | 		{ | ||||||
|  | 			set.insert(idx, idx); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		set.remove(0); | ||||||
|  | 		set.remove(32); | ||||||
|  | 		set.remove(69); | ||||||
|  | 		set.remove(420); | ||||||
|  | 		set.remove(9'999); | ||||||
|  | 
 | ||||||
|  | 		for (auto &[identifier, value] : set) | ||||||
|  | 		{ | ||||||
|  | 			expect_eq(identifier, value); | ||||||
|  | 			expect_ne(value, 0); | ||||||
|  | 			expect_ne(value, 32); | ||||||
|  | 			expect_ne(value, 69); | ||||||
|  | 			expect_ne(value, 420); | ||||||
|  | 			expect_ne(value, 9'999); | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | Suite getters = "getters"_suite = [] { | ||||||
|  | 	Case { "get_size returns correct values" } = [] { | ||||||
|  | 		auto set = Set {}; | ||||||
|  | 		for (auto idx : std::views::iota(0, 10'000)) | ||||||
|  | 		{ | ||||||
|  | 			expect_eq(set.get_size(), idx); | ||||||
|  | 			set.insert(idx, {}); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		expect_eq(set.get_size(), 10'000); | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	Case { "get_capacity returns correct values" } = [] { | ||||||
|  | 		auto set = Set { 10'000 }; | ||||||
|  | 		for (auto idx : std::views::iota(0, 10'000)) | ||||||
|  | 		{ | ||||||
|  | 			expect_eq(set.get_capacity(), 10'000); // are we testing std::vector's implementation?
 | ||||||
|  | 			set.insert(idx, {}); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		expect_eq(set.get_capacity(), 10'000); | ||||||
|  | 
 | ||||||
|  | 		set.insert(set.get_size(), {}); | ||||||
|  | 		expect_ne(set.get_capacity(), 10'000); | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	Case { "at throws with out of bound access" } = [] { | ||||||
|  | 		auto set = Set {}; | ||||||
|  | 
 | ||||||
|  | 		for (auto idx : std::views::iota(0, 50)) | ||||||
|  | 		{ | ||||||
|  | 			expect_throw([&] { | ||||||
|  | 				set.insert(idx, {}); | ||||||
|  | 				std::ignore = set.at(50); | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		set.insert(50, {}); | ||||||
|  | 		std::ignore = set.at(50); // should not throw
 | ||||||
|  | 	}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | Suite clear = "clear"_suite = [] { | ||||||
|  | 	Case { "post clear has correct state" } = [] { | ||||||
|  | 		auto set = Set { 0 }; | ||||||
|  | 		for (auto idx : std::views::iota(0, 10'000)) | ||||||
|  | 		{ | ||||||
|  | 			set.insert(idx, {}); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		set.clear(); | ||||||
|  | 		expect_eq(set.get_size(), 0); | ||||||
|  | 	}; | ||||||
|  | }; | ||||||
|  | @ -1,14 +0,0 @@ | ||||||
| #include <ecs/uuid.hpp> |  | ||||||
| 
 |  | ||||||
| namespace lt { |  | ||||||
| 
 |  | ||||||
| std::mt19937_64 UUID::s_engine = std::mt19937_64(std::random_device()()); |  | ||||||
| 
 |  | ||||||
| std::uniform_int_distribution<uint64_t> |  | ||||||
|     UUID::s_distribution = std::uniform_int_distribution<uint64_t> {}; |  | ||||||
| 
 |  | ||||||
| UUID::UUID(uint64_t uuid /* = -1 */): m_uuid(uuid == -1 ? s_distribution(s_engine) : uuid) |  | ||||||
| { |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| } // namespace lt
 |  | ||||||
|  | @ -1,6 +0,0 @@ | ||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <ecs/components/native_script.hpp> |  | ||||||
| #include <ecs/components/sprite_renderer.hpp> |  | ||||||
| #include <ecs/components/tag.hpp> |  | ||||||
| #include <ecs/components/transform.hpp> |  | ||||||
|  | @ -1,28 +0,0 @@ | ||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <ecs/components/scriptable_entity.hpp> |  | ||||||
| 
 |  | ||||||
| namespace lt { |  | ||||||
| 
 |  | ||||||
| struct NativeScriptComponent |  | ||||||
| { |  | ||||||
| 	NativeScript *(*CreateInstance)(); |  | ||||||
| 
 |  | ||||||
| 	void (*DestroyInstance)(NativeScriptComponent *); |  | ||||||
| 
 |  | ||||||
| 	template<typename t> |  | ||||||
| 	void bind() |  | ||||||
| 	{ |  | ||||||
| 		CreateInstance = []() { |  | ||||||
| 			return static_cast<NativeScript *>(new t()); |  | ||||||
| 		}; |  | ||||||
| 		DestroyInstance = [](NativeScriptComponent *nsc) { |  | ||||||
| 			delete (t *)(nsc->instance); |  | ||||||
| 			nsc->instance = nullptr; |  | ||||||
| 		}; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	NativeScript *instance; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| } // namespace lt
 |  | ||||||
|  | @ -1,46 +0,0 @@ | ||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <ecs/entity.hpp> |  | ||||||
| 
 |  | ||||||
| namespace lt { |  | ||||||
| 
 |  | ||||||
| class NativeScript |  | ||||||
| { |  | ||||||
| public: |  | ||||||
| 	friend class Scene; |  | ||||||
| 
 |  | ||||||
| 	NativeScript() = default; |  | ||||||
| 
 |  | ||||||
| 	virtual ~NativeScript() = default; |  | ||||||
| 
 |  | ||||||
| 	[[nodiscard]] auto get_uid() const -> unsigned int |  | ||||||
| 	{ |  | ||||||
| 		return m_unique_identifier; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	template<typename t> |  | ||||||
| 	auto GetComponent() -> t & |  | ||||||
| 	{ |  | ||||||
| 		return m_entity.get_component<t>(); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| protected: |  | ||||||
| 	virtual void on_create() |  | ||||||
| 	{ |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	virtual void on_destroy() |  | ||||||
| 	{ |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	virtual void on_update(float ts) |  | ||||||
| 	{ |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| private: |  | ||||||
| 	Entity m_entity; |  | ||||||
| 
 |  | ||||||
| 	unsigned int m_unique_identifier = 0; // :#todo
 |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| } // namespace lt
 |  | ||||||
|  | @ -1,35 +0,0 @@ | ||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <math/vec4.hpp> |  | ||||||
| #include <utility> |  | ||||||
| 
 |  | ||||||
| namespace lt { |  | ||||||
| 
 |  | ||||||
| class Texture; |  | ||||||
| 
 |  | ||||||
| struct SpriteRendererComponent |  | ||||||
| { |  | ||||||
| 	SpriteRendererComponent() = default; |  | ||||||
| 
 |  | ||||||
| 	SpriteRendererComponent(const SpriteRendererComponent &) = default; |  | ||||||
| 
 |  | ||||||
| 	SpriteRendererComponent( |  | ||||||
| 	    Ref<Texture> _texture, |  | ||||||
| 	    const math::vec4 &_tint = math::vec4 { 1.0f, 1.0f, 1.0f, 1.0f } |  | ||||||
| 	) |  | ||||||
| 	    : texture(std::move(std::move(_texture))) |  | ||||||
| 	    , tint(_tint) |  | ||||||
| 	{ |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	operator Ref<Texture>() const |  | ||||||
| 	{ |  | ||||||
| 		return texture; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	Ref<Texture> texture; |  | ||||||
| 
 |  | ||||||
| 	math::vec4 tint {}; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| } // namespace lt
 |  | ||||||
|  | @ -1,30 +0,0 @@ | ||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <utility> |  | ||||||
| 
 |  | ||||||
| namespace lt { |  | ||||||
| 
 |  | ||||||
| struct TagComponent |  | ||||||
| { |  | ||||||
| 	TagComponent() = default; |  | ||||||
| 
 |  | ||||||
| 	TagComponent(const TagComponent &) = default; |  | ||||||
| 
 |  | ||||||
| 	TagComponent(std::string _tag): tag(std::move(_tag)) |  | ||||||
| 	{ |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	operator std::string() const |  | ||||||
| 	{ |  | ||||||
| 		return tag; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	operator const std::string &() const |  | ||||||
| 	{ |  | ||||||
| 		return tag; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	std::string tag = "Unnamed"; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| } // namespace lt
 |  | ||||||
|  | @ -1,43 +0,0 @@ | ||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <math/mat4.hpp> |  | ||||||
| #include <math/vec3.hpp> |  | ||||||
| 
 |  | ||||||
| namespace lt { |  | ||||||
| 
 |  | ||||||
| struct TransformComponent |  | ||||||
| { |  | ||||||
| 	TransformComponent(const TransformComponent &) = default; |  | ||||||
| 
 |  | ||||||
| 	TransformComponent( |  | ||||||
| 	    const math::vec3 &_translation = math::vec3(0.0f, 0.0f, 0.0f), |  | ||||||
| 	    const math::vec3 &_scale = math::vec3(1.0f, 1.0f, 1.0f), |  | ||||||
| 	    const math::vec3 &_rotation = math::vec3(0.0f, 0.0f, 0.0f) |  | ||||||
| 	) |  | ||||||
| 
 |  | ||||||
| 	    : translation(_translation) |  | ||||||
| 	    , scale(_scale) |  | ||||||
| 	    , rotation(_rotation) |  | ||||||
| 	{ |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	[[nodiscard]] auto get_transform() const -> math::mat4 |  | ||||||
| 	{ |  | ||||||
| 		return math::translate(translation) |  | ||||||
| 		       * math::rotate(rotation.z, math::vec3 { 0.0f, 0.0f, 1.0f }) //
 |  | ||||||
| 		       * math::scale(scale); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	operator const math::mat4() const |  | ||||||
| 	{ |  | ||||||
| 		return get_transform(); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	math::vec3 translation; |  | ||||||
| 
 |  | ||||||
| 	math::vec3 scale; |  | ||||||
| 
 |  | ||||||
| 	math::vec3 rotation; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| } // namespace lt
 |  | ||||||
|  | @ -1,18 +0,0 @@ | ||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <ecs/uuid.hpp> |  | ||||||
| 
 |  | ||||||
| namespace lt { |  | ||||||
| 
 |  | ||||||
| struct UUIDComponent |  | ||||||
| { |  | ||||||
| 	UUIDComponent(UUID _uuid): uuid(_uuid) |  | ||||||
| 	{ |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	UUIDComponent(const UUIDComponent &) = default; |  | ||||||
| 
 |  | ||||||
| 	UUID uuid; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| } // namespace lt
 |  | ||||||
|  | @ -1,59 +1,54 @@ | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include <ecs/components/uuid.hpp> | #include <ecs/registry.hpp> | ||||||
| #include <ecs/scene.hpp> | #include <memory/reference.hpp> | ||||||
| #include <entt/entt.hpp> |  | ||||||
| 
 | 
 | ||||||
| namespace lt { | namespace lt::ecs { | ||||||
| 
 | 
 | ||||||
|  | /** High-level entity convenience wrapper */ | ||||||
| class Entity | class Entity | ||||||
| { | { | ||||||
| public: | public: | ||||||
| 	Entity(entt::entity handle = entt::null, Scene *scene = nullptr); | 	Entity(memory::Ref<Registry> registry, EntityId identifier) | ||||||
| 
 | 	    : m_registry(std::move(registry)) | ||||||
| 	template<typename t, typename... Args> | 	    , m_identifier(identifier) | ||||||
| 	auto add_component(Args &&...args) -> t & |  | ||||||
| 	{ | 	{ | ||||||
| 		return m_scene->m_registry.emplace<t>(m_handle, std::forward<Args>(args)...); | 		ensure(m_registry, "Failed to create Entity ({}): null registry", m_identifier); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	template<typename t> | 	template<typename Component_T> | ||||||
| 	auto get_component() -> t & | 	auto add(Component_T component) -> Component_T & | ||||||
| 	{ | 	{ | ||||||
| 		return m_scene->m_registry.get<t>(m_handle); | 		return m_registry->add(m_identifier, component); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	template<typename t> | 	template<typename Component_T> | ||||||
| 	auto has_component() -> bool | 	auto get() -> Component_T & | ||||||
| 	{ | 	{ | ||||||
| 		return m_scene->m_registry.any_of<t>(m_handle); | 		return m_registry->get<Component_T>(m_identifier); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	template<typename t> | 	template<typename Component_T> | ||||||
| 	void remove_component() | 	auto get() const -> const Component_T & | ||||||
| 	{ | 	{ | ||||||
| 		m_scene->m_registry.remove<t>(m_handle); | 		return m_registry->get<Component_T>(m_identifier); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	auto get_uuid() -> uint64_t | 	auto get_registry() -> memory::Ref<Registry> | ||||||
| 	{ | 	{ | ||||||
| 		return get_component<UUIDComponent>().uuid; | 		return m_registry; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	[[nodiscard]] auto is_valid() const -> bool | 	[[nodiscard]] auto id() const -> EntityId | ||||||
| 	{ | 	{ | ||||||
| 		return m_handle != entt::null && m_scene != nullptr; | 		return m_identifier; | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	operator uint32_t() |  | ||||||
| 	{ |  | ||||||
| 		return (uint32_t)m_handle; |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
| 	entt::entity m_handle; | 	memory::Ref<Registry> m_registry; | ||||||
| 
 | 
 | ||||||
| 	Scene *m_scene; | 	EntityId m_identifier; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } // namespace lt
 | 
 | ||||||
|  | } // namespace lt::ecs
 | ||||||
|  |  | ||||||
							
								
								
									
										260
									
								
								modules/ecs/public/registry.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										260
									
								
								modules/ecs/public/registry.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,260 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <ecs/sparse_set.hpp> | ||||||
|  | #include <memory/scope.hpp> | ||||||
|  | 
 | ||||||
|  | namespace lt::ecs { | ||||||
|  | 
 | ||||||
|  | using EntityId = uint32_t; | ||||||
|  | 
 | ||||||
|  | constexpr auto null_entity = std::numeric_limits<EntityId>::max(); | ||||||
|  | 
 | ||||||
|  | /** A registry of components, the heart of an ECS architecture.
 | ||||||
|  |  * | ||||||
|  |  * @todo(Light): Implement grouping | ||||||
|  |  * @todo(Light): Implement identifier recycling | ||||||
|  |  * @todo(Light): Optimize views/each | ||||||
|  |  * @todo(Light): Support >2 component views | ||||||
|  |  * @todo(Light): Handle more edge cases or specify the undefined behaviors | ||||||
|  |  * | ||||||
|  |  * @ref https://skypjack.github.io/
 | ||||||
|  |  * @ref https://github.com/alecthomas/entityx
 | ||||||
|  |  * @ref https://github.com/skypjack/entt
 | ||||||
|  |  * @ref https://github.com/SanderMertens/flecs
 | ||||||
|  |  */ | ||||||
|  | class Registry | ||||||
|  | { | ||||||
|  | public: | ||||||
|  | 	using UnderlyingSparseSet_T = TypeErasedSparseSet<EntityId>; | ||||||
|  | 
 | ||||||
|  | 	using Callback_T = std::function<void(Registry &, EntityId)>; | ||||||
|  | 
 | ||||||
|  | 	template<typename Component_T> | ||||||
|  | 	void connect_on_construct(Callback_T callback) | ||||||
|  | 	{ | ||||||
|  | 		m_on_construct_hooks[get_type_id<Component_T>()] = callback; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	template<typename Component_T> | ||||||
|  | 	void connect_on_destruct(Callback_T callback) | ||||||
|  | 	{ | ||||||
|  | 		m_on_destruct_hooks[get_type_id<Component_T>()] = callback; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	template<typename Component_T> | ||||||
|  | 	void disconnect_on_construct() | ||||||
|  | 	{ | ||||||
|  | 		m_on_construct_hooks.erase(get_type_id<Component_T>()); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	template<typename Component_T> | ||||||
|  | 	void disconnect_on_destruct() | ||||||
|  | 	{ | ||||||
|  | 		m_on_destruct_hooks.erase(get_type_id<Component_T>()); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	auto create_entity() -> EntityId | ||||||
|  | 	{ | ||||||
|  | 		++m_entity_count; | ||||||
|  | 		return m_current++; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	void destroy_entity(EntityId entity) | ||||||
|  | 	{ | ||||||
|  | 		for (const auto &[key, set] : m_sparsed_sets) | ||||||
|  | 		{ | ||||||
|  | 			set->remove(entity); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		--m_entity_count; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	template<typename Component_T> | ||||||
|  | 	auto get(EntityId entity) const -> const Component_T & | ||||||
|  | 	{ | ||||||
|  | 		auto &derived_set = get_derived_set<Component_T>(); | ||||||
|  | 		return derived_set.at(entity).second; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	template<typename Component_T> | ||||||
|  | 	auto get(EntityId entity) -> Component_T & | ||||||
|  | 	{ | ||||||
|  | 		auto &derived_set = get_derived_set<Component_T>(); | ||||||
|  | 		return derived_set.at(entity).second; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	template<typename Component_T> | ||||||
|  | 	auto add(EntityId entity, Component_T component) -> Component_T & | ||||||
|  | 	{ | ||||||
|  | 		auto &derived_set = get_derived_set<Component_T>(); | ||||||
|  | 		auto &added_component = derived_set.insert(entity, std::move(component)).second; | ||||||
|  | 
 | ||||||
|  | 		if (m_on_construct_hooks.contains(get_type_id<Component_T>())) | ||||||
|  | 		{ | ||||||
|  | 			m_on_construct_hooks[get_type_id<Component_T>()](*this, entity); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return added_component; | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	template<typename Component_T> | ||||||
|  | 	void remove(EntityId entity) | ||||||
|  | 	{ | ||||||
|  | 		if (m_on_destruct_hooks.contains(get_type_id<Component_T>())) | ||||||
|  | 		{ | ||||||
|  | 			m_on_destruct_hooks[get_type_id<Component_T>()](*this, entity); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		auto &derived_set = get_derived_set<Component_T>(); | ||||||
|  | 		derived_set.remove(entity); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	template<typename Component_T> | ||||||
|  | 	auto view() -> SparseSet<Component_T, EntityId> & | ||||||
|  | 	{ | ||||||
|  | 		return get_derived_set<Component_T>(); | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	template<typename ComponentA_T, typename ComponentB_T> | ||||||
|  | 	    requires(!std::is_same_v<ComponentA_T, ComponentB_T>) | ||||||
|  | 	auto view() -> std::vector<std::tuple<EntityId, ComponentA_T &, ComponentB_T &>> | ||||||
|  | 	{ | ||||||
|  | 		auto &set_a = get_derived_set<ComponentA_T>(); | ||||||
|  | 		auto &set_b = get_derived_set<ComponentB_T>(); | ||||||
|  | 		auto view = std::vector<std::tuple<EntityId, ComponentA_T &, ComponentB_T &>> {}; | ||||||
|  | 
 | ||||||
|  | 		/* iterate over the "smaller" component-set, and check if its entities have the other
 | ||||||
|  | 		 * component */ | ||||||
|  | 		if (set_a.get_size() > set_b.get_size()) | ||||||
|  | 		{ | ||||||
|  | 			for (auto &[entity, component_b] : set_b) | ||||||
|  | 			{ | ||||||
|  | 				if (set_a.contains(entity)) | ||||||
|  | 				{ | ||||||
|  | 					view.emplace_back(std::tie(entity, set_a.at(entity).second, component_b)); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			for (auto &[entity, component_a] : set_a) | ||||||
|  | 			{ | ||||||
|  | 				if (set_b.contains(entity)) | ||||||
|  | 				{ | ||||||
|  | 					view.emplace_back(std::tie(entity, component_a, set_b.at(entity).second)); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return view; | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	template<typename Component_T> | ||||||
|  | 	void each(std::function<void(EntityId, Component_T &)> functor) | ||||||
|  | 	{ | ||||||
|  | 		for (auto &[entity, component] : get_derived_set<Component_T>()) | ||||||
|  | 		{ | ||||||
|  | 			functor(entity, component); | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	template<typename ComponentA_T, typename ComponentB_T> | ||||||
|  | 	    requires(!std::is_same_v<ComponentA_T, ComponentB_T>) | ||||||
|  | 	void each(std::function<void(EntityId, ComponentA_T &, ComponentB_T &)> functor) | ||||||
|  | 	{ | ||||||
|  | 		auto &set_a = get_derived_set<ComponentA_T>(); | ||||||
|  | 		auto &set_b = get_derived_set<ComponentB_T>(); | ||||||
|  | 
 | ||||||
|  | 		/* iterate over the "smaller" component-set, and check if its entities have the other
 | ||||||
|  | 		 * component */ | ||||||
|  | 		if (set_a.get_size() > set_b.get_size()) | ||||||
|  | 		{ | ||||||
|  | 			for (auto &[entity, component_b] : set_b) | ||||||
|  | 			{ | ||||||
|  | 				if (set_a.contains(entity)) | ||||||
|  | 				{ | ||||||
|  | 					functor(entity, set_a.at(entity).second, component_b); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		for (auto &[entity, component_a] : set_a) | ||||||
|  | 		{ | ||||||
|  | 			if (set_b.contains(entity)) | ||||||
|  | 			{ | ||||||
|  | 				functor(entity, component_a, set_b.at(entity).second); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	[[nodiscard]] auto get_entity_count() const -> size_t | ||||||
|  | 	{ | ||||||
|  | 		return static_cast<size_t>(m_entity_count); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  | 	using TypeId = size_t; | ||||||
|  | 
 | ||||||
|  | 	static consteval auto hash_cstr(const char *str) -> TypeId | ||||||
|  | 	{ | ||||||
|  | 		constexpr auto fnv_offset_basis = size_t { 14695981039346656037ull }; | ||||||
|  | 		constexpr auto fnv_prime = size_t { 1099511628211ull }; | ||||||
|  | 
 | ||||||
|  | 		auto hash = fnv_offset_basis; | ||||||
|  | 
 | ||||||
|  | 		for (const auto &ch : std::string_view { str }) | ||||||
|  | 		{ | ||||||
|  | 			hash *= fnv_prime; | ||||||
|  | 			hash ^= static_cast<uint8_t>(ch); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return hash; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	template<typename T> | ||||||
|  | 	static consteval auto get_type_id() -> TypeId | ||||||
|  | 	{ | ||||||
|  | #if defined _MSC_VER | ||||||
|  | 	#define GENERATOR_PRETTY_FUNCTION __FUNCSIG__ | ||||||
|  | #elif defined __clang__ || (defined __GNUC__) | ||||||
|  | 	#define GENERATOR_PRETTY_FUNCTION __PRETTY_FUNCTION__ | ||||||
|  | #else | ||||||
|  | 	#error "Compiler not supported" | ||||||
|  | #endif | ||||||
|  | 		constexpr auto value = hash_cstr(GENERATOR_PRETTY_FUNCTION); | ||||||
|  | 
 | ||||||
|  | #undef GENERATOR_PRETTY_FUNCTION | ||||||
|  | 
 | ||||||
|  | 		return value; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	template<typename T> | ||||||
|  | 	auto get_derived_set() -> SparseSet<T, EntityId> & | ||||||
|  | 	{ | ||||||
|  | 		constexpr auto type_id = get_type_id<T>(); | ||||||
|  | 		if (!m_sparsed_sets.contains(type_id)) | ||||||
|  | 		{ | ||||||
|  | 			m_sparsed_sets[type_id] = memory::create_scope<SparseSet<T, EntityId>>(); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		auto *base_set = m_sparsed_sets[type_id].get(); | ||||||
|  | 		auto *derived_set = dynamic_cast<SparseSet<T, EntityId> *>(base_set); | ||||||
|  | 		ensure(derived_set, "Failed to downcast to derived set"); | ||||||
|  | 
 | ||||||
|  | 		return *derived_set; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	EntityId m_current; | ||||||
|  | 
 | ||||||
|  | 	TypeId m_entity_count; | ||||||
|  | 
 | ||||||
|  | 	std::flat_map<TypeId, memory::Scope<UnderlyingSparseSet_T>> m_sparsed_sets; | ||||||
|  | 
 | ||||||
|  | 	std::flat_map<TypeId, Callback_T> m_on_construct_hooks; | ||||||
|  | 
 | ||||||
|  | 	std::flat_map<TypeId, Callback_T> m_on_destruct_hooks; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace lt::ecs
 | ||||||
|  | @ -1,65 +0,0 @@ | ||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <ecs/components/transform.hpp> |  | ||||||
| #include <ecs/uuid.hpp> |  | ||||||
| #include <entt/entt.hpp> |  | ||||||
| #include <functional> |  | ||||||
| 
 |  | ||||||
| namespace lt { |  | ||||||
| 
 |  | ||||||
| class Entity; |  | ||||||
| class Framebuffer; |  | ||||||
| 
 |  | ||||||
| class Scene |  | ||||||
| { |  | ||||||
| public: |  | ||||||
| 	template<typename... T> |  | ||||||
| 	auto group() |  | ||||||
| 	{ |  | ||||||
| 		return m_registry.group(entt::get<T...>); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	template<typename T> |  | ||||||
| 	auto view() |  | ||||||
| 	{ |  | ||||||
| 		return m_registry.view<T>(); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	auto create_entity( |  | ||||||
| 	    const std::string &name, |  | ||||||
| 	    const TransformComponent &transform = TransformComponent() |  | ||||||
| 	) -> Entity; |  | ||||||
| 
 |  | ||||||
| 	auto get_entity_by_tag(const std::string &tag) -> Entity; |  | ||||||
| 
 |  | ||||||
| 	auto get_entt_registry() -> entt::registry & |  | ||||||
| 	{ |  | ||||||
| 		return m_registry; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| private: |  | ||||||
| 	friend class Entity; |  | ||||||
| 
 |  | ||||||
| 	friend class SceneSerializer; |  | ||||||
| 
 |  | ||||||
| 	friend class SceneHierarchyPanel; |  | ||||||
| 
 |  | ||||||
| 	entt::registry m_registry; |  | ||||||
| 
 |  | ||||||
| 	auto create_entity_with_uuid( |  | ||||||
| 	    const std::string &name, |  | ||||||
| 	    UUID uuid, |  | ||||||
| 	    const TransformComponent &transform = TransformComponent() |  | ||||||
| 	) -> Entity; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| namespace ecs { |  | ||||||
| 
 |  | ||||||
| using Registry = Scene; |  | ||||||
| 
 |  | ||||||
| using Entity = ::lt::Entity; |  | ||||||
| 
 |  | ||||||
| } // namespace ecs
 |  | ||||||
| 
 |  | ||||||
| } // namespace lt
 |  | ||||||
|  | @ -1,34 +0,0 @@ | ||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <ecs/entity.hpp> |  | ||||||
| #include <ecs/scene.hpp> |  | ||||||
| 
 |  | ||||||
| namespace YAML { |  | ||||||
| 
 |  | ||||||
| class Emitter; |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| namespace lt { |  | ||||||
| 
 |  | ||||||
| class SceneSerializer |  | ||||||
| { |  | ||||||
| public: |  | ||||||
| 	SceneSerializer(const Ref<Scene> &scene); |  | ||||||
| 
 |  | ||||||
| 	void serialize(const std::string &filePath); |  | ||||||
| 
 |  | ||||||
| 	auto deserialize(const std::string &file_path) -> bool; |  | ||||||
| 
 |  | ||||||
| 	void serialize_binary(const std::string &file_path); |  | ||||||
| 
 |  | ||||||
| 	auto deserialize_binary(const std::string &file_path) -> bool; |  | ||||||
| 
 |  | ||||||
| private: |  | ||||||
| 	Ref<Scene> m_scene; |  | ||||||
| 
 |  | ||||||
| 	void serialize_entity(YAML::Emitter &out, Entity entity); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| } // namespace lt
 |  | ||||||
							
								
								
									
										173
									
								
								modules/ecs/public/sparse_set.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								modules/ecs/public/sparse_set.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,173 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | namespace lt::ecs { | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * | ||||||
|  |  * @ref https://programmingpraxis.com/2012/03/09/sparse-sets/
 | ||||||
|  |  */ | ||||||
|  | template<typename Identifier_T = uint32_t> | ||||||
|  | class TypeErasedSparseSet | ||||||
|  | { | ||||||
|  | public: | ||||||
|  | 	TypeErasedSparseSet() = default; | ||||||
|  | 
 | ||||||
|  | 	TypeErasedSparseSet(TypeErasedSparseSet &&) = default; | ||||||
|  | 
 | ||||||
|  | 	TypeErasedSparseSet(const TypeErasedSparseSet &) = default; | ||||||
|  | 
 | ||||||
|  | 	auto operator=(TypeErasedSparseSet &&) -> TypeErasedSparseSet & = default; | ||||||
|  | 
 | ||||||
|  | 	auto operator=(const TypeErasedSparseSet &) -> TypeErasedSparseSet & = default; | ||||||
|  | 
 | ||||||
|  | 	virtual ~TypeErasedSparseSet() = default; | ||||||
|  | 
 | ||||||
|  | 	virtual void remove(Identifier_T identifier) = 0; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | template<typename Value_T, typename Identifier_T = uint32_t> | ||||||
|  | class SparseSet: public TypeErasedSparseSet<Identifier_T> | ||||||
|  | { | ||||||
|  | public: | ||||||
|  | 	using Dense_T = std::pair<Identifier_T, Value_T>; | ||||||
|  | 
 | ||||||
|  | 	static constexpr auto max_capacity = size_t { 1'000'000 }; | ||||||
|  | 
 | ||||||
|  | 	static constexpr auto null_identifier = std::numeric_limits<Identifier_T>().max(); | ||||||
|  | 
 | ||||||
|  | 	explicit SparseSet(size_t initial_capacity = 1) | ||||||
|  | 	{ | ||||||
|  | 		ensure( | ||||||
|  | 		    initial_capacity <= max_capacity, | ||||||
|  | 		    "Failed to create SparseSet: capacity too large ({} > {})", | ||||||
|  | 		    initial_capacity, | ||||||
|  | 		    max_capacity | ||||||
|  | 		); | ||||||
|  | 
 | ||||||
|  | 		m_dense.reserve(initial_capacity); | ||||||
|  | 		m_sparse.resize(initial_capacity, null_identifier); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	auto insert(Identifier_T identifier, Value_T value) -> Dense_T & | ||||||
|  | 	{ | ||||||
|  | 		if (m_sparse.size() < identifier + 1) | ||||||
|  | 		{ | ||||||
|  | 			auto new_capacity = std::max(static_cast<size_t>(identifier + 1), m_sparse.size() * 2); | ||||||
|  | 			new_capacity = std::min(new_capacity, max_capacity); | ||||||
|  | 
 | ||||||
|  | 			// log_dbg("Increasing sparse vector size:", m_dead_count);
 | ||||||
|  | 			// log_dbg("\tdead_count: {}", m_dead_count);
 | ||||||
|  | 			// log_dbg("\talive_count: {}", m_alive_count);
 | ||||||
|  | 			// log_dbg("\tsparse.size: {} -> {}", m_sparse.size(), new_capacity);
 | ||||||
|  | 
 | ||||||
|  | 			m_sparse.resize(new_capacity, null_identifier); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		++m_alive_count; | ||||||
|  | 		m_sparse[identifier] = m_dense.size(); | ||||||
|  | 		return m_dense.emplace_back(identifier, std::move(value)); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** @warn invalidates begin/end iterators
 | ||||||
|  | 	 * | ||||||
|  | 	 * @todo(Light): make it not invalidate the iterators >:c | ||||||
|  | 	 */ | ||||||
|  | 	void remove(Identifier_T identifier) override | ||||||
|  | 	{ | ||||||
|  | 		auto &idx = m_sparse[identifier]; | ||||||
|  | 		auto &[entity, component] = m_dense[idx]; | ||||||
|  | 
 | ||||||
|  | 		auto &[last_entity, last_component] = m_dense.back(); | ||||||
|  | 		auto &last_idx = m_sparse[last_entity]; | ||||||
|  | 
 | ||||||
|  | 		// removed entity is in dense's back, just pop and invalidate sparse[identifier]
 | ||||||
|  | 		if (entity == last_entity) | ||||||
|  | 		{ | ||||||
|  | 			idx = null_identifier; | ||||||
|  | 			m_dense.pop_back(); | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			// swap dense's 'back' to 'removed'
 | ||||||
|  | 			std::swap(component, last_component); | ||||||
|  | 			entity = last_entity; | ||||||
|  | 
 | ||||||
|  | 			// make sparse point to new idx
 | ||||||
|  | 			last_idx = idx; | ||||||
|  | 
 | ||||||
|  | 			// pop dense and invalidate sparse[identifier]
 | ||||||
|  | 			idx = null_identifier; | ||||||
|  | 			m_dense.pop_back(); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		++m_dead_count; | ||||||
|  | 		--m_alive_count; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	void clear() | ||||||
|  | 	{ | ||||||
|  | 		m_dense.clear(); | ||||||
|  | 		m_sparse.clear(); | ||||||
|  | 		m_alive_count = 0; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	[[nodiscard]] auto contains(Identifier_T identifier) const -> bool | ||||||
|  | 	{ | ||||||
|  | 		return m_sparse.size() > identifier               //
 | ||||||
|  | 		       && m_sparse[identifier] != null_identifier //
 | ||||||
|  | 		       && m_dense[m_sparse[identifier]].first == identifier; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	auto begin() -> std::vector<Dense_T>::iterator | ||||||
|  | 	{ | ||||||
|  | 		return m_dense.begin(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	auto end() -> std::vector<Dense_T>::iterator | ||||||
|  | 	{ | ||||||
|  | 		return m_dense.end(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	[[nodiscard]] auto at(Identifier_T identifier) const -> const Dense_T & | ||||||
|  | 	{ | ||||||
|  | 		return m_dense.at(m_sparse.at(identifier)); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	[[nodiscard]] auto at(Identifier_T identifier) -> Dense_T & | ||||||
|  | 	{ | ||||||
|  | 		return m_dense.at(m_sparse.at(identifier)); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** @warn unsafe, for bound-checked access: use `.at` */ | ||||||
|  | 	[[nodiscard]] auto &&operator[](this auto &&self, Identifier_T identifier) | ||||||
|  | 	{ | ||||||
|  | 		using Self_T = decltype(self); | ||||||
|  | 		return std::forward<Self_T>(self).m_dense[std::forward<Self_T>(self).m_sparse[identifier]]; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	[[nodiscard]] auto get_size() const noexcept -> size_t | ||||||
|  | 	{ | ||||||
|  | 		return m_alive_count; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	[[nodiscard]] auto get_capacity() const noexcept -> size_t | ||||||
|  | 	{ | ||||||
|  | 		return m_sparse.capacity(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	[[nodiscard]] auto is_empty() const noexcept -> bool | ||||||
|  | 	{ | ||||||
|  | 		return m_dense.empty(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  | 	std::vector<Dense_T> m_dense; | ||||||
|  | 
 | ||||||
|  | 	std::vector<Identifier_T> m_sparse; | ||||||
|  | 
 | ||||||
|  | 	size_t m_alive_count {}; | ||||||
|  | 
 | ||||||
|  | 	size_t m_dead_count {}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace lt::ecs
 | ||||||
|  | @ -1,38 +0,0 @@ | ||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <random> |  | ||||||
| 
 |  | ||||||
| namespace lt { |  | ||||||
| 
 |  | ||||||
| class UUID |  | ||||||
| { |  | ||||||
| public: |  | ||||||
| 	UUID(uint64_t uuid = -1); |  | ||||||
| 
 |  | ||||||
| 	operator uint64_t() const |  | ||||||
| 	{ |  | ||||||
| 		return m_uuid; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| private: |  | ||||||
| 	static std::mt19937_64 s_engine; |  | ||||||
| 
 |  | ||||||
| 	static std::uniform_int_distribution<uint64_t> s_distribution; |  | ||||||
| 
 |  | ||||||
| 	uint64_t m_uuid; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| } // namespace lt
 |  | ||||||
| 
 |  | ||||||
| namespace std { |  | ||||||
| 
 |  | ||||||
| template<> |  | ||||||
| struct hash<lt::UUID> |  | ||||||
| { |  | ||||||
| 	std::size_t operator()(const lt::UUID &uuid) const |  | ||||||
| 	{ |  | ||||||
| 		return hash<uint64_t>()(static_cast<uint64_t>(uuid)); |  | ||||||
| 	} |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| } // namespace std
 |  | ||||||
							
								
								
									
										1
									
								
								modules/env/CMakeLists.txt
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								modules/env/CMakeLists.txt
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | ||||||
|  | add_library_module(env) | ||||||
							
								
								
									
										68
									
								
								modules/env/public/constants.hpp
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								modules/env/public/constants.hpp
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,68 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | namespace lt { | ||||||
|  | 
 | ||||||
|  | enum class Platform : uint8_t | ||||||
|  | { | ||||||
|  | 	/** The GNU/Linux platform.
 | ||||||
|  | 	 * Tested on the following distros: arch-x86_64 | ||||||
|  | 	 * @note: Named like so because `linux` is a built-in identifier. | ||||||
|  | 	 * */ | ||||||
|  | 	gnu_linux, | ||||||
|  | 
 | ||||||
|  | 	/**
 | ||||||
|  | 	 * The Microsoft Windows(tm) platform. | ||||||
|  | 	 * Tested on the following architectures: x86_64 | ||||||
|  | 	 */ | ||||||
|  | 	windows, | ||||||
|  | 
 | ||||||
|  | 	/**
 | ||||||
|  | 	 * The apple's macOS platform. | ||||||
|  | 	 * Currently not supported. | ||||||
|  | 	 */ | ||||||
|  | 	mac, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** The compiler that was used for compiling the project. */ | ||||||
|  | enum class Compiler : uint8_t | ||||||
|  | { | ||||||
|  | 	clang, | ||||||
|  | 	gcc, | ||||||
|  | 	msvc, | ||||||
|  | 	apple_clang, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | namespace constants { | ||||||
|  | 
 | ||||||
|  | #if defined(LIGHT_PLATFORM_WINDOWS) | ||||||
|  | 	#define lt_win(x) | ||||||
|  | constexpr auto platform = Platform::windows; | ||||||
|  | constexpr auto platform_name = "windows"; | ||||||
|  | 
 | ||||||
|  | 	#undef LIGHT_PLATFORM_WINDOWS | ||||||
|  | 
 | ||||||
|  | #elif defined(LIGHT_PLATFORM_LINUX) | ||||||
|  | constexpr auto platform = Platform::gnu_linux; | ||||||
|  | constexpr auto platform_name = "gnu_linux"; | ||||||
|  | 
 | ||||||
|  | #elif defined(LIGHT_PLATFORM_MAC) | ||||||
|  | 	#define lt_mac(x) x | ||||||
|  | constexpr auto platform = Platform::mac; | ||||||
|  | constexpr auto platform_name = "mac"; | ||||||
|  | 
 | ||||||
|  | #else | ||||||
|  | 	#error "Unsupported platform: Unknown" | ||||||
|  | 
 | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | #ifdef __clang__ | ||||||
|  | constexpr auto compiler = Compiler::clang; | ||||||
|  | constexpr auto compiler_name = "clang"; | ||||||
|  | 
 | ||||||
|  | /** @todo(Light): insert the full identifier, including version information and such */ | ||||||
|  | constexpr auto full_compiler_identifier = "clang"; | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | } // namespace constants
 | ||||||
|  | 
 | ||||||
|  | } // namespace lt
 | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| add_library_module(input system.cpp) | add_library_module(input system.cpp) | ||||||
| target_link_libraries(input PUBLIC surface math  logger) | target_link_libraries(input PUBLIC surface math logger tbb) | ||||||
| 
 | 
 | ||||||
| add_test_module(input system.test.cpp) | add_test_module(input system.test.cpp) | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| #include <input/components.hpp> | #include <input/components.hpp> | ||||||
| #include <input/system.hpp> | #include <input/system.hpp> | ||||||
|  | #include <memory/reference.hpp> | ||||||
| 
 | 
 | ||||||
| namespace lt::input { | namespace lt::input { | ||||||
| 
 | 
 | ||||||
|  | @ -9,22 +10,23 @@ struct overloads: Ts... | ||||||
| 	using Ts::operator()...; | 	using Ts::operator()...; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| System::System(Ref<ecs::Registry> registry): m_registry(std::move(registry)) | System::System(memory::Ref<ecs::Registry> registry): m_registry(std::move(registry)) | ||||||
| { | { | ||||||
| 	ensure(m_registry, "Failed to initialize input system: null registry"); | 	ensure(m_registry, "Failed to initialize input system: null registry"); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| auto System::tick() -> bool | void System::tick(app::TickInfo tick) | ||||||
|  | { | ||||||
|  | 	for (auto &[entity, surface] : m_registry->view<surface::SurfaceComponent>()) | ||||||
| 	{ | 	{ | ||||||
| 	m_registry->view<surface::SurfaceComponent>().each([&](const entt::entity, |  | ||||||
| 	                                                       surface::SurfaceComponent &surface) { |  | ||||||
| 		for (const auto &event : surface.peek_events()) | 		for (const auto &event : surface.peek_events()) | ||||||
| 		{ | 		{ | ||||||
| 			handle_event(event); | 			handle_event(event); | ||||||
| 		} | 		} | ||||||
| 	}); | 	} | ||||||
| 
 | 
 | ||||||
| 	m_registry->view<InputComponent>().each([&](const entt::entity, InputComponent &input) { | 	for (auto &[entity, input] : m_registry->view<InputComponent>()) | ||||||
|  | 	{ | ||||||
| 		// TODO(Light): instead of iterating over all actions each frame,
 | 		// TODO(Light): instead of iterating over all actions each frame,
 | ||||||
| 		// make a list of "dirty" actions to reset
 | 		// make a list of "dirty" actions to reset
 | ||||||
| 		// and a surface_input->input_action mapping to get to action through input
 | 		// and a surface_input->input_action mapping to get to action through input
 | ||||||
|  | @ -48,9 +50,14 @@ auto System::tick() -> bool | ||||||
| 				action.state = InputAction::State::inactive; | 				action.state = InputAction::State::inactive; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	}); | 	} | ||||||
| 
 | 
 | ||||||
| 	return false; | 	const auto now = std::chrono::steady_clock::now(); | ||||||
|  | 	m_last_tick_result = app::TickResult { | ||||||
|  | 		.info = tick, | ||||||
|  | 		.duration = now - tick.start_time, | ||||||
|  | 		.end_time = now, | ||||||
|  | 	}; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void System::on_register() | void System::on_register() | ||||||
|  |  | ||||||
|  | @ -1,6 +1,10 @@ | ||||||
| #include <ecs/entity.hpp> | #include <ecs/entity.hpp> | ||||||
| #include <input/components.hpp> | #include <input/components.hpp> | ||||||
| #include <input/system.hpp> | #include <input/system.hpp> | ||||||
|  | #include <memory/reference.hpp> | ||||||
|  | #include <memory/scope.hpp> | ||||||
|  | #include <ranges> | ||||||
|  | #include <surface/system.hpp> | ||||||
| #include <test/test.hpp> | #include <test/test.hpp> | ||||||
| 
 | 
 | ||||||
| // NOLINTBEGIN
 | // NOLINTBEGIN
 | ||||||
|  | @ -17,35 +21,49 @@ using test::expect_throw; | ||||||
| using test::Suite; | using test::Suite; | ||||||
| // NOLINTEND
 | // NOLINTEND
 | ||||||
| 
 | 
 | ||||||
|  | [[nodiscard]] auto tick_info() -> app::TickInfo | ||||||
|  | { | ||||||
|  | 	return { | ||||||
|  | 		.delta_time = std::chrono::milliseconds { 16 }, | ||||||
|  | 		.budget = std::chrono::milliseconds { 10 }, | ||||||
|  | 		.start_time = std::chrono::steady_clock::now(), | ||||||
|  | 	}; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| class Fixture | class Fixture | ||||||
| { | { | ||||||
| public: | public: | ||||||
| 	[[nodiscard]] auto registry() -> Ref<ecs::Registry> | 	[[nodiscard]] auto registry() -> memory::Ref<ecs::Registry> | ||||||
| 	{ | 	{ | ||||||
| 		return m_registry; | 		return m_registry; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	auto add_input_component() -> ecs::Entity | 	auto add_input_component() -> ecs::EntityId | ||||||
| 	{ | 	{ | ||||||
| 		auto entity = m_registry->create_entity(""); | 		auto entity = m_registry->create_entity(); | ||||||
| 		entity.add_component<InputComponent>(); | 		m_registry->add<InputComponent>(entity, {}); | ||||||
| 
 | 
 | ||||||
| 		return entity; | 		return entity; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	auto add_surface_component() -> ecs::Entity | 	auto add_surface_component() -> ecs::EntityId | ||||||
| 	{ | 	{ | ||||||
| 		auto entity = m_registry->create_entity(""); | 		auto entity = m_registry->create_entity(); | ||||||
| 		entity.add_component<surface::SurfaceComponent>(surface::SurfaceComponent::CreateInfo {}); | 		m_surface_system.create_surface_component( | ||||||
|  | 		    entity, | ||||||
|  | 		    { .title = "", .resolution = { 20u, 20u } } | ||||||
|  | 		); | ||||||
| 
 | 
 | ||||||
| 		return entity; | 		return entity; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
| 	Ref<ecs::Registry> m_registry = create_ref<ecs::Registry>(); | 	memory::Ref<ecs::Registry> m_registry = memory::create_ref<ecs::Registry>(); | ||||||
|  | 
 | ||||||
|  | 	surface::System m_surface_system = surface::System { m_registry }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| Suite raii = [] { | Suite raii = "raii"_suite = "raii"_suite = [] { | ||||||
| 	Case { "happy path won't throw" } = [&] { | 	Case { "happy path won't throw" } = [&] { | ||||||
| 		System { Fixture {}.registry() }; | 		System { Fixture {}.registry() }; | ||||||
| 	}; | 	}; | ||||||
|  | @ -63,67 +81,76 @@ Suite raii = [] { | ||||||
| 	}; | 	}; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| Suite system_events = [] { | Suite system_events = "system_events"_suite = [] { | ||||||
| 	Case { "on_register won't throw" } = [] { | 	Case { "on_register won't throw" } = [] { | ||||||
| 		auto fixture = Fixture {}; | 		auto fixture = Fixture {}; | ||||||
| 		auto system = System { fixture.registry() }; | 		auto registry = fixture.registry(); | ||||||
|  | 		auto system = System { registry }; | ||||||
| 
 | 
 | ||||||
| 		system.on_register(); | 		system.on_register(); | ||||||
| 		expect_eq(fixture.registry()->view<InputComponent>().size(), 0); | 		expect_eq(registry->view<InputComponent>().get_size(), 0); | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	Case { "on_unregister won't throw" } = [] { | 	Case { "on_unregister won't throw" } = [] { | ||||||
| 		auto fixture = Fixture {}; | 		auto fixture = Fixture {}; | ||||||
| 		auto system = System { fixture.registry() }; | 		auto registry = fixture.registry(); | ||||||
|  | 		auto system = System { registry }; | ||||||
| 
 | 
 | ||||||
| 		system.on_register(); | 		system.on_register(); | ||||||
| 		system.on_unregister(); | 		system.on_unregister(); | ||||||
| 		expect_eq(fixture.registry()->view<InputComponent>().size(), 0); | 		expect_eq(registry->view<InputComponent>().get_size(), 0); | ||||||
| 	}; | 	}; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| Suite registry_events = [] { | Suite registry_events = "registry_events"_suite = [] { | ||||||
| 	Case { "on_construct<InputComnent>" } = [] { | 	Case { "on_construct<InputComnent>" } = [] { | ||||||
| 		auto fixture = Fixture {}; | 		auto fixture = Fixture {}; | ||||||
| 		auto system = System { fixture.registry() }; | 		auto registry = fixture.registry(); | ||||||
|  | 		auto system = System { registry }; | ||||||
| 
 | 
 | ||||||
| 		const auto &entity = fixture.add_input_component(); | 		const auto &entity = fixture.add_input_component(); | ||||||
| 		expect_eq(fixture.registry()->view<InputComponent>().size(), 1); | 		expect_eq(registry->view<InputComponent>().get_size(), 1); | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	Case { "on_destrroy<InputComponent>" } = [] { | 	Case { "on_destrroy<InputComponent>" } = [] { | ||||||
| 		auto fixture = Fixture {}; | 		auto fixture = Fixture {}; | ||||||
| 		auto system = create_scope<System>(fixture.registry()); | 		auto registry = fixture.registry(); | ||||||
|  | 		auto system = memory::create_scope<System>(registry); | ||||||
| 
 | 
 | ||||||
| 		auto entity_a = fixture.add_input_component(); | 		auto entity_a = fixture.add_input_component(); | ||||||
| 		auto entity_b = fixture.add_input_component(); | 		auto entity_b = fixture.add_input_component(); | ||||||
| 		expect_eq(fixture.registry()->view<InputComponent>().size(), 2); | 		expect_eq(registry->view<InputComponent>().get_size(), 2); | ||||||
| 
 | 
 | ||||||
| 		entity_a.remove_component<InputComponent>(); | 		registry->remove<InputComponent>(entity_a); | ||||||
| 		expect_eq(fixture.registry()->view<InputComponent>().size(), 1); | 		expect_eq(registry->view<InputComponent>().get_size(), 1); | ||||||
| 
 | 
 | ||||||
| 		system.reset(); | 		system.reset(); | ||||||
| 		expect_eq(fixture.registry()->view<InputComponent>().size(), 1); | 		expect_eq(registry->view<InputComponent>().get_size(), 1); | ||||||
| 
 | 
 | ||||||
| 		entity_b.remove_component<InputComponent>(); | 		registry->remove<InputComponent>(entity_b); | ||||||
| 		expect_eq(fixture.registry()->view<InputComponent>().size(), 0); | 		expect_eq(registry->view<InputComponent>().get_size(), 0); | ||||||
| 	}; | 	}; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| Suite tick = [] { | Suite tick = "tick"_suite = [] { | ||||||
| 	Case { "Empty tick won't throw" } = [] { | 	Case { "Empty tick won't throw" } = [] { | ||||||
| 		auto fixture = Fixture {}; | 		auto fixture = Fixture {}; | ||||||
|  | 		auto registry = fixture.registry(); | ||||||
| 		auto system = System { fixture.registry() }; | 		auto system = System { fixture.registry() }; | ||||||
| 
 | 
 | ||||||
| 		expect_false(system.tick()); | 		system.tick(tick_info()); | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	Case { "Tick triggers input action" } = [] { | 	Case { "Tick triggers input action" } = [] { | ||||||
| 		auto fixture = Fixture {}; | 		auto fixture = Fixture {}; | ||||||
|  | 		auto registry = fixture.registry(); | ||||||
| 		auto system = System { fixture.registry() }; | 		auto system = System { fixture.registry() }; | ||||||
| 
 | 
 | ||||||
| 		auto &surface = fixture.add_surface_component().get_component<surface::SurfaceComponent>(); | 		auto surface_entity = fixture.add_surface_component(); | ||||||
| 		auto &input = fixture.add_input_component().get_component<InputComponent>(); | 		auto &surface = registry->get<surface::SurfaceComponent>(surface_entity); | ||||||
|  | 
 | ||||||
|  | 		auto input_entity = fixture.add_input_component(); | ||||||
|  | 		auto &input = registry->get<InputComponent>(input_entity); | ||||||
| 
 | 
 | ||||||
| 		auto action_key = input.add_action( | 		auto action_key = input.add_action( | ||||||
| 		    { | 		    { | ||||||
|  | @ -133,32 +160,37 @@ Suite tick = [] { | ||||||
| 		); | 		); | ||||||
| 
 | 
 | ||||||
| 		expect_eq(input.get_action(action_key).state, input::InputAction::State::inactive); | 		expect_eq(input.get_action(action_key).state, input::InputAction::State::inactive); | ||||||
| 		system.tick(); | 		system.tick(tick_info()); | ||||||
| 		expect_eq(input.get_action(action_key).state, input::InputAction::State::inactive); | 		expect_eq(input.get_action(action_key).state, input::InputAction::State::inactive); | ||||||
| 
 | 
 | ||||||
| 		surface.push_event(surface::KeyPressedEvent(69)); | 		surface.push_event(surface::KeyPressedEvent(69)); | ||||||
| 		system.tick(); | 		system.tick(tick_info()); | ||||||
| 		expect_eq(input.get_action(action_key).state, input::InputAction::State::triggered); | 		expect_eq(input.get_action(action_key).state, input::InputAction::State::triggered); | ||||||
| 
 | 
 | ||||||
| 		system.tick(); | 		system.tick(tick_info()); | ||||||
| 		expect_eq(input.get_action(action_key).state, input::InputAction::State::active); | 		expect_eq(input.get_action(action_key).state, input::InputAction::State::active); | ||||||
| 
 | 
 | ||||||
| 		system.tick(); | 		system.tick(tick_info()); | ||||||
| 		system.tick(); | 		system.tick(tick_info()); | ||||||
| 		system.tick(); | 		system.tick(tick_info()); | ||||||
| 		expect_eq(input.get_action(action_key).state, input::InputAction::State::active); | 		expect_eq(input.get_action(action_key).state, input::InputAction::State::active); | ||||||
| 
 | 
 | ||||||
| 		surface.push_event(surface::KeyReleasedEvent(69)); | 		surface.push_event(surface::KeyReleasedEvent(69)); | ||||||
| 		system.tick(); | 		system.tick(tick_info()); | ||||||
| 		expect_eq(input.get_action(action_key).state, input::InputAction::State::inactive); | 		expect_eq(input.get_action(action_key).state, input::InputAction::State::inactive); | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	Case { "Tick triggers" } = [] { | 	Case { "Tick triggers" } = [] { | ||||||
| 		auto fixture = Fixture {}; | 		auto fixture = Fixture {}; | ||||||
|  | 		auto registry = fixture.registry(); | ||||||
| 		auto system = System { fixture.registry() }; | 		auto system = System { fixture.registry() }; | ||||||
| 
 | 
 | ||||||
| 		auto &surface = fixture.add_surface_component().get_component<surface::SurfaceComponent>(); | 		auto surface_entity = fixture.add_surface_component(); | ||||||
| 		auto &input = fixture.add_input_component().get_component<InputComponent>(); | 		auto &surface = registry->get<surface::SurfaceComponent>(surface_entity); | ||||||
|  | 
 | ||||||
|  | 		auto input_entity = fixture.add_input_component(); | ||||||
|  | 		auto &input = registry->get<InputComponent>(input_entity); | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| 		auto action_key = input.add_action( | 		auto action_key = input.add_action( | ||||||
| 		    { | 		    { | ||||||
|  |  | ||||||
|  | @ -1,7 +1,8 @@ | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include <app/system.hpp> | #include <app/system.hpp> | ||||||
| #include <ecs/scene.hpp> | #include <ecs/registry.hpp> | ||||||
|  | #include <memory/reference.hpp> | ||||||
| #include <surface/components.hpp> | #include <surface/components.hpp> | ||||||
| #include <surface/events/keyboard.hpp> | #include <surface/events/keyboard.hpp> | ||||||
| #include <surface/events/mouse.hpp> | #include <surface/events/mouse.hpp> | ||||||
|  | @ -11,14 +12,19 @@ namespace lt::input { | ||||||
| class System: public app::ISystem | class System: public app::ISystem | ||||||
| { | { | ||||||
| public: | public: | ||||||
| 	System(Ref<ecs::Registry> registry); | 	System(memory::Ref<ecs::Registry> registry); | ||||||
| 
 | 
 | ||||||
| 	auto tick() -> bool override; | 	void tick(app::TickInfo tick) override; | ||||||
| 
 | 
 | ||||||
| 	void on_register() override; | 	void on_register() override; | ||||||
| 
 | 
 | ||||||
| 	void on_unregister() override; | 	void on_unregister() override; | ||||||
| 
 | 
 | ||||||
|  | 	[[nodiscard]] auto get_last_tick_result() const -> const app::TickResult & override | ||||||
|  | 	{ | ||||||
|  | 		return m_last_tick_result; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| private: | private: | ||||||
| 	void handle_event(const surface::SurfaceComponent::Event &event); | 	void handle_event(const surface::SurfaceComponent::Event &event); | ||||||
| 
 | 
 | ||||||
|  | @ -34,13 +40,15 @@ private: | ||||||
| 
 | 
 | ||||||
| 	void on_button_release(const lt::surface::ButtonReleasedEvent &event); | 	void on_button_release(const lt::surface::ButtonReleasedEvent &event); | ||||||
| 
 | 
 | ||||||
| 	Ref<ecs::Registry> m_registry; | 	memory::Ref<ecs::Registry> m_registry; | ||||||
| 
 | 
 | ||||||
| 	std::array<bool, 512> m_keys {}; | 	std::array<bool, 512> m_keys {}; | ||||||
| 
 | 
 | ||||||
| 	std::array<bool, 512> m_buttons {}; | 	std::array<bool, 512> m_buttons {}; | ||||||
| 
 | 
 | ||||||
| 	math::vec2 m_pointer_position; | 	math::vec2 m_pointer_position; | ||||||
|  | 
 | ||||||
|  | 	app::TickResult m_last_tick_result {}; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -35,13 +35,13 @@ public: | ||||||
| 	void static show_imgui_window(); | 	void static show_imgui_window(); | ||||||
| 
 | 
 | ||||||
| 	template<typename... Args> | 	template<typename... Args> | ||||||
| 	void static log(LogLvl lvl, std::format_string<Args...> fmt, Args &&...args) | 	void static log(LogLvl lvl, std::format_string<Args...> fmt, Args &&...args) noexcept | ||||||
| 	{ | 	{ | ||||||
| 		std::ignore = lvl; | 		std::ignore = lvl; | ||||||
| 		std::println(fmt, std::forward<Args>(args)...); | 		std::println(fmt, std::forward<Args>(args)...); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	void static log(LogLvl lvl, const char *message) | 	void static log(LogLvl lvl, const char *message) noexcept | ||||||
| 	{ | 	{ | ||||||
| 		std::ignore = lvl; | 		std::ignore = lvl; | ||||||
| 		std::println("{}", message); | 		std::println("{}", message); | ||||||
|  | @ -53,37 +53,37 @@ private: | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| template<typename... Args> | template<typename... Args> | ||||||
| void log_trc(std::format_string<Args...> fmt, Args &&...args) | void log_trc(std::format_string<Args...> fmt, Args &&...args) noexcept | ||||||
| { | { | ||||||
| 	Logger::log(LogLvl::trace, fmt, std::forward<Args>(args)...); | 	Logger::log(LogLvl::trace, fmt, std::forward<Args>(args)...); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| template<typename... Args> | template<typename... Args> | ||||||
| void log_dbg(std::format_string<Args...> fmt, Args &&...args) | void log_dbg(std::format_string<Args...> fmt, Args &&...args) noexcept | ||||||
| { | { | ||||||
| 	Logger::log(LogLvl::debug, fmt, std::forward<Args>(args)...); | 	Logger::log(LogLvl::debug, fmt, std::forward<Args>(args)...); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| template<typename... Args> | template<typename... Args> | ||||||
| void log_inf(std::format_string<Args...> fmt, Args &&...args) | void log_inf(std::format_string<Args...> fmt, Args &&...args) noexcept | ||||||
| { | { | ||||||
| 	Logger::log(LogLvl::info, fmt, std::forward<Args>(args)...); | 	Logger::log(LogLvl::info, fmt, std::forward<Args>(args)...); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| template<typename... Args> | template<typename... Args> | ||||||
| void log_wrn(std::format_string<Args...> fmt, Args &&...args) | void log_wrn(std::format_string<Args...> fmt, Args &&...args) noexcept | ||||||
| { | { | ||||||
| 	Logger::log(LogLvl::warn, fmt, std::forward<Args>(args)...); | 	Logger::log(LogLvl::warn, fmt, std::forward<Args>(args)...); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| template<typename... Args> | template<typename... Args> | ||||||
| void log_err(std::format_string<Args...> fmt, Args &&...args) | void log_err(std::format_string<Args...> fmt, Args &&...args) noexcept | ||||||
| { | { | ||||||
| 	Logger::log(LogLvl::error, fmt, std::forward<Args>(args)...); | 	Logger::log(LogLvl::error, fmt, std::forward<Args>(args)...); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| template<typename... Args> | template<typename... Args> | ||||||
| void log_crt(std::format_string<Args...> fmt, Args &&...args) | void log_crt(std::format_string<Args...> fmt, Args &&...args) noexcept | ||||||
| { | { | ||||||
| 	Logger::log(LogLvl::critical, fmt, std::forward<Args>(args)...); | 	Logger::log(LogLvl::critical, fmt, std::forward<Args>(args)...); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -31,25 +31,29 @@ namespace lt::math { | ||||||
|  * |  * | ||||||
|  * the 1 at [z][3] is to save the Z axis into the resulting W for perspective division. |  * the 1 at [z][3] is to save the Z axis into the resulting W for perspective division. | ||||||
|  * |  * | ||||||
|  * thanks to pikuma: https://www.youtube.com/watch?v=EqNcqBdrNyI
 |  * @ref Thanks to pikuma for explaining the math behind this: | ||||||
|  |  * https://www.youtube.com/watch?v=EqNcqBdrNyI
 | ||||||
|  */ |  */ | ||||||
| template<typename T> | template<typename T> | ||||||
| constexpr auto perspective(T field_of_view, T aspect_ratio, T z_near, T z_far) | constexpr auto perspective(T field_of_view, T aspect_ratio, T z_near, T z_far) | ||||||
| { | { | ||||||
| 	const T half_fov_tan = std::tan(field_of_view / static_cast<T>(2)); | 	const T half_fov_tan = std::tan(field_of_view / static_cast<T>(2)); | ||||||
| 
 | 
 | ||||||
| 	auto result = mat4_impl<T> { T { 0 } }; | 	auto result = mat4_impl<T>::identity(); | ||||||
| 
 | 
 | ||||||
| 	result[0][0] = T { 1 } / (aspect_ratio * half_fov_tan); | 	result[0][0] = T { 1 } / (aspect_ratio * half_fov_tan); | ||||||
| 
 | 	//
 | ||||||
| 	result[1][1] = T { 1 } / (half_fov_tan); | 	result[1][1] = T { 1 } / (half_fov_tan); | ||||||
| 
 | 	//
 | ||||||
| 	result[2][2] = -(z_far + z_near) / (z_far - z_near); | 	//	result[2][2] = -(z_far + z_near) / (z_far - z_near);
 | ||||||
| 
 | 	//
 | ||||||
|  | 	result[2][2] = z_far / (z_far - z_near); | ||||||
|  | 	//
 | ||||||
| 	result[2][3] = -T { 1 }; | 	result[2][3] = -T { 1 }; | ||||||
| 
 | 	//
 | ||||||
| 	result[3][2] = -(T { 2 } * z_far * z_near) / (z_far - z_near); | 	// result[3][2] = -(T { 2 } * z_far * z_near) / (z_far - z_near);
 | ||||||
| 
 | 	result[3][2] = -(z_far * z_near) / (z_far - z_near); | ||||||
|  | 	//
 | ||||||
| 	return result; | 	return result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -9,6 +9,7 @@ template<typename T = float> | ||||||
| struct mat4_impl | struct mat4_impl | ||||||
| { | { | ||||||
| 	using Column_T = vec4_impl<T>; | 	using Column_T = vec4_impl<T>; | ||||||
|  | 
 | ||||||
| 	constexpr explicit mat4_impl(T scalar = 0) | 	constexpr explicit mat4_impl(T scalar = 0) | ||||||
| 	    : values( | 	    : values( | ||||||
| 	          { | 	          { | ||||||
|  | @ -43,7 +44,7 @@ struct mat4_impl | ||||||
| 	{ | 	{ | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	[[nodiscard]] constexpr auto identity() -> mat4_impl<T> | 	[[nodiscard]] static constexpr auto identity() -> mat4_impl<T> | ||||||
| 	{ | 	{ | ||||||
| 		return mat4_impl<T> { | 		return mat4_impl<T> { | ||||||
| 			{ 1 }, {},    {},    {},    //
 | 			{ 1 }, {},    {},    {},    //
 | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
|  | #include <cstdint> | ||||||
| #include <math/vec2.hpp> | #include <math/vec2.hpp> | ||||||
| 
 | 
 | ||||||
| namespace lt::math { | namespace lt::math { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include <array> | #include <array> | ||||||
|  | #include <cstdint> | ||||||
| 
 | 
 | ||||||
| namespace lt::math { | namespace lt::math { | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										1
									
								
								modules/memory/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								modules/memory/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | ||||||
|  | add_library_module(memory) | ||||||
							
								
								
									
										94
									
								
								modules/memory/public/pointer_types/null_on_move.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								modules/memory/public/pointer_types/null_on_move.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,94 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | namespace lt::memory { | ||||||
|  | 
 | ||||||
|  | /** Holds an `Underlying_T`, assigns it to `null_value` when this object is moved.
 | ||||||
|  |  * | ||||||
|  |  * @note For avoiding the need to explicitly implement the move constructor for objects that hold | ||||||
|  |  * Vulkan objects. But may server other purposes, hence why I kept the implementation generic. | ||||||
|  |  */ | ||||||
|  | template<typename Underlying_T, Underlying_T null_value = nullptr> | ||||||
|  | class NullOnMove | ||||||
|  | { | ||||||
|  | public: | ||||||
|  | 	NullOnMove() = default; | ||||||
|  | 
 | ||||||
|  | 	NullOnMove(Underlying_T value): m_value(value) | ||||||
|  | 	{ | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	~NullOnMove() = default; | ||||||
|  | 
 | ||||||
|  | 	NullOnMove(const NullOnMove &) = delete; | ||||||
|  | 
 | ||||||
|  | 	auto operator=(const NullOnMove &) -> NullOnMove & = delete; | ||||||
|  | 
 | ||||||
|  | 	NullOnMove(NullOnMove &&other) noexcept | ||||||
|  | 	{ | ||||||
|  | 		*this = std::move(other); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	auto operator=(NullOnMove &&other) noexcept -> NullOnMove & | ||||||
|  | 	{ | ||||||
|  | 		if (this == std::addressof(other)) | ||||||
|  | 		{ | ||||||
|  | 			return *this; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		m_value = other.m_value; | ||||||
|  | 		other.m_value = null_value; | ||||||
|  | 
 | ||||||
|  | 		return *this; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	auto operator->() -> Underlying_T | ||||||
|  | 	{ | ||||||
|  | 		return m_value; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// NOLINTNEXTLINE
 | ||||||
|  | 	auto operator->() const -> const Underlying_T | ||||||
|  | 	{ | ||||||
|  | 		return m_value; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	auto operator&() const -> const Underlying_T * | ||||||
|  | 	{ | ||||||
|  | 		return &m_value; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	auto operator&() -> Underlying_T * | ||||||
|  | 	{ | ||||||
|  | 		return &m_value; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	operator bool() const | ||||||
|  | 	{ | ||||||
|  | 		return m_value != null_value; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	operator Underlying_T() const | ||||||
|  | 	{ | ||||||
|  | 		return m_value; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	operator Underlying_T() | ||||||
|  | 	{ | ||||||
|  | 		return m_value; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	operator uint64_t() const | ||||||
|  | 	{ | ||||||
|  | 		return (uint64_t)m_value; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	[[nodiscard]] auto get() -> Underlying_T | ||||||
|  | 	{ | ||||||
|  | 		return m_value; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  | 	Underlying_T m_value; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace lt::memory
 | ||||||
Some files were not shown because too many files have changed in this diff Show more
		Loading…
	
	Add table
		
		Reference in a new issue