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