Compare commits

..

173 commits

Author SHA1 Message Date
d029c0e473 build: add /EHsc and DriveMode: cl to .clangd file
Some checks are pending
continuous-integration/drone/push Build is running
2025-10-30 18:09:40 +03:30
604ee5e6a1 fix(renderer/vk/raii): special member function issues
Some checks are pending
continuous-integration/drone/push Build is running
2025-10-30 13:45:48 +03:30
7ee4381bbf feat(renderer/vk): add some vulkan functions 2025-10-30 13:45:48 +03:30
8730d31e2f refactor(renderer/vk/gpu): quality of life modifications 2025-10-30 13:45:48 +03:30
f50208653e build: add .clangd to fix clangd lsp issues on Windows
Some checks are pending
continuous-integration/drone/push Build is running
2025-10-29 23:17:37 +03:30
5422792705
feat(renderer): storage & staging buffer types
Some checks reported errors
continuous-integration/drone/push Build was killed
2025-10-27 23:29:08 +03:30
2ddb90faff
chore: remove code of conduct
Some checks failed
continuous-integration/drone/push Build is failing
2025-10-26 16:44:12 +03:30
736c37d2f1
feat(renderer/vk): dynamic rendering
Some checks failed
continuous-integration/drone/push Build is failing
2025-10-26 16:43:56 +03:30
97ca429d38 feat: frame constants & camera component (#62)
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #62
Co-authored-by: light7734 <light7734@tuta.io>
Co-committed-by: light7734 <light7734@tuta.io>
2025-10-26 06:56:18 +00:00
5a404d5269 feat(renderer): buffer (#61)
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #61
Co-authored-by: light7734 <light7734@tuta.io>
Co-committed-by: light7734 <light7734@tuta.io>
2025-10-25 12:56:14 +00:00
a9e27d6935
fix: old baked shaders
Some checks failed
continuous-integration/drone/push Build is failing
2025-10-22 06:08:10 +03:30
80662983a3
ci: revert commenting the entire .drone.yml
Some checks reported errors
continuous-integration/drone/push Build was killed
2025-10-22 05:58:48 +03:30
c39ce89a9b
chore(ci): bump c++ standard version to 26
Some checks reported errors
continuous-integration/drone/push Build was killed
2025-10-22 05:55:20 +03:30
f1a91c9b81 ci(amd64/clang/msan): fix msan errors (#59)
Some checks reported errors
continuous-integration/drone/push Build was killed
Reviewed-on: #59
Co-authored-by: light7734 <light7734@tuta.io>
Co-committed-by: light7734 <light7734@tuta.io>
2025-10-21 09:49:42 +00:00
ec5483d13f refactor(ci): fix shellcheck analysis
Some checks reported errors
continuous-integration/drone/push Build was killed
2025-10-16 14:42:23 +03:30
598e1b232d ci: add shell check 2025-10-16 14:41:33 +03:30
3f5a85197a style: format tools/cmake files 2025-10-16 14:41:33 +03:30
479a15bfd0 style: format shell scripts 2025-10-16 14:41:30 +03:30
30548ea4db ci: add shell_format check 2025-10-16 14:40:11 +03:30
9de1bc7ba7 chore: remove test.sh 2025-10-16 14:40:11 +03:30
4b5d380a0e style: apply cmake-format to all cmake files 2025-10-16 14:40:11 +03:30
2612a19f3c ci: add cmake_format check 2025-10-16 14:40:11 +03:30
bd8a111607 refactor: fix some static analysis errors 2025-10-16 14:40:11 +03:30
3066153d6c refactor: fix some static analyzer errors 2025-10-16 14:40:11 +03:30
d5dc37d081 chore: remove build_ci_images.sh 2025-10-16 14:40:11 +03:30
b393cbb31c ci(amd64/gcc/valgrind): fix valgrind errors (#58)
Some checks reported errors
continuous-integration/drone/push Build was killed
Reviewed-on: #58
Co-authored-by: light7734 <light7734@tuta.io>
Co-committed-by: light7734 <light7734@tuta.io>
2025-10-09 15:51:01 +00:00
847ad7dd74 ci(amd64/clang/lsan): fix leak sanitizer errors (#57)
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #57
Co-authored-by: light7734 <light7734@tuta.io>
Co-committed-by: light7734 <light7734@tuta.io>
2025-10-09 14:08:14 +00:00
80a3afbb75
ci: wip
Some checks failed
continuous-integration/drone/push Build is failing
2025-10-08 20:01:00 +03:30
a6a1a9e243
ci: wip
Some checks failed
continuous-integration/drone/push Build is failing
2025-10-08 19:56:32 +03:30
2890853ffe
ci: wip
Some checks failed
continuous-integration/drone/push Build is failing
2025-10-08 19:52:32 +03:30
49596c31f3
ci(amd64/clang/lsan): attempt to fix lsan
Some checks failed
continuous-integration/drone/push Build is failing
2025-10-08 13:35:42 +03:30
a19e095a8e
ci(amd64/gcc/valgrind): modify suppression file
Some checks failed
continuous-integration/drone/push Build is failing
2025-10-08 13:25:10 +03:30
b6976c01c4
refactor(renderer): do not call dlclose for vulkan
Some checks reported errors
continuous-integration/drone/push Build was killed
2025-10-08 13:16:25 +03:30
36d2d81e8a
ci(amd64/gcc/valgrind): fix suppression file
Some checks failed
continuous-integration/drone/push Build is failing
2025-10-08 13:03:14 +03:30
cbe391ba32
ci(amd64/clang/lsan): fix suppression path
Some checks reported errors
continuous-integration/drone/push Build was killed
2025-10-08 13:01:17 +03:30
b763b10034
ci(amd64/gcc/valgrind): add suppression for XFlush
Some checks failed
continuous-integration/drone/push Build is failing
2025-10-08 12:48:22 +03:30
400b49f1d8
ci(amd64/gcc/valgrind): update suppression file
Some checks reported errors
continuous-integration/drone/push Build was killed
2025-10-08 12:44:44 +03:30
94335375ec
ci(amd64/gcc/valgrind): add suppression for XFlush
Some checks failed
continuous-integration/drone/push Build is failing
2025-10-08 11:25:11 +03:30
df056d08ed
ci(amd64/gcc/valgrind): add suppression for XFlush
Some checks failed
continuous-integration/drone/push Build is failing
2025-10-08 11:12:23 +03:30
686ccc8f71
ci(amd64/gcc/valgrind): set --num-callers to 50
Some checks failed
continuous-integration/drone/push Build is failing
2025-10-08 10:53:19 +03:30
23d84c251a
ci(amd64/clang/lsan): add suppression rules for dlopen/close and xlib
Some checks failed
continuous-integration/drone/push Build is failing
2025-10-08 10:21:47 +03:30
07a3dfcb36
refactor(renderer): dlclose when destroying vulkan instance
Some checks failed
continuous-integration/drone/push Build is failing
2025-10-08 09:59:14 +03:30
e70438706c
ci(amd64/gcc/valgrind): add suppressions for xlib & dlopen nonsense
Some checks reported errors
continuous-integration/drone/push Build was killed
2025-10-08 09:58:51 +03:30
56ef217142
ci(amd64/clang/coverage): fix path
Some checks failed
continuous-integration/drone/push Build is failing
2025-10-08 09:15:38 +03:30
38ec10f4fd
ci(amd64/clang/coverage): fix path
Some checks failed
continuous-integration/drone/push Build is failing
2025-10-08 09:08:18 +03:30
00332ee958
ci(amd64/clang/coverage): fix path 2025-10-08 09:07:42 +03:30
9cbebaef06
ci: fix paths
Some checks reported errors
continuous-integration/drone/push Build was killed
2025-10-08 09:02:15 +03:30
b804360884
ci: do not cd into build
Some checks reported errors
continuous-integration/drone/push Build was killed
2025-10-08 08:58:27 +03:30
dc7c6ff0aa
data(test): add triangle shaders
Some checks failed
continuous-integration/drone/push Build is failing
2025-10-08 08:24:22 +03:30
0471969615
refactor(mirror): adjust to recent changes
Some checks reported errors
continuous-integration/drone/push Build was killed
2025-10-08 08:22:16 +03:30
eb9e358d83
refactor(renderer): turn debug messenger from component to normal object 2025-10-08 08:22:03 +03:30
fc0e63455b
test(ecs): fix a testcase
Some checks failed
continuous-integration/drone/push Build is failing
2025-10-08 06:50:13 +03:30
487f907ffb
build(time): link tbb to fix gcc compilation issues
Some checks failed
continuous-integration/drone/push Build is failing
2025-10-08 06:30:30 +03:30
20ef8c04d8
build(test): link tbb to fix gcc compilation issues 2025-10-08 06:30:11 +03:30
77c04d38c9
refactor(mirror): adjust to recent changes 2025-10-08 06:29:36 +03:30
81811351b8
fix(renderer): compilation issues on gcc 2025-10-08 06:29:20 +03:30
e1360cabce
fix(input): adjust to recent changes + link tbb to fix gcc compilation 2025-10-08 06:29:00 +03:30
054af3fd8f
fix(assets): a mistake in tests 2025-10-08 06:28:34 +03:30
5fbd9282d1
build(asset_baker): link tbb to fix gcc compilation 2025-10-08 06:27:42 +03:30
7132bd3324
feat(ecs): add Entity::id for retrieving the entity identifier 2025-10-08 06:27:25 +03:30
3a06d51ee4
fix(surface): some errors & some problems with tests 2025-10-08 06:27:03 +03:30
61473c2758
test(renderer): overhaul tests & fix many bugs
Some checks failed
continuous-integration/drone/push Build is failing
2025-10-07 16:09:50 +03:30
e7c61b2faf
refactor(test): remove constexpr from throwing function
Some checks reported errors
continuous-integration/drone/push Build was killed
continuous-integration/drone Build is failing
2025-10-06 12:24:31 +03:30
41575df141
refactor(mirror): adjust to recent changes
Some checks reported errors
continuous-integration/drone/push Build was killed
2025-10-06 12:00:17 +03:30
237d852ede
fix(ecs): revert mistakenly removing return statement in Entity::add 2025-10-06 11:59:57 +03:30
879d375b7f
feat(renderer): made messenger component working + bug fixes 2025-10-06 11:59:28 +03:30
8defb9a3ec
refactor(surface): made surface component creation only possible through surface system 2025-10-06 11:58:43 +03:30
68c49ebdfb
fix: forgot to stage this
Some checks reported errors
continuous-integration/drone/push Build was killed
2025-10-05 10:07:48 +03:30
d506d6a6a7
refactor: split base module into std, bitwise, memory & env 2025-10-05 10:07:36 +03:30
16f3a80fd3
feat(renderer): separate things into backend/frontend + major vk backend refactors
Some checks reported errors
continuous-integration/drone/push Build was killed
2025-10-04 22:08:19 +03:30
9ca94dc7d7
refactor(surface&renderer): resizing issues
Some checks reported errors
continuous-integration/drone/push Build was killed
2025-10-03 13:26:07 +03:30
b05762c95b
feat(memory): add a get method to null_on_move due to weird cast required by vk_debug_set_object_name
Some checks reported errors
continuous-integration/drone/push Build was killed
2025-10-02 23:54:08 +03:30
6af758643e
feat(mirror) add renderer system 2025-10-02 23:53:44 +03:30
ef2f728cd6
feat(renderer): swapchain recreation, bug fixes & other stuff 2025-10-02 23:53:28 +03:30
1ce8aed8a2
test: add test data
Some checks reported errors
continuous-integration/drone/push Build was killed
2025-10-01 17:32:38 +03:30
c4403b7c90
build: add asset modules 2025-10-01 17:32:02 +03:30
ebf1f54d31
feat(renderer): vulkan triangle 2025-10-01 17:31:46 +03:30
01db551fa9
chore(data): remove unused shaders 2025-10-01 17:30:50 +03:30
4ad50122ef
refactor(asset_baker): removed unused cod & add shader baking support through system glslc calls 2025-10-01 17:30:22 +03:30
0c4b3dd0f9
feat: assets module 2025-10-01 17:29:45 +03:30
0fe399a33e
chore(asset_parser): removed for rewrite 2025-10-01 17:29:35 +03:30
85dbe47990
chore(asset_manager): delete unused module 2025-10-01 17:29:04 +03:30
83a872f3f3
docs: include stoic touch 2025-10-01 11:33:52 +03:30
4976773218
feat(test): minor additions
Some checks reported errors
continuous-integration/drone/push Build was killed
2025-09-30 14:04:38 +03:30
5148b8836c
feat(renderer): contextn, test utils, refactors & changes 2025-09-30 14:04:22 +03:30
405c707e23
style: minor renames 2025-09-30 14:03:39 +03:30
21a82ff57d
tests: add names to all test suites 2025-09-30 06:44:09 +03:30
a46f36aefd
feat(test): add regex filtering for suites and cases 2025-09-30 06:37:27 +03:30
723ade84ea
feat(test): add option parsing && the --stop-on-fail & -h options 2025-09-29 09:47:59 +03:30
cce627a350
refactor(renderer): null initialize vk functions 2025-09-28 23:35:32 +03:30
a77abe312b
fix(surface): not checking if surface component has initialized x11 native data 2025-09-27 23:32:21 +03:30
84d0026051
refactor(surface): wrap typedef struct _XDisplay inside #ifdef guards for linux 2025-09-25 22:24:03 +03:30
34fa8344ac
feat(ecs): add "add" function to Entity 2025-09-25 22:23:32 +03:30
fa1bfaae1e
refactor(renderer): split up context into multiple objectes & fix some edge cases 2025-09-25 22:23:08 +03:30
d411c9ab2c
chore(renderer): remove legacy code-base 2025-09-25 22:13:50 +03:30
607e6864b4
feat(renderer/vk): create swapchain images
Some checks reported errors
continuous-integration/drone/push Build was killed
2025-09-25 11:41:25 +03:30
f268724034
fix(renderer): calling vkDestroyInstance causes XCloseWindow to segfault
Some checks failed
continuous-integration/drone/push Build is failing
ref: 0017308648
ref: https://github.com/KhronosGroup/Vulkan-LoaderAndValidationLayers/issues/1894
ref: https://www.xfree86.org/4.7.0/DRI11.html
2025-09-24 15:08:15 +03:30
030556c733
feat(renderer): swapchain creation
Some checks failed
continuous-integration/drone/push Build is failing
2025-09-24 14:37:16 +03:30
26dd49188b
fix(renderer): minor mistake 2025-09-24 10:43:58 +03:30
131d3472ac
refactor(renderer): minor code refactors 2025-09-24 10:39:45 +03:30
bf6f2e9981
fix(memory): using the overloaded & instead of std::addressof
Some checks failed
continuous-integration/drone/push Build is failing
2025-09-24 10:34:28 +03:30
bf8ffc3dc9
fix(memory): move construct of NullOnMove
Some checks failed
continuous-integration/drone/push Build is failing
2025-09-24 10:31:52 +03:30
963032617e
fix(surface): XDefaultRootWindow being called even if XOpenDisplay fails
Some checks failed
continuous-integration/drone/push Build is failing
2025-09-24 10:11:59 +03:30
55d68e3b71
refactor(renderer): fix clang-tidy checks in system tests 2025-09-24 10:08:48 +03:30
6e838afbab
ci: add full gdb backtrace to unit tests
Some checks failed
continuous-integration/drone/push Build is failing
2025-09-24 10:07:52 +03:30
c2f2abedd7
ci(amd64/gcc/unit_tests): made backtraces full and set off pagination
Some checks failed
continuous-integration/drone/push Build is failing
2025-09-24 10:02:57 +03:30
fc0f039395
ci(amd64): clean up Dockerfile
Some checks failed
continuous-integration/drone/push Build is failing
2025-09-24 09:54:21 +03:30
4e96a871c9
ci: uncomment stuff, testing is finished 2025-09-24 09:54:03 +03:30
1765dd0bd0
ci(amd64/gcc/unit_tests): gdb return child process exit code 2025-09-24 09:53:51 +03:30
f04e3652a5
wip
All checks were successful
continuous-integration/drone/push Build is passing
2025-09-24 09:47:24 +03:30
46a8ebf6da
wip
Some checks failed
continuous-integration/drone/push Build is failing
2025-09-24 09:23:43 +03:30
eb7b780a20
wip
Some checks failed
continuous-integration/drone/push Build is failing
2025-09-24 08:58:57 +03:30
1ad45dec5e
wip
Some checks failed
continuous-integration/drone/push Build is failing
2025-09-24 08:38:30 +03:30
c3142f3117
wip
Some checks failed
continuous-integration/drone/push Build is failing
2025-09-24 08:15:05 +03:30
f2ac6daf1f
wip
All checks were successful
continuous-integration/drone/push Build is passing
2025-09-24 08:13:37 +03:30
ac2d5c7c20
wip
Some checks failed
continuous-integration/drone/push Build is failing
2025-09-24 07:45:01 +03:30
65d086c3d4
wip
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2025-09-24 07:43:26 +03:30
3e2cf440c9
wip
Some checks failed
continuous-integration/drone/push Build is failing
2025-09-24 07:37:02 +03:30
19df29495c
wip
Some checks failed
continuous-integration/drone/push Build is failing
2025-09-24 07:35:01 +03:30
1f1535262f
wip: testing
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2025-09-24 07:31:13 +03:30
cda81f7b3c
wip: testing
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2025-09-24 07:30:05 +03:30
fef6c4bf52
wip: testing
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2025-09-24 07:28:59 +03:30
47b8cbc3aa
wip: testing
Some checks failed
continuous-integration/drone/push Build is failing
2025-09-24 07:27:59 +03:30
febe633520
ci(amd64/gcc/unit_tests): log lshw display info
Some checks reported errors
continuous-integration/drone/push Build was killed
2025-09-24 06:06:49 +03:30
b99167fc4f
ci(amd64/gcc/unit_tests): log vulkaninfo summary
Some checks reported errors
continuous-integration/drone/push Build was killed
2025-09-24 05:46:56 +03:30
25e742b8ab
ci(amd64): add vulkan packages to Dockerfile
Some checks failed
continuous-integration/drone/push Build is failing
2025-09-23 23:05:54 +03:30
8063903344
feat: add memory module
Some checks failed
continuous-integration/drone/push Build is failing
2025-09-22 18:53:35 +03:30
f465f152e3
ci(amd64/Dockerfile): add gdb & remove invalid package from pacman packages 2025-09-22 18:52:59 +03:30
d66ef55cc8
refactor(surface): adjusted to new changes 2025-09-22 18:52:22 +03:30
e77a42cf7f
refactor(mirror): adjusted to new changes 2025-09-22 18:52:04 +03:30
53dd008df5
feat(app): add tick info/result and system diagnosis 2025-09-22 18:51:51 +03:30
d924d14ab0
refactor(input): adjusted to recent changes 2025-09-22 18:51:23 +03:30
b6834310a7
feat(ecs): add convenient Entity class 2025-09-22 18:50:59 +03:30
a58b0c030f
feat(renderer): wip hello triangle 2025-09-22 18:50:17 +03:30
dd0f8ebf0a
ci(amd64/gcc/unit_tests): add gdb-backtrace to tests
Some checks failed
continuous-integration/drone/push Build is failing
2025-09-22 13:57:52 +03:30
ca91c5c1d1
ci(static_analysis/clang_tidy): turn clang-tidy static analysis back on
Some checks reported errors
continuous-integration/drone/push Build was killed
2025-09-21 15:37:06 +03:30
85a1bbfcab
refactor: fix all clang-tidy diagnosis 2025-09-21 15:34:08 +03:30
5cb331def9
ci: remove more .dockerfiles 2025-09-21 14:39:07 +03:30
154d6eacf4
ci: remove old .dockerfiles
Some checks reported errors
continuous-integration/drone/push Build was killed
2025-09-21 14:37:36 +03:30
1555f2867d
ci: add unified Dockerfile 2025-09-21 14:36:39 +03:30
b1e0e6a9e0
ci: change .drone.yml script to use the unified dockerfile image
Some checks failed
continuous-integration/drone/push Build is failing
2025-09-21 14:31:25 +03:30
fa8a1c53b4
build: refactor cmake options out of root CMakeLists.txt 2025-09-21 14:29:19 +03:30
e3a20e2c33
chore: remove conanfile.py :D 2025-09-21 14:25:33 +03:30
4ef2bca643
ci: fix even more silly mistakes!
Some checks failed
continuous-integration/drone/push Build is failing
2025-09-21 14:17:09 +03:30
fc01fb6d6e
ci: fix more silly mistakes
Some checks failed
continuous-integration/drone/push Build is failing
2025-09-21 14:15:33 +03:30
57eb9797ca
ci: fix silly mistake
Some checks failed
continuous-integration/drone/push Build is failing
2025-09-21 14:11:58 +03:30
3800c62827
ci(static_analysis/clang_tidy): replace conan commands with cmake
Some checks failed
continuous-integration/drone/push Build is failing
2025-09-21 14:06:27 +03:30
6d301ec510
ci(amd64/gcc/valgrind): replace conan commands with cmake 2025-09-21 14:03:57 +03:30
6537b456f9
ci(amd64/gcc/unit_tests): replace conan commands with cmake 2025-09-21 14:02:30 +03:30
3d3ddd2197
ci(amd64/clang/msan): replace conan commands with cmake 2025-09-21 13:54:57 +03:30
5de1037e93
ci(amd64/clang/lsan): fix
Some checks failed
continuous-integration/drone/push Build is failing
2025-09-21 13:52:32 +03:30
5c96e2deb9
ci(amd64/clang/lsan): replace conan commands with cmake
Some checks failed
continuous-integration/drone/push Build is failing
2025-09-21 13:49:03 +03:30
0700ab282a
ci(amd64/clang/coverage): replace conan commands with cmake
Some checks failed
continuous-integration/drone/push Build is failing
2025-09-21 13:36:18 +03:30
f0f8836042
ci(amd64/clang/coverage): replace conan commands with cmake
Some checks failed
continuous-integration/drone/push Build is failing
2025-09-21 11:03:01 +03:30
dc0258219d
refactor(surface): minor changes
Some checks failed
continuous-integration/drone/push Build is failing
2025-09-21 09:48:55 +03:30
8b98768539
docs(ecs): add some docs 2025-09-21 09:48:40 +03:30
9267214300
docs(ecs): fix typo 2025-09-21 09:47:13 +03:30
04c2e59ada
fix(ecs): sparse_set not properly removing elements 2025-09-21 08:41:56 +03:30
1b6d53f1c1
fix(surface): silly mistake
Some checks failed
continuous-integration/drone/push Build is failing
2025-09-20 15:44:10 +03:30
9badcddeae
refactor(surface): adjust to new ecs
Some checks failed
continuous-integration/drone/push Build is failing
2025-09-20 15:41:34 +03:30
120b6c24d9
fix(camera): compilation issues due to new ecs changes 2025-09-20 15:41:03 +03:30
d72ee8d9ef
refactor(base): add <flat_map> to pch 2025-09-20 15:40:48 +03:30
03225b3ae6
refactor(renderer): replace ecs/scene.hpp with ecs/registry.hpp 2025-09-20 15:40:32 +03:30
7266451b45
refactor(mirror): adjust to new ecs 2025-09-20 15:40:05 +03:30
91d86545dc
refactor(input): adjust to new ecs 2025-09-20 15:39:45 +03:30
21e7291189
feat(ecs): new (simple) implementation without entt 2025-09-20 15:39:18 +03:30
0c35c13ac1
chore: remove entt as dependency 2025-09-20 15:36:15 +03:30
b179149597
fix(debug): ensure taking in Expression_T by copy 2025-09-20 06:08:53 +03:30
ca29c61521
chore: remove default_gui_layout.ini
Some checks reported errors
continuous-integration/drone/push Build was killed
2025-09-19 09:44:39 +03:30
2061fc74c2
chore: remove lz4 as dependency
Some checks failed
continuous-integration/drone/push Build is failing
2025-09-19 09:44:12 +03:30
b570653c82
chore: remove stb as dependency 2025-09-19 09:42:56 +03:30
f47afbdbcc
chore: remove yaml-cpp as dependency 2025-09-19 09:40:26 +03:30
4be35c76c0
chore: remove imgui as conan dependency 2025-09-19 09:30:42 +03:30
289 changed files with 9694 additions and 8524 deletions

5
.clangd Normal file
View file

@ -0,0 +1,5 @@
CompileFlags:
DriverMode: cl
Add:
- /EHsc
- /std:c++latest

View file

@ -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/

View file

@ -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)

View file

@ -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.

View file

@ -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 persons mind so soon and so easily? I tell you, you must not expect it.” —Epictetus, Discourses 1.15.7-8

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -0,0 +1 @@
The quick brown fox jumps over the lazy dog

View 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);
}

Binary file not shown.

View 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];
}

Binary file not shown.

View file

@ -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

View file

@ -1,24 +1,26 @@
# engine
add_subdirectory(./base)
add_subdirectory(./std)
add_subdirectory(./bitwise)
add_subdirectory(./env)
add_subdirectory(./memory)
add_subdirectory(./time)
add_subdirectory(./logger)
add_subdirectory(./debug)
add_subdirectory(./math)
#
#
add_subdirectory(./asset_baker)
add_subdirectory(./asset_parser)
# add_subdirectory(./asset_manager)
#
add_subdirectory(./assets)
#
add_subdirectory(./camera)
add_subdirectory(./input)
# add_subdirectory(./ui)
#
#
add_subdirectory(./surface)
# add_subdirectory(./renderer)
add_subdirectory(./renderer)
add_subdirectory(./ecs)
#
#
add_subdirectory(./app)
# apps
# apps
add_subdirectory(./mirror)
add_subdirectory(test)

View file

@ -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)

View file

@ -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));
}

View file

@ -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;
};

View file

@ -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)
{

View file

@ -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

View file

@ -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)

View file

@ -0,0 +1,2 @@
#include <asset_baker/bakers.hpp>
#include <test/test.hpp>

View file

@ -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;

View file

@ -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;
// Don't bother linking to shaderc, just invoke the command with a system call.
// NOLINTNEXTLINE(concurrency-mt-unsafe)
system(
std::format(
"glslc --target-env=vulkan1.4 -std=450core -fshader-stage={} {} -o {}",
type == vertex ? "vert" : "frag",
glsl_path,
spv_path
)
.c_str()
);
Loader(const Loader &) = delete;
auto stream = std::ifstream(spv_path, std::ios::binary);
lt::ensure(
stream.is_open(),
"Failed to open compiled {} shader at: {}",
type == vertex ? "vert" : "frag",
spv_path
);
auto operator=(Loader &&) -> Loader & = default;
stream.seekg(0, std::ios::end);
const auto size = stream.tellg();
auto operator=(const Loader &) -> Loader & = delete;
auto bytes = std::vector<std::byte>(size);
stream.seekg(0, std::ios::beg);
stream.read((char *)bytes.data(), size); // NOLINT
log_dbg("BYTES: {}", bytes.size());
stream.close();
std::filesystem::remove(spv_path);
virtual ~Loader() = default;
private:
};
class TextureLoader: public Loader
{
public:
TextureLoader() = default;
[[nodiscard]] virtual auto load(std::filesystem::path file_path) const
-> Assets::TextureAsset::PackageData
= 0;
};
class StbLoader: public TextureLoader
{
public:
StbLoader() = default;
void load(std::filesystem::path path);
[[nodiscard]] static auto get_supported_extensions() -> std::unordered_set<std::string_view>
{
return { ".png" };
}
[[nodiscard]] auto get_name() const -> std::string_view override
{
return "StbLoader";
}
[[nodiscard]] auto load(std::filesystem::path file_path) const
-> Assets::TextureAsset::PackageData override
{
auto width = int {};
auto height = int {};
auto channels = int {};
auto *pixels = stbi_load(file_path.string().c_str(), &width, &height, &channels, 4);
if (!pixels)
{
throw std::runtime_error {
std::format("Failed to load image file at: {} using stbi_load", file_path.string()),
};
}
const auto metadata = Assets::Asset::Metadata {
.type = Assets::Asset::Type::Texture,
};
const auto texture_metadata = Assets::TextureAsset::Metadata {
.format = Assets::TextureAsset::Format::RGBA8,
.num_components = static_cast<uint32_t>(channels),
.pixel_size = {
static_cast<uint32_t>(width),
static_cast<uint32_t>(height),
{},
},
};
auto pixels_blob = Assets::Blob {};
pixels_blob.resize(static_cast<size_t>(width) * height * channels);
// TODO(Light): figure out if it's possible to directly populate a blob with stbi functions
memcpy(pixels_blob.data(), pixels, pixels_blob.size());
stbi_image_free(pixels);
return Assets::TextureAsset::PackageData {
.metadata = metadata,
.texture_metadata = texture_metadata,
.pixels = std::move(pixels_blob),
};
}
};
class TextureLoaderFactory
{
public:
static auto create(std::string_view file_extension) -> std::unique_ptr<TextureLoader>
{
if (StbLoader::get_supported_extensions().contains(file_extension))
{
return std::make_unique<StbLoader>();
}
return {};
}
};
class TextLoader: Loader
{
public:
[[nodiscard]] static auto get_supported_extensions() -> std::unordered_set<std::string_view>
{
return { ".glsl", ".txt", ".hlsl" };
}
[[nodiscard]] auto get_name() const -> std::string_view override
{
return "TextLoader";
}
[[nodiscard]] auto load(const std::filesystem::path &file_path) const
-> Assets::TextAsset::PackageData
{
auto stream = std::ifstream { file_path, std::ios::binary };
if (!stream.good())
{
throw std::runtime_error {
std::format(
"Failed to open ifstream for text loading of file: {}",
file_path.string()
),
};
}
auto file_size = std::filesystem::file_size(file_path);
auto text_blob = Assets::Blob(file_size);
stream.read((char *)(text_blob.data()), static_cast<long>(file_size)); // NOLINT
const auto metadata = Assets::Asset::Metadata {
.type = Assets::Asset::Type::Text,
};
const auto text_metadata = Assets::TextAsset::Metadata {
.lines = {},
};
return Assets::TextAsset::PackageData {
.metadata = metadata,
.text_metadata = {},
.text_blob = std::move(text_blob),
};
}
};
class TextLoaderFactory
{
public:
static auto create(std::string_view file_extension) -> std::unique_ptr<TextLoader>
{
if (TextLoader::get_supported_extensions().contains(file_extension))
{
return std::make_unique<TextLoader>();
}
return {};
}
};
} // namespace lt
ShaderAsset::pack(
out_path,
lt::assets::AssetMetadata {
.version = lt::assets::current_version,
.type = ShaderAsset::asset_type_identifier,
},
ShaderAsset::Metadata {
.type = type,
},
std::move(bytes)
);
}

View file

@ -1,9 +0,0 @@
add_library_module(asset_manager
asset_manager.cpp
)
target_link_libraries(
asset_manager
PUBLIC asset_parser
PRIVATE logger
)

View file

@ -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

View file

@ -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

View file

@ -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
)

View file

@ -1,5 +1,4 @@
#include <asset_parser/assets/text.hpp>
#include <lz4.h>
namespace Assets {

View file

@ -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 *)&current_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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -1,14 +0,0 @@
#pragma once
#include <cstdint>
namespace Assets {
enum class CompressionType : uint32_t // NOLINT(performance-enum-size)
{
None,
LZ4,
LZ4HC,
};
}

View file

@ -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

View 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)

View 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

View 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));
}
};
};

View file

@ -0,0 +1,3 @@
#pragma once
// TO BE DOOO

View 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

View 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

View file

@ -1,2 +0,0 @@
add_library_module(base)
target_precompile_headers(base INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/private/pch.hpp)

View file

@ -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>

View file

@ -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

View file

@ -0,0 +1 @@
add_library_module(bitwise)

View 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

View file

@ -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)

View file

@ -1,6 +0,0 @@
#include <camera/camera.hpp>
namespace lt {
}

View file

@ -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

View file

@ -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

View file

@ -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

View 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

View file

@ -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

View file

@ -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)

View file

@ -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()

View file

@ -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)

View file

@ -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

View 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);
};
};

View file

@ -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

View file

@ -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

View 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);
};
};

View file

@ -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

View file

@ -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>

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View 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

View file

@ -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

View file

@ -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

View 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

View file

@ -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
View file

@ -0,0 +1 @@
add_library_module(env)

68
modules/env/public/constants.hpp vendored Normal file
View 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

View file

@ -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)

View file

@ -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()

View file

@ -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(
{

View file

@ -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 {};
};

View file

@ -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)...);
}

View file

@ -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;
}

View file

@ -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 }, {}, {}, {}, //

View file

@ -1,5 +1,6 @@
#pragma once
#include <cstdint>
#include <math/vec2.hpp>
namespace lt::math {

View file

@ -1,6 +1,7 @@
#pragma once
#include <array>
#include <cstdint>
namespace lt::math {

View file

@ -0,0 +1 @@
add_library_module(memory)

View 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