Compare commits

..

1 commit

Author SHA1 Message Date
4ce413a1d7
wip
Some checks are pending
continuous-integration/drone/pr Build is running
2025-10-21 15:37:33 +03:30
248 changed files with 10109 additions and 14215 deletions

View file

@ -1,17 +0,0 @@
# How wide to allow formatted cmake files
line_width: 80
# How many spaces to tab for indent
tab_size: 4
dangle_parens: true
# Additional FLAGS and KWARGS for custom commands
additional_commands:
foo:
flags: [BAR, BAZ]
kwargs:
HEADERS : '*'
SOURCES : '*'
DEPENDS : '*'

View file

@ -1,44 +1,42 @@
--- # ---
kind: pipeline # kind: pipeline
type: exec # type: exec
name: amd64 — msvc # name: amd64 — msvc
node: # trigger:
environment: lina # branch:
trigger: # - main
branch: # platform:
- main # os: windows
platform: # arch: amd64
os: windows #
arch: amd64 # steps:
# - name: unit tests
steps: # shell: powershell
- name: unit tests # commands:
shell: powershell # - ./tools/ci/amd64/msvc/unit_tests.ps1
commands: #
- ./tools/ci/amd64/msvc/unit_tests.ps1 # ---
# kind: pipeline
--- # type: docker
kind: pipeline # name: amd64 — gcc
type: docker # trigger:
name: amd64 — gcc # branch:
trigger: # - main
branch: #
- main # steps:
# - name: unit tests
steps: # image: ci:latest
- name: unit tests # pull: if-not-exists
image: ci:latest # commands:
pull: if-not-exists # - ./tools/ci/amd64/gcc/unit_tests.sh
commands: #
- ./tools/ci/amd64/gcc/unit_tests.sh # - name: valgrind
# image: ci:latest
- name: valgrind # pull: if-not-exists
image: ci:latest # commands:
pull: if-not-exists # - ./tools/ci/amd64/gcc/valgrind.sh
commands: #
- ./tools/ci/amd64/gcc/valgrind.sh # ---
---
kind: pipeline kind: pipeline
type: docker type: docker
name: amd64 — clang name: amd64 — clang
@ -47,110 +45,114 @@ trigger:
- main - main
steps: steps:
- name: code coverage # - name: code coverage
image: ci:latest # image: ci:latest
pull: if-not-exists # pull: if-not-exists
environment: # environment:
CODECOV_TOKEN: # CODECOV_TOKEN:
from_secret: CODECOV_TOKEN # from_secret: CODECOV_TOKEN
commands: # commands:
- ./tools/ci/amd64/clang/coverage.sh # - ./tools/ci/amd64/clang/coverage.sh
#
- name: leak sanitizer # - name: leak sanitizer
image: ci:latest # image: ci:latest
pull: if-not-exists # pull: if-not-exists
commands: # commands:
- ./tools/ci/amd64/clang/lsan.sh # - ./tools/ci/amd64/clang/lsan.sh
#
- name: memory sanitizer - name: memory sanitizer
image: ci:latest image: ci:latest
pull: if-not-exists pull: if-not-exists
commands: commands:
- ./tools/ci/amd64/clang/msan.sh - ./tools/ci/amd64/clang/msan.sh
#
--- # ---
kind: pipeline # kind: pipeline
type: docker # type: docker
name: static analysis # name: static analysis
trigger: # trigger:
branch: # branch:
- main # - main
#
steps: # steps:
- name: clang tidy # - name: clang tidy
image: ci:latest # image: ci:latest
pull: if-not-exists # pull: if-not-exists
privileged: true # privileged: true
commands: # commands:
- ./tools/ci/static_analysis/clang_tidy.sh # - ./tools/ci/static_analysis/clang_tidy.sh
#
- name: shell check # - name: shell check
image: ci:latest # image: ci:latest
pull: if-not-exists # pull: if-not-exists
commands: # commands:
- ./tools/ci/static_analysis/shell_check.sh # - ./tools/ci/static_analysis/shell_check.sh
#
- name: clang format # - name: clang format
image: ci:latest # image: ci:latest
pull: if-not-exists # pull: if-not-exists
commands: # commands:
- ./tools/ci/static_analysis/clang_format.sh # - ./tools/ci/static_analysis/clang_format.sh
#
- name: cmake format # - name: cmake format
image: ci:latest # image: ci:latest
pull: if-not-exists # pull: if-not-exists
commands: # commands:
- ./tools/ci/static_analysis/cmake_format.sh # - ./tools/ci/static_analysis/cmake_format.sh
#
- name: shell format # - name: shell format
image: ci:latest # image: ci:latest
pull: if-not-exists # pull: if-not-exists
commands: # commands:
- ./tools/ci/static_analysis/shell_format.sh # - ./tools/ci/static_analysis/shell_format.sh
#
--- # ---
kind: pipeline # kind: pipeline
type: docker # type: docker
name: documentation — development # name: documentation — development
node: # node:
environment: ryali # environment: ryali
trigger: # trigger:
branch: # branch:
- main # - main
#
steps: # steps:
- name: build and deploy # - name: build and deploy
image: documentation:latest # image: documentation:latest
pull: if-not-exists # pull: if-not-exists
commands: # commands:
- cd docs # - pwd
- sphinx-build -M html . . # - cd docs
# - mkdir generated
- rm -rf /light_docs_dev/* # - touch generated/changelogs.rst
- mv ./html/* /light_docs_dev/ # - touch generated/api.rst
# - sphinx-build -M html . .
--- #
# - rm -rf /light_docs_dev/*
kind: pipeline # - mv ./html/* /light_docs_dev/
type: docker #
name: documentation — production # ---
node: #
environment: ryali # kind: pipeline
trigger: # type: docker
event: # name: documentation — production
- tag # node:
# environment: ryali
steps: # trigger:
- name: build and deploy # event:
image: documentation:latest # - tag
pull: if-not-exists #
commands: # steps:
- cd docs # - name: build and deploy
- mkdir generated # image: documentation:latest
- touch generated/changelogs.rst # pull: if-not-exists
- touch generated/api.rst # commands:
- sphinx-build -M html . . # - cd docs
# - mkdir generated
- rm -rf /light_docs/* # - touch generated/changelogs.rst
- mv ./html/* /light_docs/ # - touch generated/api.rst
# - sphinx-build -M html . .
#
# - rm -rf /light_docs/*
# - mv ./html/* /light_docs/
#

View file

@ -1,10 +1,4 @@
set(CMAKE_CXX_STANDARD 23) cmake_minimum_required(VERSION 3.14)
set(CMAKE_EPXORT_COMPILE_COMMANDS TRUE)
set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "d0edc3af-4c50-42ea-a356-e2862fe7a444")
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
set(CMAKE_CXX_MODULE_STD 1)
cmake_minimum_required(VERSION 4.1)
project(Light) project(Light)
include(${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake/functions.cmake) include(${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake/functions.cmake)

128
CODE_OF_CONDUCT.md Normal file
View file

@ -0,0 +1,128 @@
# 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.

14
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,14 @@
# How to contribute to Light Engine
Thanks for putting in the time to contribute to this project <3
## Coding conventions
For the time being, don't worry too much about the conventions...
Try to read other parts of the code and you'll get the hang of it
I have to learn clang-format, then everyone contributing can use it to format their code
###### happy coding-

View file

@ -1,36 +0,0 @@
#version 450 core
layout(push_constant) uniform pc
{
mat4 view_projection;
};
struct VertexData
{
vec3 position;
vec3 color;
};
// readonly SSBO containing the vertex data
layout(set = 0, binding = 0, std430) readonly buffer vertex_data {
VertexData data[];
};
vec3 position(int idx)
{
return data[idx].position;
}
vec3 color(int idx)
{
return data[idx].color;
}
layout(location = 0) out vec3 out_frag_color;
void main()
{
gl_Position = view_projection * vec4(position(gl_VertexIndex), 1.0);
out_frag_color = color(gl_VertexIndex);
}

Binary file not shown.

Binary file not shown.

View file

@ -1,26 +1,21 @@
#version 450 core #version 450 core
layout(push_constant) uniform pc { vec2 positions[3] = vec2[](
mat4 view_projection; vec2(0.0, -0.5),
}; vec2(0.5, 0.5),
vec2(-0.5, 0.5)
);
struct VertexData vec3 colors[3] = vec3[](
{ vec3(1.0, 0.0, 0.0),
vec3 position; vec3(0.0, 1.0, 0.0),
vec3 color; vec3(0.0, 0.0, 1.0)
}; );
layout(std140, set = 0, binding = 0) readonly buffer Vertices {
VertexData vertices[];
} ssbo_vertices;
layout(location = 0) out vec3 out_frag_color; layout(location = 0) out vec3 out_frag_color;
void main() void main()
{ {
VertexData vertex = ssbo_vertices.vertices[gl_VertexIndex]; gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
out_frag_color = colors[gl_VertexIndex];
gl_Position = view_projection * vec4(vertex.position, 1.0);
out_frag_color = vertex.color;
} }

Binary file not shown.

3
docs/.gitignore vendored
View file

@ -1,4 +1,3 @@
_build/ _build/
generated/ generated/
html/
xml/

View file

@ -1,86 +0,0 @@
TARGET = ./
INPUT = "../modules"
RECURSIVE = YES
PROJECT_NAME = "Light"
JAVADOC_AUTOBRIEF = YES
JAVADOC_BANNER = YES
GENERATE_XML = YES
EXTRACT_PRIVATE = NO
EXTRACT_STATIC = NO
EXTRACT_LOCAL_CLASSES = NO
HIDE_UNDOC_RELATIONS = YES
HAVE_DOT = NO
GENERATE_TODOLIST = NO
GENERATE_HTML = NO
GENERATE_DOCSET = NO
GENERATE_HTMLHELP = NO
GENERATE_CHI = NO
GENERATE_QHP = NO
GENERATE_ECLIPSEHELP = NO
GENERATE_TREEVIEW = NO
GENERATE_LATEX = NO
GENERATE_RTF = NO
GENERATE_MAN = NO
GENERATE_DOCBOOK = NO
GENERATE_AUTOGEN_DEF = NO
GENERATE_SQLITE3 = NO
GENERATE_PERLMOD = NO
GENERATE_TAGFILE = NO
GENERATE_LEGEND = NO
GENERATE_TESTLIST = NO
GENERATE_BUGLIST = NO
GENERATE_DEPRECATEDLIST= NO
FILE_PATTERNS = *.c \
*.cc \
*.cxx \
*.cxxm \
*.cpp \
*.cppm \
*.ccm \
*.c++ \
*.c++m \
*.java \
*.ii \
*.ixx \
*.ipp \
*.i++ \
*.inl \
*.idl \
*.ddl \
*.odl \
*.h \
*.hh \
*.hxx \
*.hpp \
*.h++ \
*.l \
*.cs \
*.d \
*.php \
*.php4 \
*.php5 \
*.phtml \
*.inc \
*.m \
*.markdown \
*.md \
*.mm \
*.dox \
*.py \
*.pyw \
*.f90 \
*.f95 \
*.f03 \
*.f08 \
*.f18 \
*.f \
*.for \
*.vhd \
*.vhdl \
*.ucf \
*.qsf \
*.ice

View file

@ -1,17 +0,0 @@
Application
===================================================================================================
.. toctree::
:maxdepth: 3
:caption: App
Functions
---------------------------------------------------------------------------------------------------
.. doxygenfunction:: main
Classes
---------------------------------------------------------------------------------------------------
.. doxygenclass:: lt::app::ISystem
.. doxygenstruct:: lt::app::TickInfo
.. doxygenstruct:: lt::app::TickResult

View file

@ -1,13 +0,0 @@
Renderer
===================================================================================================
.. toctree::
:maxdepth: 3
:caption: App
Classes
---------------------------------------------------------------------------------------------------
.. doxygenenum:: lt::renderer::Api
.. doxygenclass:: lt::renderer::System
.. doxygenstruct:: lt::renderer::components::Sprite

View file

@ -13,21 +13,13 @@ author = 'light7734'
# -- General configuration --------------------------------------------------- # -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
extensions = ['breathe'] extensions = []
breathe_projects = {"Light": "./xml"}
breathe_default_project = "Light"
breathe_default_members = ()
# Tell sphinx what the primary language being documented is.
primary_domain = 'cpp'
# Tell sphinx what the pygments highlight language should be.
highlight_language = 'cpp'
templates_path = ['_templates'] templates_path = ['_templates']
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# -- Options for HTML output ------------------------------------------------- # -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output

View file

@ -0,0 +1,68 @@
from git import Repo
import re
repo = Repo(search_parent_directories=True)
assert not repo.bare
file_path = "generated/changelog.rst"
messages = []
short_shas = []
hex_shas = []
logs = []
remote_url = "https://git.light7734.com/light7734/light/commit"
def format_log(commit_type, message, major, minor, patch, short_sha, hex_sha):
href = f"{remote_url}/{hex_sha}"
version = f"{major}.{minor}.{patch}-kitten+{short_sha}";
link = f"`{version} <{remote_url}/{hex_sha}>`__"
return f"| **{message}** ({link})"
for commit in repo.iter_commits():
messages.append(commit.summary)
short_shas.append(repo.git.rev_parse(commit.hexsha, short=5))
hex_shas.append(commit.hexsha)
ver_major = 0
ver_minor = 0
ver_patch = 0
idx = len(messages)
for message in reversed(messages):
idx = idx - 1;
commit_type = re.match("^(feat|fix|refactor|perf|build|asset|test|chore|ci|docs)", message)
if not commit_type:
continue
match commit_type.group(0):
case "feat":
ver_minor = ver_minor + 1
ver_patch = 0
case "fix":
ver_patch = ver_patch + 1
case "refactor":
ver_patch = ver_patch + 1
case "perf":
ver_patch = ver_patch + 1
case "build":
ver_patch = ver_patch + 1
case "asset":
ver_patch = ver_patch + 1
logs.append(format_log(commit_type, message, ver_major, ver_minor, ver_patch, short_shas[idx], hex_shas[idx]))
with open(file_path, "w") as f:
f.write(".. changelogs\n\n\n")
f.write("Changelogs\n")
f.write("==================================================\n\n")
f.write("KITTEN\n")
f.write("--------------------------------------------------\n\n")
for log in reversed(logs):
f.write(log + '\n')

View file

@ -1,46 +1,49 @@
.. guidelines/development .. guidelines/development
Development Strategy Development
=================================================================================================== ===================================================================================================
Defines: As a solo-project, I am not only the **developer**, but also the **manager**.
Therefore there is a need, if this project is to succeed, to have a development plan.
- A **unit of work**. Such a plan should:
- A **pipeline** for making changes ---should **minimize ambiguity**.
- A way for **distributing work** and **tracking productivity**.
An **unpragmatic strategy** is utterly useless. It should pull our minds out from the **engineering dreamland**, and make us focus on the **product delivery**. - Define a way to **distribute work** (across time, since there's only 1 developer).
- Define what is a **unit of work** (cycles).
- Provide a way to **track productivity**, which helps projecting the future and **detecting patterns** early on.
- Provide a **pipeline** for the work to go through and **minimize ambiguity**.
.. note:: These are the **management** aspects of the project, which help the development goals to be more **pragmatic**
**Forgejo issues** is used as the project-management tool. ---by pulling my mind out of its **engineering dreamland**, and make it focus on the **broader picture**.
No need for fancy boards or 3rd party apps. Simple tags, titles, milestones, etc... should suffice.
Cycle Cycle
--------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------
A cycle is one **step** in development, 1 cycle == 1 issue, and it consists of 4 stages: A cycle is one **step** in development, one cycle = one ticket, and it consists of 4 stages:
1 - Make it known 1 - Make it known
- Write the commit title. - Write the commit message.
- This limits the **scope of changes** and gives you a very specific **goal** to work towards. - This limits the **scope of changes** and gives you a very specific **goal** to work towards.
- If something outside of this scope really bothers you, fix and stash for a future cycle.
- Make a ticket if stash-fix is implausible ---**DO NOT** write **todo** comments.
- The message should follow the project's **commit message specifications**. - The message should follow the project's **commit message specifications**.
- Make an issue. - Make a ticket.
- Git is a **version-control** tool, not a **project-management** tool. - Version control (git) is a **development-tool**, not a **management-tool**.
- Preferably, provide a very brief description ---This may be used in the commit message's body. - Provide a very brief description ---This may be used in the commit message's body.
2 - Make it work 2 - Make it work
- Write high-level tests that confirms the cycle's requirements are met. - Write high-level tests that confirms the cycle's requirements are met.
- That is, specify requirements in a programming language instead of English. - That is, specify requirements in a programming language instead of English.
- You're done when all the tests pass. - You're done when all the tests pass.
- Preferably write the tests first, but it's okay to start with the interface. - Preferably write the tests first, but it's okay to start with the interface.
- Tests **may** not be necessary depending on the requirements and commit type. - Tests may not be necessary depending on the requirements and commit type.
- **Make it work** doesn't mean liberally producing substandard code, you should: - "Make it work" doesn't mean liberally producing shit code, you should:
- Follow project's **conventions**. - Follow project's **conventions**.
- Follow **best practices** and **proven swe principles**. - Follow **best practices** and **proven swe principles**.
- Enable **warnings as errors**. - Enable **warnings as errors**.
- Enable **static analysis**. - Enable **static analysis**.
- Don't break existing tests. - Don't break any pre-existing-tests.
- Have the overall picture in mind. - Have the over-all picture in mind.
3 - Make it right 3 - Make it right
- Test driven refactoring - Test driven refactoring
@ -52,14 +55,12 @@ A cycle is one **step** in development, 1 cycle == 1 issue, and it consists of 4
- Get a performance and/or memory profile and try to alleviate the bottlenecks. - Get a performance and/or memory profile and try to alleviate the bottlenecks.
- Avoid premature optimizations, be certain what you change has performance benefits. - Avoid premature optimizations, be certain what you change has performance benefits.
Sprint Sprint
--------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------
A sprint is the collection of all the finished cycles in **one week**. A sprint is the collection of all the finished cycles in one week.
They start from **Monday mornings**, and end on **Sunday nights**, It's meant to provide insight on development speed and help projecting the future.
where we'll do a **12hr coding marathon** (streamed on Discord) to wrap everything up.
Sprints begin by **defining** what cycles/issues are expected to be done.
And end by **reflecting** on the results, which may **affect** our future approach.
Commit Message Specification Commit Message Specification
--------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------
@ -115,10 +116,6 @@ With the following commit types:
- For changes to the documentations. - For changes to the documentations.
- Does not affect the version. - Does not affect the version.
.. note::
Since we're in beta, any commit might change the api, no need for ! (breaking) tags.
Semantic Versioning Semantic Versioning
--------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------
Coupled with conventional commit style messages, we can automajically version the project following Coupled with conventional commit style messages, we can automajically version the project following
@ -142,3 +139,9 @@ Using the following format:
The shortened hexsha of a commit is obtained by: The shortened hexsha of a commit is obtained by:
``git rev-parse --short=5 <commit_hexsha>`` ``git rev-parse --short=5 <commit_hexsha>``

View file

@ -23,10 +23,10 @@
guidelines/conventions.rst guidelines/conventions.rst
.. toctree:: .. toctree::
:maxdepth: 3 :maxdepth: 2
:caption: API :caption: Generated Docs
api/app.rst generated/api.rst
api/renderer.rst generated/changelog.rst

35
docs/make.bat Normal file
View file

@ -0,0 +1,35 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=.
set BUILDDIR=_build
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.https://www.sphinx-doc.org/
exit /b 1
)
if "%1" == "" goto help
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
:end
popd

View file

@ -1,262 +1,26 @@
add_module(NAME preliminary INTERFACES module.cppm fundumental_types.cppm assertions.cppm build_constants.cppm) # engine
add_module(NAME logger INTERFACES logger.cppm TESTS logger.test.cpp DEPENDENCIES preliminary) add_subdirectory(./std)
add_module(NAME tracer INTERFACES tracer.cppm DEPENDENCIES preliminary logger) 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(./assets)
#
add_subdirectory(./camera)
add_subdirectory(./input)
# add_subdirectory(./ui)
#
add_subdirectory(./surface)
add_subdirectory(./renderer)
add_subdirectory(./ecs)
#
add_subdirectory(./app)
add_module(NAME bitwise INTERFACES operations.cppm DEPENDENCIES preliminary) # apps
add_module(NAME memory INTERFACES null_on_move.cppm reference.cppm scope.cppm add_subdirectory(./mirror)
DEPENDENCIES add_subdirectory(test)
preliminary
logger
)
add_module(NAME time INTERFACES timer.cppm TESTS timer.test.cpp DEPENDENCIES preliminary)
add_module(
NAME
test
INTERFACES
module.cppm
test.cppm
expects.cppm
registry.cppm
SOURCES
entrypoint.cpp
DEPENDENCIES
preliminary
logger
TESTS
test.test.cpp
)
add_module(
NAME
math
INTERFACES
algebra.cppm
trig.cppm
vec2.cppm
vec3.cppm
vec4.cppm
mat4.cppm
components.cppm
DEPENDENCIES
preliminary
TESTS
trig.test.cpp
vec2.test.cpp
vec3.test.cpp
vec4.test.cpp
mat4.test.cpp
)
add_module(
NAME
assets
INTERFACES
shader.cppm
metadata.cppm
DEPENDENCIES
preliminary
logger
TESTS
shader.test.cpp
)
add_module(
NAME
asset_baker
ROOT_DIR
${CMAKE_CURRENT_SOURCE_DIR}/asset_baker
INTERFACES
bakers.cppm
ENTRYPOINT
entrypoint.cpp
DEPENDENCIES
preliminary
assets
logger
)
# add_executable(asset_baker entrypoint.cpp) target_link_libraries(asset_baker
# PRIVATE libasset_baker)
add_module(NAME camera INTERFACES components.cppm DEPENDENCIES preliminary math)
add_module(
NAME
app
INTERFACES
application.cppm
system.cppm
DEPENDENCIES
preliminary
memory
PRIVATE_DEPENDENCIES
)
add_module(
NAME
ecs
INTERFACES
sparse_set.cppm
registry.cppm
entity.cppm
DEPENDENCIES
logger
memory
TESTS
registry.test.cpp
sparse_set.test.cpp
)
add_module(NAME input_codes INTERFACES input_codes.cppm DEPENDENCIES preliminary)
if(WIN32)
add_module(
NAME
surface
INTERFACES
constants.cppm
system.cppm
requests.cppm
events.cppm
components.cppm
DEPENDENCIES
preliminary
ecs
app
math
memory
input_codes
PRIVATE_DEPENDENCIES
logger
time
TESTS
system.test.cpp
)
elseif(UNIX)
add_module(
NAME
surface
INTERFACES
constants.cppm
system.cppm
requests.cppm
events.cppm
components.cppm
DEPENDENCIES
preliminary
ecs
app
math
memory
input_codes
wayland-client
PRIVATE_DEPENDENCIES
X11
logger
time
TESTS
system.test.cpp
)
function(add_wayland_protocol_target TARGET_NAME SPEC NAME)
add_custom_target(wayland_${TARGET_NAME}_header COMMAND wayland-scanner client-header /usr/share/wayland-protocols${SPEC} ${CMAKE_CURRENT_SOURCE_DIR}/surface/wayland-protocols/${NAME}.h)
add_dependencies(surface wayland_${TARGET_NAME}_header)
add_custom_target(wayland_${TARGET_NAME}_source COMMAND wayland-scanner private-code /usr/share/wayland-protocols${SPEC} ${CMAKE_CURRENT_SOURCE_DIR}/surface/wayland-protocols/${NAME}.c)
add_dependencies(surface wayland_${TARGET_NAME}_source)
target_sources(surface PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/surface/wayland-protocols/${NAME}.c)
endfunction()
target_include_directories(surface PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/surface/wayland-protocols/)
add_wayland_protocol_target(xdg_shell "/stable/xdg-shell/xdg-shell.xml" xdg-shell)
else()
message(FATAL "Failed to generate cmake: unsupported platform")
endif()
add_module(
NAME
input
INTERFACES
system.cppm
components.cppm
events.cppm
DEPENDENCIES
preliminary
input_codes
surface
math
logger
TESTS
system.test.cpp
)
find_package(Vulkan REQUIRED)
message("Vulkan Libraries are: ${Vulkan_LIBRARIES}")
add_module(
NAME
renderer
INTERFACES
data.cppm
system.cppm
frontends.cppm
components.cppm
factory.cppm
vk/api_wrapper.cppm
vk/device.cppm
vk/gpu.cppm
vk/instance.cppm
vk/surface.cppm
vk/swapchain.cppm
vk/buffer.cppm
vk/pass.cppm
vk/renderer.cppm
vk/debugger.cppm
DEPENDENCIES
preliminary
app
ecs
memory
assets
time
bitwise
camera
${Vulkan_LIBRARIES}
Vulkan::Vulkan
PRIVATE_DEPENDENCIES
surface
TESTS
_tests/buffer.cpp
_tests/debugger.cpp
_tests/device.cpp
_tests/pass.cpp
_tests/renderer.cpp
_tests/surface.cpp
_tests/system.cpp
TEST_INTERFACES
_tests/utils.cppm
)
add_module(
NAME
mirror
ROOT_DIR
${CMAKE_CURRENT_SOURCE_DIR}/mirror
INTERFACES
system.cppm
DEPENDENCIES
memory
app
time
input
surface
renderer
camera
)
if(ENABLE_SANDBOX)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/sandbox/)
endif()

View file

@ -0,0 +1,5 @@
add_library_module(app application.cpp)
target_link_libraries(
app
PUBLIC memory
PRIVATE lt_debug)

View file

@ -1,49 +1,7 @@
export module app; #include <app/application.hpp>
#include <app/system.hpp>
#include <memory/reference.hpp>
import preliminary;
import app.system;
import memory.reference;
import memory.scope;
export namespace lt::app {
/** The main application class.
* Think of this like an aggregate of systems, you register systems through this interface.
* Then they'll tick every "application frame".
*/
class Application
{
public:
Application(const Application &) = delete;
Application(Application &&) = delete;
auto operator=(const Application &) -> Application & = delete;
auto operator=(Application &&) -> Application & = delete;
virtual ~Application() = default;
void game_loop();
void register_system(memory::Ref<app::ISystem> system);
void unregister_system(memory::Ref<app::ISystem> system);
protected:
Application() = default;
private:
std::vector<memory::Ref<app::ISystem>> m_systems;
std::vector<memory::Ref<app::ISystem>> m_systems_to_be_unregistered;
std::vector<memory::Ref<app::ISystem>> m_systems_to_be_registered;
};
} // namespace lt::app
module :private;
namespace lt::app { namespace lt::app {
void Application::game_loop() void Application::game_loop()

View file

@ -0,0 +1,47 @@
#pragma once
#include <memory/reference.hpp>
#include <memory/scope.hpp>
namespace lt::app {
class ISystem;
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.
* Then they'll tick every "application frame".
*/
class Application
{
public:
Application(const Application &) = delete;
Application(Application &&) = delete;
auto operator=(const Application &) -> Application & = delete;
auto operator=(Application &&) -> Application & = delete;
virtual ~Application() = default;
void game_loop();
void register_system(memory::Ref<app::ISystem> system);
void unregister_system(memory::Ref<app::ISystem> system);
protected:
Application() = default;
private:
std::vector<memory::Ref<app::ISystem>> m_systems;
std::vector<memory::Ref<app::ISystem>> m_systems_to_be_unregistered;
std::vector<memory::Ref<app::ISystem>> m_systems_to_be_registered;
};
} // namespace lt::app

View file

@ -0,0 +1,27 @@
#pragma once
#include <app/application.hpp>
#include <memory/scope.hpp>
auto main(int argc, char *argv[]) -> int32_t
try
{
std::ignore = argc;
std::ignore = argv;
auto application = lt::memory::Scope<lt::app::Application> {};
application = lt::app::create_application();
if (!application)
{
throw std::runtime_error { "Failed to create application\n" };
}
application->game_loop();
return EXIT_SUCCESS;
}
catch (const std::exception &exp)
{
log_crt("Terminating due to uncaught exception:");
log_crt("\texception.what(): {}", exp.what());
return EXIT_FAILURE;
}

View file

@ -1,9 +1,8 @@
export module app.system; #pragma once
import preliminary; #include <chrono>
import logger;
export namespace lt::app { namespace lt::app {
/** Information required to tick a system. /** Information required to tick a system.
* @note May be used across an entire application-frame (consisting of multiple systems ticking) * @note May be used across an entire application-frame (consisting of multiple systems ticking)
@ -12,7 +11,7 @@ struct TickInfo
{ {
using Timepoint_T = std::chrono::time_point<std::chrono::steady_clock>; using Timepoint_T = std::chrono::time_point<std::chrono::steady_clock>;
using Duration_T = std::chrono::duration<f64>; using Duration_T = std::chrono::duration<double>;
/** Duration since previous tick's end_time to current tick's start_time. */ /** Duration since previous tick's end_time to current tick's start_time. */
Duration_T delta_time {}; Duration_T delta_time {};
@ -35,7 +34,7 @@ struct TickResult
{ {
using Timepoint_T = std::chrono::time_point<std::chrono::steady_clock>; using Timepoint_T = std::chrono::time_point<std::chrono::steady_clock>;
using Duration_T = std::chrono::duration<f64>; using Duration_T = std::chrono::duration<double>;
/** The info supplied to the system for ticking. */ /** The info supplied to the system for ticking. */
TickInfo info; TickInfo info;
@ -47,9 +46,10 @@ struct TickResult
Timepoint_T end_time; Timepoint_T end_time;
}; };
struct SystemDiagnosis struct SystemDiagnosis
{ {
enum class Severity : u8 enum class Severity : uint8_t
{ {
verbose, verbose,
info, info,
@ -70,9 +70,9 @@ class SystemStats
public: public:
void push_diagnosis(SystemDiagnosis &&diagnosis) void push_diagnosis(SystemDiagnosis &&diagnosis)
{ {
auto &diag = m_diagnosis.emplace_back(std::move(diagnosis)); auto diag = m_diagnosis.emplace_back(std::move(diagnosis));
log::info("message: {}", std::string { diag.message }); log_dbg("message: {}", diag.message);
} }
[[nodiscard]] auto empty_diagnosis() const -> bool [[nodiscard]] auto empty_diagnosis() const -> bool

View file

@ -0,0 +1,6 @@
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)
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,9 +1,7 @@
import preliminary; #include <asset_baker/bakers.hpp>
import assets.shader; #include <assets/shader.hpp>
import logger;
import bakers;
auto main(i32 argc, char *argv[]) -> i32 auto main(int argc, char *argv[]) -> int32_t
try try
{ {
if (argc != 2) if (argc != 2)
@ -20,8 +18,7 @@ try
} }
const auto &in_path = directory_iterator.path(); const auto &in_path = directory_iterator.path();
const std::string in_path_str = in_path.generic_string(); const auto out_path = std::format("{}.asset", in_path.c_str());
const auto out_path = std::format("{}.asset", in_path_str);
if (in_path.extension() == ".vert") if (in_path.extension() == ".vert")
{ {
@ -33,12 +30,12 @@ try
} }
} }
return 0; return EXIT_SUCCESS;
} }
catch (const std::exception &exp) catch (const std::exception &exp)
{ {
lt::log::critical("Terminating due to uncaught exception:"); log_crt("Terminating due to uncaught exception:");
lt::log::critical("\texception.what: {}:", exp.what()); log_crt("\texception.what: {}:", exp.what());
return 1; return EXIT_FAILURE;
} }

View file

@ -1,11 +1,8 @@
export module bakers; #pragma once
import preliminary; #include <assets/shader.hpp>
import assets.metadata;
import assets.shader;
import logger;
export void bake_shader( inline void bake_shader(
const std::filesystem::path &in_path, const std::filesystem::path &in_path,
const std::filesystem::path &out_path, const std::filesystem::path &out_path,
lt::assets::ShaderAsset::Type type lt::assets::ShaderAsset::Type type
@ -14,18 +11,18 @@ export void bake_shader(
using lt::assets::ShaderAsset; using lt::assets::ShaderAsset;
using enum lt::assets::ShaderAsset::Type; using enum lt::assets::ShaderAsset::Type;
auto glsl_path = std::string { in_path.string() }; auto glsl_path = in_path.string();
auto spv_path = std::format("{}.spv", glsl_path); auto spv_path = std::format("{}.spv", glsl_path);
lt::log::trace( log_trc(
"Compiling {} shader {} -> {}", "Compiling {} shader {} -> {}",
type == vertex ? "vertex" : "fragment", type == vertex ? "vertex" : "fragment",
std::string { glsl_path }, glsl_path,
std::string { spv_path } spv_path
); );
// Don't bother linking to shaderc, just invoke the command with a system call. // Don't bother linking to shaderc, just invoke the command with a system call.
// NOLINTNEXTLINE(concurrency-mt-unsafe) // NOLINTNEXTLINE(concurrency-mt-unsafe)
std::system( system(
std::format( std::format(
"glslc --target-env=vulkan1.4 -std=450core -fshader-stage={} {} -o {}", "glslc --target-env=vulkan1.4 -std=450core -fshader-stage={} {} -o {}",
type == vertex ? "vert" : "frag", type == vertex ? "vert" : "frag",
@ -36,7 +33,7 @@ export void bake_shader(
); );
auto stream = std::ifstream(spv_path, std::ios::binary); auto stream = std::ifstream(spv_path, std::ios::binary);
ensure( lt::ensure(
stream.is_open(), stream.is_open(),
"Failed to open compiled {} shader at: {}", "Failed to open compiled {} shader at: {}",
type == vertex ? "vert" : "frag", type == vertex ? "vert" : "frag",
@ -46,9 +43,10 @@ export void bake_shader(
stream.seekg(0, std::ios::end); stream.seekg(0, std::ios::end);
const auto size = stream.tellg(); const auto size = stream.tellg();
auto bytes = std::vector<byte>(size); auto bytes = std::vector<std::byte>(size);
stream.seekg(0, std::ios::beg); stream.seekg(0, std::ios::beg);
stream.read((char *)bytes.data(), size); // NOLINT stream.read((char *)bytes.data(), size); // NOLINT
log_dbg("BYTES: {}", bytes.size());
stream.close(); stream.close();
std::filesystem::remove(spv_path); std::filesystem::remove(spv_path);

View file

@ -0,0 +1,162 @@
#include <asset_parser/assets/text.hpp>
namespace Assets {
/* static */ void TextAsset::pack(const PackageData &data, const std::filesystem::path &out_path)
{
const auto &[metadata, text_metadata, text] = 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 Text 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 *)&text_metadata, sizeof(text_metadata));
constexpr auto number_of_blobs = uint32_t { 1 };
stream.write((char *)&number_of_blobs, sizeof(number_of_blobs));
auto textblob_metadata = BlobMetadata {
.tag = BlobMetadata::Tag::text,
.offset = static_cast<size_t>(stream.tellp()) + sizeof(BlobMetadata),
.compression_type = CompressionType::None,
.compressed_size = text.size(),
.uncompressed_size = text.size(),
};
stream.write((char *)&textblob_metadata, sizeof(textblob_metadata));
stream.write((char *)text.data(), static_cast<long>(text.size()));
// NOLINTEND(cppcoreguidelines-pro-type-cstyle-cast)
}
TextAsset::TextAsset(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 Text 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 Text asset: invalid number of blobs: {}", num_blobs)
};
}
m_stream.read((char *)&m_text_blob_metadata, sizeof(m_text_blob_metadata));
if (m_text_blob_metadata.tag != BlobMetadata::Tag::text)
{
throw std::runtime_error {
std::format(
"Failed to load Text asset: invalid blob tag, expected {}, got {}",
std::to_underlying(BlobMetadata::Tag::text),
std::to_underlying(m_text_blob_metadata.tag)
),
};
}
// NOLINTEND(cppcoreguidelines-pro-type-cstyle-cast)
}
void TextAsset::unpack_blob(
BlobMetadata::Tag tag,
std::byte *destination,
size_t destination_capacity
) const
{
if (tag != BlobMetadata::Tag::text)
{
throw std::runtime_error {
std::format("Invalid tag for unpack_blob of TextAsset: {}", std::to_underlying(tag))
};
}
m_stream.seekg(static_cast<long>(m_text_blob_metadata.offset));
switch (m_text_blob_metadata.compression_type)
{
case Assets::CompressionType::None:
if (m_text_blob_metadata.uncompressed_size != m_text_blob_metadata.compressed_size)
{
throw std::runtime_error(
"Failed to unpack blob from TextAsset: "
"compressed/uncompressed size mismatch for no compression "
"type"
);
}
if (m_text_blob_metadata.uncompressed_size > destination_capacity)
{
throw std::runtime_error(
"Failed to unpack blob from TextAsset: "
"uncompressed_size > destination_capacity, unpacking "
"would result in segfault"
);
}
if (!m_stream.is_open())
{
throw std::runtime_error(
"Failed to unpack blob from TextAsset: ifstream is "
"closed"
);
}
m_stream.read(
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
(char *)destination,
static_cast<long>(m_text_blob_metadata.uncompressed_size)
);
return;
default:
throw std::runtime_error(
std::format(
"Failed to unpack blob from TextAsset: unsupported "
"compression type: {}",
std::to_underlying(m_text_blob_metadata.compression_type)
)
);
}
}
[[nodiscard]] auto TextAsset::get_asset_metadata() const -> const Asset::Metadata &
{
return m_asset_metadata;
}
[[nodiscard]] auto TextAsset::get_metadata() const -> const Metadata &
{
return m_metadata;
}
[[nodiscard]] auto TextAsset::get_blob_metadata(BlobMetadata::Tag tag) const -> const BlobMetadata &
{
if (tag != BlobMetadata::Tag::text)
{
throw std::runtime_error { std::format(
"Invalid tag for get_blob_metadata of TextAsset: {}",
std::to_underlying(tag)
) };
}
return m_text_blob_metadata;
}
} // 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

@ -1,80 +1,4 @@
export module assets.shader; #include <assets/shader.hpp>
import preliminary;
import assets.metadata;
import logger;
export namespace lt::assets {
class ShaderAsset
{
public:
static constexpr auto asset_type_identifier = Type_T { "SHADER_________" };
enum class BlobTag : Tag_T
{
code,
};
enum class Type : u8
{
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<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
namespace lt::assets { namespace lt::assets {
@ -88,8 +12,7 @@ constexpr auto total_metadata_size = //
+ sizeof(BlobMetadata::compressed_size) // + sizeof(BlobMetadata::compressed_size) //
+ sizeof(BlobMetadata::uncompressed_size); + sizeof(BlobMetadata::uncompressed_size);
ShaderAsset::ShaderAsset(const std::filesystem::path &path) ShaderAsset::ShaderAsset(const std::filesystem::path &path): m_stream(path)
: m_stream(path, std::ios::beg | std::ios::binary)
{ {
ensure(m_stream.is_open(), "Failed to open shader asset at: {}", path.string()); ensure(m_stream.is_open(), "Failed to open shader asset at: {}", path.string());
const auto read = [this](auto &field) { const auto read = [this](auto &field) {
@ -110,7 +33,6 @@ ShaderAsset::ShaderAsset(const std::filesystem::path &path)
read(m_asset_metadata.type); read(m_asset_metadata.type);
read(m_asset_metadata.version); read(m_asset_metadata.version);
read(m_metadata.type); read(m_metadata.type);
read(m_code_blob_metadata.tag); read(m_code_blob_metadata.tag);
read(m_code_blob_metadata.offset); read(m_code_blob_metadata.offset);
read(m_code_blob_metadata.compression_type); read(m_code_blob_metadata.compression_type);
@ -192,7 +114,7 @@ ShaderAsset::ShaderAsset(const std::filesystem::path &path)
stream.write(std::bit_cast<char *>(code_blob.data()), static_cast<long long>(code_blob.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<byte> destination) const 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(tag == BlobTag::code, "Invalid blob tag for shader asset: {}", std::to_underlying(tag));
@ -206,10 +128,10 @@ void ShaderAsset::unpack_to(BlobTag tag, std::span<byte> destination) const
m_code_blob_metadata.uncompressed_size m_code_blob_metadata.uncompressed_size
); );
m_stream.seekg(static_cast<long long>(m_code_blob_metadata.offset), std::ifstream::beg); m_stream.seekg(static_cast<long long>(m_code_blob_metadata.offset));
m_stream.read( m_stream.read(
std::bit_cast<char *>(destination.data()), std::bit_cast<char *>(destination.data()),
m_code_blob_metadata.uncompressed_size static_cast<long long>(m_code_blob_metadata.uncompressed_size)
); );
} }

View file

@ -1,65 +1,44 @@
import test; #include <assets/shader.hpp>
import assets.metadata; #include <ranges>
import assets.shader; #include <test/test.hpp>
using ::lt::assets::AssetMetadata; using ::lt::assets::AssetMetadata;
using ::lt::assets::Blob;
using ::lt::assets::BlobMetadata; using ::lt::assets::BlobMetadata;
using ::lt::assets::ShaderAsset; 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 test_data_path = std::filesystem::path { "./data/test_assets" };
const auto tmp_path = std::filesystem::path { "/tmp/lt_assets_tests/" }; const auto tmp_path = std::filesystem::path { "/tmp/lt_assets_tests/" };
[[nodiscard]] auto generate_blob(size_t size) -> Blob
{
auto blob = Blob {};
for (auto idx : std::views::iota(0u, size))
{
blob.emplace_back(static_cast<byte>(idx));
}
return blob;
}
Suite raii = "shader_raii"_suite = [] { Suite raii = "shader_raii"_suite = [] {
std::filesystem::current_path(test_data_path); std::filesystem::current_path(test_data_path);
std::filesystem::create_directories(tmp_path); std::filesystem::create_directories(tmp_path);
Case { "happy paths" } = [] { Case { "happy path won't throw" } = [] {
auto shader_asset = ShaderAsset { "triangle.frag.asset" };
}; };
Case { "unhappy paths" } = [] { Case { "many won't freeze/throw" } = [] {
// non-existent file
expect_throw([] { ShaderAsset { "path" }; });
// incompatible type
expect_throw([] { ShaderAsset { "dummytext" }; });
// some random stressing
expect_throw([] {
for (auto idx : std::views::iota(0u, 1'000u))
{
auto shader_asset = ShaderAsset { std::to_string(idx) };
}
});
}; };
Case { "many" } = [] { Case { "unhappy path throws" } = [] {
for (auto idx : std::views::iota(0u, 1'000u)) expect_throw([] { ShaderAsset { "random_path" }; });
{
ignore = idx;
auto shader_asset = ShaderAsset { "triangle.frag.asset" };
}
}; };
}; };
// NOLINTNEXTLINE(cppcoreguidelines-interfaces-global-init)
Suite packing = "shader_pack"_suite = [] { Suite packing = "shader_pack"_suite = [] {
Case { "Unpacking packed data returns the same data" } = [] { Case { "" } = [] {
const auto out_path = tmp_path / "shader_packing"; const auto out_path = tmp_path / "shader_packing";
constexpr auto blob_size = size_t { 255u }; auto dummy_blob = lt::assets::Blob {};
for (auto idx : std::views::iota(0, 255))
auto blob = generate_blob(blob_size); {
dummy_blob.emplace_back(static_cast<std::byte>(idx));
}
const auto expected_size = // const auto expected_size = //
sizeof(AssetMetadata::type) // sizeof(AssetMetadata::type) //
@ -70,7 +49,7 @@ Suite packing = "shader_pack"_suite = [] {
+ sizeof(BlobMetadata::compression_type) // + sizeof(BlobMetadata::compression_type) //
+ sizeof(BlobMetadata::compressed_size) // + sizeof(BlobMetadata::compressed_size) //
+ sizeof(BlobMetadata::uncompressed_size) // + sizeof(BlobMetadata::uncompressed_size) //
+ blob.size(); + dummy_blob.size();
ShaderAsset::pack( ShaderAsset::pack(
out_path, out_path,
@ -81,7 +60,7 @@ Suite packing = "shader_pack"_suite = [] {
ShaderAsset::Metadata { ShaderAsset::Metadata {
.type = ShaderAsset::Type::vertex, .type = ShaderAsset::Type::vertex,
}, },
std::move(blob) std::move(dummy_blob)
); );
auto stream = std::ifstream { auto stream = std::ifstream {
@ -90,7 +69,7 @@ Suite packing = "shader_pack"_suite = [] {
}; };
expect_true(stream.is_open()); expect_true(stream.is_open());
stream.seekg(0u, std::ios::end); stream.seekg(0, std::ios::end);
const auto file_size = static_cast<size_t>(stream.tellg()); const auto file_size = static_cast<size_t>(stream.tellg());
expect_eq(file_size, expected_size); expect_eq(file_size, expected_size);
stream.close(); stream.close();
@ -104,12 +83,12 @@ Suite packing = "shader_pack"_suite = [] {
const auto &metadata = shader_asset.get_metadata(); const auto &metadata = shader_asset.get_metadata();
expect_eq(metadata.type, ShaderAsset::Type::vertex); expect_eq(metadata.type, ShaderAsset::Type::vertex);
auto unpakced_blob = shader_asset.unpack(ShaderAsset::BlobTag::code); auto blob = shader_asset.unpack(ShaderAsset::BlobTag::code);
expect_eq(unpakced_blob.size(), blob_size); expect_eq(blob.size(), 255);
for (auto idx : std::views::iota(0u, blob_size)) for (auto idx : std::views::iota(0, 255))
{ {
expect_eq(unpakced_blob[idx], static_cast<byte>(idx)); expect_eq(blob[idx], static_cast<std::byte>(idx));
} }
}; };
}; };

View file

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

View file

@ -1,20 +1,18 @@
export module assets.metadata; #pragma once
import preliminary; namespace lt::assets {
export namespace lt::assets {
using Type_T = std::array<const char, 16>; using Type_T = std::array<const char, 16>;
using Tag_T = u8; using Tag_T = uint8_t;
using Version = u8; using Version = uint8_t;
using Blob = std::vector<byte>; using Blob = std::vector<std::byte>;
constexpr auto current_version = Version { 1u }; constexpr auto current_version = Version { 1u };
enum class CompressionType : u8 enum class CompressionType : uint8_t
{ {
none, none,
lz4, lz4,

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

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

View file

@ -1,12 +0,0 @@
export module bitwise;
import preliminary;
namespace lt::bitwise {
/* bit-wise */
export constexpr auto bit(u32 x) -> u32
{
return u32 { 1u } << x;
}
} // namespace lt::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

@ -0,0 +1,3 @@
add_library_module(camera camera.cpp scene.cpp)
target_link_libraries(camera PUBLIC math)

View file

@ -1,23 +0,0 @@
export module camera.components;
import preliminary;
import math.vec4;
export namespace lt::camera::components {
struct PerspectiveCamera
{
f32 vertical_fov {};
f32 near_plane {};
f32 far_plane {};
f32 aspect_ratio {};
math::vec4 background_color;
bool is_primary {};
};
} // namespace lt::camera::components

View file

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

View file

@ -0,0 +1,84 @@
#include <camera/camera.hpp>
#include <camera/component.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

@ -0,0 +1,35 @@
#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

@ -0,0 +1,29 @@
#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,100 @@
#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

@ -0,0 +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)

View file

@ -0,0 +1,71 @@
#include <logger/logger.hpp>
#include <lt_debug/instrumentor.hpp>
namespace lt {
void Instrumentor::begin_session_impl(const std::string &outputPath)
{
std::filesystem::create_directory(outputPath.substr(0, outputPath.find_last_of('/') + 1));
m_output_file_stream.open(outputPath);
m_output_file_stream << "{\"traceEvents\":[";
}
void Instrumentor::end_session_impl()
{
if (m_current_session_count == 0u)
{
log_wrn("0 profiling for the ended session");
}
m_current_session_count = 0u;
m_output_file_stream << "]}";
m_output_file_stream.flush();
m_output_file_stream.close();
}
void Instrumentor::submit_scope_profile_impl(const ScopeProfileResult &profileResult)
{
if (m_current_session_count++ == 0u)
{
m_output_file_stream << "{";
}
else
{
m_output_file_stream << ",{";
}
m_output_file_stream << R"("name":")" << profileResult.name << "\",";
m_output_file_stream << R"("cat": "scope",)";
m_output_file_stream << R"("ph": "X",)";
m_output_file_stream << "\"ts\":" << profileResult.start << ",";
m_output_file_stream << "\"dur\":" << profileResult.duration << ",";
m_output_file_stream << "\"pid\":0,";
m_output_file_stream << "\"tid\":" << profileResult.threadID << "";
m_output_file_stream << "}";
}
InstrumentorTimer::InstrumentorTimer(const std::string &scopeName)
: m_result({ .name = scopeName, .start = 0, .duration = 0, .threadID = 0 })
, m_start(std::chrono::steady_clock::now())
{
}
InstrumentorTimer::~InstrumentorTimer()
{
auto end = std::chrono::steady_clock::now();
m_result.start = std::chrono::time_point_cast<std::chrono::microseconds>(m_start)
.time_since_epoch()
.count();
m_result.duration = std::chrono::time_point_cast<std::chrono::microseconds>(end)
.time_since_epoch()
.count()
- m_result.start;
Instrumentor::submit_scope_profile(m_result);
}
} // namespace lt

View file

@ -0,0 +1,3 @@
#pragma once
#include <lt_debug/assertions.hpp>

View file

@ -0,0 +1,36 @@
#pragma once
#include <format>
#include <logger/logger.hpp>
#include <source_location>
namespace lt {
template<typename Expression_T, typename... Args_T>
struct ensure
{
ensure(
const Expression_T &expression,
std::format_string<Args_T...> fmt,
Args_T &&...args,
const std::source_location &location = std::source_location::current()
)
{
if (!static_cast<bool>(expression))
{
throw std::runtime_error { std::format(
"exception: {}\nlocation: {}:{}",
std::format(fmt, std::forward<Args_T>(args)...),
location.file_name(),
location.line()
) };
}
}
};
template<typename Expression_T, typename... Args_T>
ensure(Expression_T, std::format_string<Args_T...>, Args_T &&...)
-> ensure<Expression_T, Args_T...>;
} // namespace lt

View file

@ -0,0 +1,77 @@
#pragma once
#include <chrono>
#include <fstream>
namespace lt {
struct ScopeProfileResult
{
std::string name;
long long start, duration;
uint32_t threadID;
};
class Instrumentor
{
public:
static auto instance() -> Instrumentor &
{
static auto instance = Instrumentor {};
return instance;
}
static void begin_session(const std::string &outputPath)
{
instance().begin_session_impl(outputPath);
}
static void end_session()
{
instance().end_session_impl();
}
static void submit_scope_profile(const ScopeProfileResult &profileResult)
{
instance().submit_scope_profile_impl(profileResult);
}
private:
std::ofstream m_output_file_stream;
unsigned int m_current_session_count { 0u };
Instrumentor() = default;
void begin_session_impl(const std::string &outputPath);
void end_session_impl();
void submit_scope_profile_impl(const ScopeProfileResult &profileResult);
};
class InstrumentorTimer
{
public:
InstrumentorTimer(const std::string &scopeName);
~InstrumentorTimer();
private:
ScopeProfileResult m_result;
std::chrono::time_point<std::chrono::steady_clock> m_start;
};
} // namespace lt
/* scope */
#define lt_profile_scope(name) lt_profile_scope_no_redifinition(name, __LINE__)
#define lt_profile_scope_no_redifinition(name, line) lt_profile_scope_no_redifinition2(name, line)
#define lt_profile_scope_no_redifinition2(name, line) InstrumentorTimer timer##line(name)
/* function */
#define LT_PROFILE_FUNCTION lt_profile_scope(__FUNCSIG__)
/* session */
#define lt_profile_begin_session(outputPath) ::lt::Instrumentor::begin_session(outputPath)
#define lt_profile_end_session() ::lt::Instrumentor::end_session()

View file

@ -0,0 +1,4 @@
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,12 +1,23 @@
import test; #include <ecs/registry.hpp>
import ecs.registry; #include <ranges>
#include <test/expects.hpp>
#include <test/test.hpp>
using ::lt::ecs::EntityId; using lt::test::Case;
using ::lt::ecs::Registry; 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 struct Component
{ {
i32 m_int {}; int m_int {};
std::string m_string; std::string m_string;
[[nodiscard]] friend auto operator==(const Component &lhs, const Component &rhs) -> bool [[nodiscard]] friend auto operator==(const Component &lhs, const Component &rhs) -> bool
@ -30,7 +41,7 @@ struct std::formatter<Component>
struct Component_B struct Component_B
{ {
f32 m_float {}; float m_float {};
[[nodiscard]] friend auto operator==(const Component_B lhs, const Component_B &rhs) -> bool [[nodiscard]] friend auto operator==(const Component_B lhs, const Component_B &rhs) -> bool
{ {
@ -52,21 +63,20 @@ struct std::formatter<Component_B>
}; };
Suite raii = "raii"_suite = [] { Suite raii = "raii"_suite = [] {
Case { "happy paths" } = [] { Case { "happy path won't throw" } = [] {
ignore = Registry {}; std::ignore = Registry {};
}; };
Case { "unhappy paths" } = [] { Case { "many won't freeze/throw" } = [] {
};
Case { "many" } = [] {
for (auto idx : std::views::iota(0, 100'000)) for (auto idx : std::views::iota(0, 100'000))
{ {
ignore = idx; std::ignore = Registry {};
ignore = Registry {};
} }
}; };
Case { "unhappy path throws" } = [] {
};
Case { "post construct has correct state" } = [] { Case { "post construct has correct state" } = [] {
auto registry = Registry {}; auto registry = Registry {};
expect_eq(registry.get_entity_count(), 0); expect_eq(registry.get_entity_count(), 0);
@ -150,7 +160,6 @@ Suite callbacks = "callbacks"_suite = [] {
Case { "on_construct/on_destruct won't get called on unrelated component" } = [] { Case { "on_construct/on_destruct won't get called on unrelated component" } = [] {
auto registry = Registry {}; auto registry = Registry {};
registry.connect_on_construct<Component>([&](Registry &, EntityId) { registry.connect_on_construct<Component>([&](Registry &, EntityId) {
expect_unreachable(); expect_unreachable();
}); });
@ -160,7 +169,6 @@ Suite callbacks = "callbacks"_suite = [] {
for (auto idx : std::views::iota(0, 100'000)) for (auto idx : std::views::iota(0, 100'000))
{ {
ignore = idx;
registry.add<Component_B>(registry.create_entity(), {}); registry.add<Component_B>(registry.create_entity(), {});
} }
}; };
@ -182,8 +190,6 @@ Suite callbacks = "callbacks"_suite = [] {
expect_true(on_destruct_called.empty()); expect_true(on_destruct_called.empty());
for (auto idx : std::views::iota(0, 100'000)) for (auto idx : std::views::iota(0, 100'000))
{ {
ignore = idx;
auto entity = all_entities.emplace_back(registry.create_entity()); auto entity = all_entities.emplace_back(registry.create_entity());
registry.add<Component>(entity, {}); registry.add<Component>(entity, {});
} }
@ -218,7 +224,7 @@ Suite each = "each"_suite = [] {
component_map_a[entity] = component; component_map_a[entity] = component;
} }
auto component_map_b = std::unordered_map<EntityId, Component_B> {}; auto component_map_b = std::unordered_map<lt::ecs::EntityId, Component_B> {};
for (auto idx : std::views::iota(0, 10'000)) for (auto idx : std::views::iota(0, 10'000))
{ {
auto entity = EntityId {}; auto entity = EntityId {};
@ -233,7 +239,7 @@ Suite each = "each"_suite = [] {
} }
auto &component = registry.add<Component_B>( auto &component = registry.add<Component_B>(
entity, entity,
{ .m_float = static_cast<f32>(idx) / 2.0f } { .m_float = static_cast<float>(idx) / 2.0f }
); );
component_map_b[entity] = component; component_map_b[entity] = component;
@ -304,7 +310,7 @@ Suite views = "views"_suite = [] {
} }
auto &component = registry.add<Component_B>( auto &component = registry.add<Component_B>(
entity, entity,
{ .m_float = static_cast<f32>(idx) / 2.0f } { .m_float = static_cast<float>(idx) / 2.0f }
); );
component_map_b[entity] = component; component_map_b[entity] = component;

View file

View file

@ -1,26 +1,28 @@
import test; #include <ecs/sparse_set.hpp>
import ecs.sparse_set; #include <ranges>
#include <test/expects.hpp>
#include <test/test.hpp>
using Value_T = i32; using lt::test::Case;
using Set = lt::ecs::SparseSet<Value_T>; 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; constexpr auto capacity = 100;
Suite raii = "raii"_suite = [] { Suite raii = "raii"_suite = [] {
Case { "happy paths" } = [] { Case { "happy path won't throw" } = [] {
ignore = Set {}; std::ignore = Set {};
ignore = Set { Set::max_capacity }; std::ignore = Set { Set::max_capacity };
}; };
Case { "unhappy paths" } = [] { Case { "unhappy path throws" } = [] {
expect_throw([] { ignore = Set { Set::max_capacity + 1 }; }); expect_throw([] { std::ignore = Set { Set::max_capacity + 1 }; });
};
Case { "many" } = [] {
for (auto idx : std::views::iota(0, 1'000))
{
ignore = Set { static_cast<size_t>(idx) };
}
}; };
Case { "post construct has correct state" } = [&] { Case { "post construct has correct state" } = [&] {
@ -31,29 +33,7 @@ Suite raii = "raii"_suite = [] {
}; };
Suite element_raii = "element_raii"_suite = [] { Suite element_raii = "element_raii"_suite = [] {
Case { "happy paths" } = [] { Case { "many inserts/removes won't freeze/throw" } = [] {
auto set = Set { capacity };
set.insert(0, {});
set.remove(0);
};
Case { "unhappy paths" } = [] {
expect_throw([] {
auto set = Set { capacity };
set.insert(Set::max_capacity + 1, {});
});
expect_throw([] {
auto set = Set { capacity };
set.insert(0, {});
set.insert(1, {});
set.insert(2, {});
set.remove(3);
});
};
Case { "many" } = [] {
auto set = Set {}; auto set = Set {};
for (auto idx : std::views::iota(0, 10'000)) for (auto idx : std::views::iota(0, 10'000))
{ {
@ -97,7 +77,7 @@ Suite element_raii = "element_raii"_suite = [] {
expect_eq(set.get_size(), 10'000 - (idx + 1)); expect_eq(set.get_size(), 10'000 - (idx + 1));
expect_false(set.contains(idx)); expect_false(set.contains(idx));
expect_throw([&] { ignore = set.at(idx); }); expect_throw([&] { std::ignore = set.at(idx); });
} }
}; };
@ -117,7 +97,7 @@ Suite element_raii = "element_raii"_suite = [] {
for (auto &[identifier, value] : set) for (auto &[identifier, value] : set)
{ {
expect_eq(static_cast<Value_T>(identifier), value); expect_eq(identifier, value);
expect_ne(value, 0); expect_ne(value, 0);
expect_ne(value, 32); expect_ne(value, 32);
expect_ne(value, 69); expect_ne(value, 69);
@ -149,7 +129,7 @@ Suite getters = "getters"_suite = [] {
expect_eq(set.get_capacity(), 10'000); expect_eq(set.get_capacity(), 10'000);
set.insert(static_cast<Value_T>(set.get_size()), {}); set.insert(set.get_size(), {});
expect_ne(set.get_capacity(), 10'000); expect_ne(set.get_capacity(), 10'000);
}; };
@ -160,12 +140,12 @@ Suite getters = "getters"_suite = [] {
{ {
expect_throw([&] { expect_throw([&] {
set.insert(idx, {}); set.insert(idx, {});
ignore = set.at(50); std::ignore = set.at(50);
}); });
} }
set.insert(50, {}); set.insert(50, {});
ignore = set.at(50); // should not throw std::ignore = set.at(50); // should not throw
}; };
}; };
@ -179,10 +159,5 @@ Suite clear = "clear"_suite = [] {
set.clear(); set.clear();
expect_eq(set.get_size(), 0); expect_eq(set.get_size(), 0);
for (auto idx : std::views::iota(0, 10'000))
{
expect_throw([&] { ignore = set.at(idx); });
}
}; };
}; };

View file

@ -1,10 +1,9 @@
export module ecs.entity; #pragma once
import preliminary; #include <ecs/registry.hpp>
import memory.reference; #include <memory/reference.hpp>
import ecs.registry;
export namespace lt::ecs { namespace lt::ecs {
/** High-level entity convenience wrapper */ /** High-level entity convenience wrapper */
class Entity class Entity
@ -51,4 +50,5 @@ private:
EntityId m_identifier; EntityId m_identifier;
}; };
} // namespace lt::ecs } // namespace lt::ecs

View file

@ -1,12 +1,11 @@
export module ecs.registry; #pragma once
import preliminary; #include <ecs/sparse_set.hpp>
import ecs.sparse_set; #include <memory/scope.hpp>
import memory.scope;
export namespace lt::ecs { namespace lt::ecs {
using EntityId = u32; using EntityId = uint32_t;
constexpr auto null_entity = std::numeric_limits<EntityId>::max(); constexpr auto null_entity = std::numeric_limits<EntityId>::max();
@ -208,7 +207,7 @@ private:
for (const auto &ch : std::string_view { str }) for (const auto &ch : std::string_view { str })
{ {
hash *= fnv_prime; hash *= fnv_prime;
hash ^= static_cast<u8>(ch); hash ^= static_cast<uint8_t>(ch);
} }
return hash; return hash;
@ -251,16 +250,11 @@ private:
TypeId m_entity_count; TypeId m_entity_count;
/** MSVC DOES NOT SUPPORT FLAT MAP!! std::flat_map<TypeId, memory::Scope<UnderlyingSparseSet_T>> m_sparsed_sets;
* IT"S YEAR ~2026, great...
* using ::std::map for the time being.
*/
std::map<TypeId, memory::Scope<UnderlyingSparseSet_T>> m_sparsed_sets; std::flat_map<TypeId, Callback_T> m_on_construct_hooks;
std::map<TypeId, Callback_T> m_on_construct_hooks; std::flat_map<TypeId, Callback_T> m_on_destruct_hooks;
std::map<TypeId, Callback_T> m_on_destruct_hooks;
}; };
} // namespace lt::ecs } // namespace lt::ecs

View file

@ -1,13 +1,12 @@
export module ecs.sparse_set; #pragma once
import preliminary; namespace lt::ecs {
export namespace lt::ecs {
/** /**
*
* @ref https://programmingpraxis.com/2012/03/09/sparse-sets/ * @ref https://programmingpraxis.com/2012/03/09/sparse-sets/
*/ */
template<typename Identifier_T = u32> template<typename Identifier_T = uint32_t>
class TypeErasedSparseSet class TypeErasedSparseSet
{ {
public: public:
@ -26,7 +25,7 @@ public:
virtual void remove(Identifier_T identifier) = 0; virtual void remove(Identifier_T identifier) = 0;
}; };
template<typename Value_T, typename Identifier_T = u32> template<typename Value_T, typename Identifier_T = uint32_t>
class SparseSet: public TypeErasedSparseSet<Identifier_T> class SparseSet: public TypeErasedSparseSet<Identifier_T>
{ {
public: public:
@ -51,18 +50,21 @@ public:
auto insert(Identifier_T identifier, Value_T value) -> Dense_T & auto insert(Identifier_T identifier, Value_T value) -> Dense_T &
{ {
ensure(identifier < max_capacity, "SparseSet::insert: identifier < max_capacity");
if (m_sparse.size() < identifier + 1) if (m_sparse.size() < identifier + 1)
{ {
auto new_capacity = std::max(static_cast<size_t>(identifier + 1), m_sparse.size() * 2); auto new_capacity = std::max(static_cast<size_t>(identifier + 1), m_sparse.size() * 2);
new_capacity = std::min(new_capacity, max_capacity); 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_sparse.resize(new_capacity, null_identifier);
} }
++m_alive_count; ++m_alive_count;
m_sparse[identifier] = static_cast<Identifier_T>(m_dense.size()); m_sparse[identifier] = m_dense.size();
return m_dense.emplace_back(identifier, std::move(value)); return m_dense.emplace_back(identifier, std::move(value));
} }
@ -72,27 +74,7 @@ public:
*/ */
void remove(Identifier_T identifier) override void remove(Identifier_T identifier) override
{ {
ensure(
identifier < m_sparse.size(),
"Failed to ensure: identifier < m_sparse.size() [{} < {}]",
identifier,
m_sparse.size()
);
auto &idx = m_sparse[identifier]; auto &idx = m_sparse[identifier];
ensure(
idx != null_identifier,
"Failed to ensure: idx != null_identifier [{} != {}]",
idx,
null_identifier
);
ensure(
idx < m_dense.size(),
"Failed to ensure: idx < m_dense.size() [{} < {}]",
idx,
m_dense.size()
);
auto &[entity, component] = m_dense[idx]; auto &[entity, component] = m_dense[idx];
auto &[last_entity, last_component] = m_dense.back(); auto &[last_entity, last_component] = m_dense.back();

1
modules/env/CMakeLists.txt vendored Normal file
View file

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

View file

@ -1,11 +1,8 @@
export module preliminary.build_constants; #pragma once
import preliminary.fundumental_types; namespace lt {
import std;
export namespace build_constants { enum class Platform : uint8_t
enum class Platform : u8
{ {
/** The GNU/Linux platform. /** The GNU/Linux platform.
* Tested on the following distros: arch-x86_64 * Tested on the following distros: arch-x86_64
@ -27,7 +24,7 @@ enum class Platform : u8
}; };
/** The compiler that was used for compiling the project. */ /** The compiler that was used for compiling the project. */
enum class Compiler : u8 enum class Compiler : uint8_t
{ {
clang, clang,
gcc, gcc,
@ -35,51 +32,37 @@ enum class Compiler : u8
apple_clang, apple_clang,
}; };
enum class BuildType namespace constants {
{
debug,
release,
distribution
};
#if defined(LIGHT_PLATFORM_WINDOWS) #if defined(LIGHT_PLATFORM_WINDOWS)
#define lt_win(x) #define lt_win(x)
constexpr auto platform = Platform::windows; constexpr auto platform = Platform::windows;
constexpr auto platform_name = "windows"; constexpr auto platform_name = "windows";
constexpr auto platform_identifier = platform_name; // TODO(Light)
#undef LIGHT_PLATFORM_WINDOWS #undef LIGHT_PLATFORM_WINDOWS
#elif defined(LIGHT_PLATFORM_LINUX) #elif defined(LIGHT_PLATFORM_LINUX)
constexpr auto platform = Platform::gnu_linux; constexpr auto platform = Platform::gnu_linux;
constexpr auto platform_name = "gnu_linux"; constexpr auto platform_name = "gnu_linux";
constexpr auto platform_identifier = platform_name; // TODO(Light)
#elif defined(LIGHT_PLATFORM_MAC) #elif defined(LIGHT_PLATFORM_MAC)
#define lt_mac(x) x #define lt_mac(x) x
constexpr auto platform = Platform::mac; constexpr auto platform = Platform::mac;
constexpr auto platform_name = "mac"; constexpr auto platform_name = "mac";
constexpr auto platform_identifier = platform_name; // TODO(Light)
#else #else
#error "Unsupported platform: Unknown" #error "Unsupported platform: Unknown"
#endif #endif
/** @TODO(Light): Handle other compilers... */
#ifdef __clang__ #ifdef __clang__
constexpr auto compiler = Compiler::clang; constexpr auto compiler = Compiler::clang;
constexpr auto compiler_name = "clang"; constexpr auto compiler_name = "clang";
/** @TODO(Light): insert the full identifier, including version information and such */ /** @todo(Light): insert the full identifier, including version information and such */
constexpr auto compiler_identifier = "clang"; constexpr auto full_compiler_identifier = "clang";
#endif #endif
// @TODO(Light): inject build info through CMake using LIGHT_... constant macros } // namespace constants
#if defined(_DEBUG)
constexpr auto build_type = BuildType::debug;
#else
constexpr auto build_type = BuildType::release;
#endif
} // namespace build_constants } // namespace lt

View file

@ -0,0 +1,4 @@
add_library_module(input system.cpp)
target_link_libraries(input PUBLIC surface math logger tbb)
add_test_module(input system.test.cpp)

View file

@ -1,41 +0,0 @@
export module input.events;
import input.codes;
import math.vec2;
import std;
namespace lt::input {
export class AnalogEvent
{
public:
AnalogEvent(Key key, math::vec2_u32 pointer_position)
: m_key(key)
, m_pointer_position(pointer_position)
{
}
[[nodiscard]] auto get_key() const -> Key
{
return m_key;
};
[[nodiscard]] auto get_pointer_position() const -> math::vec2_u32
{
return m_pointer_position;
}
[[nodiscard]] auto to_string() const -> std::string
{
const auto &[x, y] = m_pointer_position;
return std::format("input::AnalogEvent: {} @ {}, {}", std::to_underlying(m_key), x, y);
}
private:
Key m_key;
math::vec2_u32 m_pointer_position;
};
} // namespace lt::input

View file

@ -1,65 +1,9 @@
export module input.system; #include <input/components.hpp>
export import :components; #include <input/system.hpp>
import logger; #include <memory/reference.hpp>
import app.system;
import ecs.registry;
import memory.reference;
import surface.system;
import surface.events;
import math.vec2;
import std;
namespace lt::input { namespace lt::input {
export class System: public app::ISystem
{
public:
System(memory::Ref<ecs::Registry> registry);
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);
void on_surface_lost_focus();
void on_key_press(const lt::surface::KeyPressedEvent &event);
void on_key_release(const lt::surface::KeyReleasedEvent &event);
void on_pointer_move(const lt::surface::MouseMovedEvent &event);
void on_button_press(const lt::surface::ButtonPressedEvent &event);
void on_button_release(const lt::surface::ButtonReleasedEvent &event);
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 {};
};
} // namespace lt::input
module :private;
namespace lt::input {
template<class... Ts> template<class... Ts>
struct overloads: Ts... struct overloads: Ts...
{ {
@ -89,7 +33,7 @@ void System::tick(app::TickInfo tick)
// instead of brute-force checking all of them. // instead of brute-force checking all of them.
for (auto &action : input.m_actions) for (auto &action : input.m_actions)
{ {
auto code = std::to_underlying(action.trigger.mapped_keycode); auto code = action.trigger.mapped_keycode;
if (code < m_keys.size() && m_keys[code]) if (code < m_keys.size() && m_keys[code])
{ {
if (action.state == InputAction::State::triggered) if (action.state == InputAction::State::triggered)
@ -155,9 +99,9 @@ void System::on_surface_lost_focus()
void System::on_key_press(const lt::surface::KeyPressedEvent &event) void System::on_key_press(const lt::surface::KeyPressedEvent &event)
{ {
if (std::to_underlying(event.get_key()) > m_keys.size()) if (event.get_key() > m_keys.size())
{ {
log::warn( log_dbg(
"Key code larger than key container size, implement platform-dependant " "Key code larger than key container size, implement platform-dependant "
"key-code-mapping!" "key-code-mapping!"
); );
@ -165,14 +109,14 @@ void System::on_key_press(const lt::surface::KeyPressedEvent &event)
return; return;
} }
m_keys[std::to_underlying(event.get_key())] = true; m_keys[event.get_key()] = true;
} }
void System::on_key_release(const lt::surface::KeyReleasedEvent &event) void System::on_key_release(const lt::surface::KeyReleasedEvent &event)
{ {
if (std::to_underlying(event.get_key()) > m_keys.size()) if (event.get_key() > m_keys.size())
{ {
log::warn( log_dbg(
"Key code larger than key container size, implement platform-dependant " "Key code larger than key container size, implement platform-dependant "
"key-code-mapping!" "key-code-mapping!"
); );
@ -180,7 +124,7 @@ void System::on_key_release(const lt::surface::KeyReleasedEvent &event)
return; return;
} }
m_keys[std::to_underlying(event.get_key())] = false; m_keys[event.get_key()] = false;
} }
void System::on_pointer_move(const lt::surface::MouseMovedEvent &event) void System::on_pointer_move(const lt::surface::MouseMovedEvent &event)
@ -190,12 +134,12 @@ void System::on_pointer_move(const lt::surface::MouseMovedEvent &event)
void System::on_button_press(const lt::surface::ButtonPressedEvent &event) void System::on_button_press(const lt::surface::ButtonPressedEvent &event)
{ {
m_buttons[std::to_underlying(event.get_button())] = true; m_buttons[event.get_button()] = true;
} }
void System::on_button_release(const lt::surface::ButtonReleasedEvent &event) void System::on_button_release(const lt::surface::ButtonReleasedEvent &event)
{ {
m_buttons[std::to_underlying(event.get_button())] = false; m_buttons[event.get_button()] = false;
} }
} // namespace lt::input } // namespace lt::input

View file

@ -1,18 +1,27 @@
import test; #include <ecs/entity.hpp>
import input.system; #include <input/components.hpp>
import input.codes; #include <input/system.hpp>
import surface.events; #include <memory/reference.hpp>
import memory.scope; #include <memory/scope.hpp>
import memory.reference; #include <ranges>
import app.system; #include <surface/system.hpp>
import ecs.entity; #include <test/test.hpp>
import ecs.registry;
import surface.system;
using ::lt::input::InputComponent; // NOLINTBEGIN
using ::lt::input::System; using namespace lt;
using input::InputComponent;
using input::System;
using std::ignore;
using test::Case;
using test::expect_eq;
using test::expect_false;
using test::expect_ne;
using test::expect_not_nullptr;
using test::expect_throw;
using test::Suite;
// NOLINTEND
[[nodiscard]] auto tick_info() -> lt::app::TickInfo [[nodiscard]] auto tick_info() -> app::TickInfo
{ {
return { return {
.delta_time = std::chrono::milliseconds { 16 }, .delta_time = std::chrono::milliseconds { 16 },
@ -24,12 +33,12 @@ using ::lt::input::System;
class Fixture class Fixture
{ {
public: public:
[[nodiscard]] auto registry() -> lt::memory::Ref<lt::ecs::Registry> [[nodiscard]] auto registry() -> memory::Ref<ecs::Registry>
{ {
return m_registry; return m_registry;
} }
auto add_input_component() -> lt::ecs::EntityId auto add_input_component() -> ecs::EntityId
{ {
auto entity = m_registry->create_entity(); auto entity = m_registry->create_entity();
m_registry->add<InputComponent>(entity, {}); m_registry->add<InputComponent>(entity, {});
@ -37,7 +46,7 @@ public:
return entity; return entity;
} }
auto add_surface_component() -> lt::ecs::EntityId auto add_surface_component() -> ecs::EntityId
{ {
auto entity = m_registry->create_entity(); auto entity = m_registry->create_entity();
m_surface_system.create_surface_component( m_surface_system.create_surface_component(
@ -49,28 +58,27 @@ public:
} }
private: private:
lt::memory::Ref<lt::ecs::Registry> m_registry = lt::memory::create_ref<lt::ecs::Registry>(); memory::Ref<ecs::Registry> m_registry = memory::create_ref<ecs::Registry>();
lt::surface::System m_surface_system = lt::surface::System { m_registry }; surface::System m_surface_system = surface::System { m_registry };
}; };
Suite raii = "raii"_suite = "raii"_suite = [] { Suite raii = "raii"_suite = "raii"_suite = [] {
Case { "happy paths" } = [&] { Case { "happy path won't throw" } = [&] {
System { Fixture {}.registry() }; System { Fixture {}.registry() };
}; };
Case { "unhappy paths" } = [] { Case { "many won't freeze/throw" } = [&] {
expect_throw([] { ignore = System { {} }; });
};
Case { "many" } = [&] {
auto fixture = Fixture {}; auto fixture = Fixture {};
for (auto idx : std::views::iota(0, 10'000)) for (auto idx : std::views::iota(0, 10'000))
{ {
ignore = idx;
ignore = System { fixture.registry() }; ignore = System { fixture.registry() };
} }
}; };
Case { "unhappy path throws" } = [] {
expect_throw([] { ignore = System { {} }; });
};
}; };
Suite system_events = "system_events"_suite = [] { Suite system_events = "system_events"_suite = [] {
@ -100,14 +108,14 @@ Suite registry_events = "registry_events"_suite = [] {
auto registry = fixture.registry(); auto registry = fixture.registry();
auto system = System { registry }; auto system = System { registry };
fixture.add_input_component(); const auto &entity = fixture.add_input_component();
expect_eq(registry->view<InputComponent>().get_size(), 1); expect_eq(registry->view<InputComponent>().get_size(), 1);
}; };
Case { "on_destrroy<InputComponent>" } = [] { Case { "on_destrroy<InputComponent>" } = [] {
auto fixture = Fixture {}; auto fixture = Fixture {};
auto registry = fixture.registry(); auto registry = fixture.registry();
auto system = lt::memory::create_scope<System>(registry); auto system = memory::create_scope<System>(registry);
auto entity_a = fixture.add_input_component(); auto entity_a = fixture.add_input_component();
auto entity_b = fixture.add_input_component(); auto entity_b = fixture.add_input_component();
@ -139,7 +147,7 @@ Suite tick = "tick"_suite = [] {
auto system = System { fixture.registry() }; auto system = System { fixture.registry() };
auto surface_entity = fixture.add_surface_component(); auto surface_entity = fixture.add_surface_component();
auto &surface = registry->get<lt::surface::SurfaceComponent>(surface_entity); auto &surface = registry->get<surface::SurfaceComponent>(surface_entity);
auto input_entity = fixture.add_input_component(); auto input_entity = fixture.add_input_component();
auto &input = registry->get<InputComponent>(input_entity); auto &input = registry->get<InputComponent>(input_entity);
@ -147,29 +155,48 @@ Suite tick = "tick"_suite = [] {
auto action_key = input.add_action( auto action_key = input.add_action(
{ {
.name { "test" }, .name { "test" },
.trigger = { .mapped_keycode = Key::a }, .trigger = { .mapped_keycode = 69 },
} }
); );
using enum ::lt::input::InputAction::State; expect_eq(input.get_action(action_key).state, input::InputAction::State::inactive);
expect_eq(input.get_action(action_key).state, inactive);
system.tick(tick_info()); system.tick(tick_info());
expect_eq(input.get_action(action_key).state, inactive); expect_eq(input.get_action(action_key).state, input::InputAction::State::inactive);
surface.push_event(lt::surface::KeyPressedEvent(Key::a)); surface.push_event(surface::KeyPressedEvent(69));
system.tick(tick_info()); system.tick(tick_info());
expect_eq(input.get_action(action_key).state, triggered); expect_eq(input.get_action(action_key).state, input::InputAction::State::triggered);
system.tick(tick_info()); system.tick(tick_info());
expect_eq(input.get_action(action_key).state, active); expect_eq(input.get_action(action_key).state, input::InputAction::State::active);
system.tick(tick_info()); system.tick(tick_info());
system.tick(tick_info()); system.tick(tick_info());
system.tick(tick_info()); system.tick(tick_info());
expect_eq(input.get_action(action_key).state, active); expect_eq(input.get_action(action_key).state, input::InputAction::State::active);
surface.push_event(lt::surface::KeyReleasedEvent(Key::a)); surface.push_event(surface::KeyReleasedEvent(69));
system.tick(tick_info()); system.tick(tick_info());
expect_eq(input.get_action(action_key).state, inactive); 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_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(
{
.name { "test" },
.trigger = { .mapped_keycode = 69 },
}
);
}; };
}; };

View file

@ -1,18 +1,19 @@
export module input.system:components; #pragma once
import preliminary; #include <vector>
import input.codes;
export namespace lt::input { namespace lt::input {
struct Trigger struct Trigger
{ {
Key mapped_keycode; uint32_t mapped_keycode;
}; };
struct InputAction struct InputAction
{ {
enum class State : u8 using Key = size_t;
enum class State : uint8_t
{ {
inactive, inactive,
active, active,
@ -38,7 +39,7 @@ public:
return m_actions.size() - 1; return m_actions.size() - 1;
} }
auto get_action(size_t idx) -> const InputAction & auto get_action(auto idx) -> const InputAction &
{ {
return m_actions[idx]; return m_actions[idx];
} }

View file

@ -0,0 +1,44 @@
#pragma once
#include <math/vec2.hpp>
namespace lt::input {
class AnalogEvent
{
public:
AnalogEvent(uint32_t input_code, math::uvec2 pointer_position)
: m_input_code(input_code)
, m_pointer_position(pointer_position)
{
}
[[nodiscard]] auto get_code() const -> uint32_t
{
return m_input_code;
};
[[nodiscard]] auto get_pointer_position() const -> math::uvec2
{
return m_pointer_position;
}
[[nodiscard]] auto to_string() const -> std::string
{
auto stream = std::stringstream {};
const auto &[x, y] = m_pointer_position;
stream << "input::AnalogEvent: " << m_input_code << " @ " << x << ", " << y;
return stream.str();
}
private:
uint32_t m_input_code;
math::uvec2 m_pointer_position;
};
class AxisEvent
{
};
} // namespace lt::input

View file

@ -0,0 +1,180 @@
#pragma once
#include <cstdint>
namespace lt::Key {
enum : uint16_t
{
/* digits */
D0 = 48,
D1 = 49,
D2 = 50,
D3 = 51,
D4 = 52,
D5 = 53,
D6 = 54,
D7 = 55,
D8 = 56,
D9 = 57,
Semicolon = 59, // ;
Equal = 61, // =
/* letters */
A = 65,
B = 66,
C = 67,
D = 68,
E = 69,
F = 70,
G = 71,
H = 72,
I = 73,
J = 74,
K = 75,
L = 76,
M = 77,
N = 78,
O = 79,
P = 80,
Q = 81,
R = 82,
S = 83,
t = 84,
U = 85,
V = 86,
W = 87,
X = 88,
Y = 89,
Z = 90,
/* brackets */
LeftBracket = 91, // [
LBracket = LeftBracket, // [
RightBracket = 93, // ]
RBracket = RightBracket, // ]
/* arrow */
Right = 262,
RightArrow = Right,
RArrow = Right,
Left = 263,
LeftArrow = Left,
LArrow = Left,
Down = 264,
DownArrow = Down,
DArrow = Down,
Up = 265,
UpArrow = Up,
UArrow = Up,
/* page */
PageUp = 266,
PageDown = 267,
/* home/end */
Home = 268,
end = 269,
/* toggles */
CapsLock = 280,
ScrollLock = 281,
NumLock = 282,
NumberLock = NumLock,
/* function */
F1 = 290,
F2 = 291,
F3 = 292,
F4 = 293,
F5 = 294,
F6 = 295,
F7 = 296,
F8 = 297,
F9 = 298,
F10 = 299,
F11 = 300,
F12 = 301,
F13 = 302,
F14 = 303,
F15 = 304,
F16 = 305,
F17 = 306,
F18 = 307,
F19 = 308,
F20 = 309,
F21 = 310,
F22 = 311,
F23 = 312,
F24 = 313,
F25 = 314,
/* keypad */
Kp0 = 320,
Kp1 = 321,
Kp2 = 322,
Kp3 = 323,
Kp4 = 324,
Kp5 = 325,
Kp6 = 326,
Kp7 = 327,
Kp8 = 328,
Kp9 = 329,
KpDecimal = 330,
KpDivide = 331,
KpMultiply = 332,
KpSubstract = 333,
KpAdd = 334,
KpEnter = 335,
KpEqual = 336,
/* modifiers */
LeftShift = 340,
LShift = LeftShift,
LeftControl = 341,
LControl = LeftControl,
LeftAlt = 342,
LAlt = LeftAlt,
LeftSuper = 343,
LSuper = LeftSuper,
RightShift = 344,
RShift = 344,
RightControl = 345,
RControl = 345,
RightAlt = 346,
RAlt = 346,
RightSuper = 347,
RSuper = 347,
/* misc */
Space = 32,
Apostrophe = 39, // '
Quote = Apostrophe,
Comma = 44, // ,
Minus = 45, // -
Period = 46, // .
Slash = 47, // /
ForwardSlash = Slash, // /
BackSlash = 92, // \
GraveAccent = 96, // `
Console = GraveAccent,
World1 = 161, // non-US #1
World2 = 162, // non-US #2
Escape = 256,
Esc = Escape,
Enter = 257,
Tab = 258,
BackSpace = 259,
Insert = 260,
Delete = 261,
PrintScreen = 283,
Pause = 284,
Menu = 348,
};
}

View file

@ -0,0 +1,23 @@
#pragma once
#include <cstdint>
namespace lt::Mouse {
enum : uint8_t
{
Button1 = 0,
Button2 = 1,
Button3 = 2,
Button4 = 3,
Button5 = 4,
Button6 = 5,
Button7 = 6,
Button8 = 7,
LButton = Button1,
RButton = Button2,
MButton = Button3,
};
}

View file

@ -0,0 +1,55 @@
#pragma once
#include <app/system.hpp>
#include <ecs/registry.hpp>
#include <memory/reference.hpp>
#include <surface/components.hpp>
#include <surface/events/keyboard.hpp>
#include <surface/events/mouse.hpp>
namespace lt::input {
class System: public app::ISystem
{
public:
System(memory::Ref<ecs::Registry> registry);
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);
void on_surface_lost_focus();
void on_key_press(const lt::surface::KeyPressedEvent &event);
void on_key_release(const lt::surface::KeyReleasedEvent &event);
void on_pointer_move(const lt::surface::MouseMovedEvent &event);
void on_button_press(const lt::surface::ButtonPressedEvent &event);
void on_button_release(const lt::surface::ButtonReleasedEvent &event);
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 {};
};
} // namespace lt::input

View file

@ -1,292 +0,0 @@
/**
* @note: The reason this is a separate module, rather than being in the `Input` module is that
* the input is received from the hardware through the `Surface` module, and it is further parsed
* inside the `Input` module, USING the `Surface` module's events.
*
* Hence, both `Surface` and `Input` needs to agree to the same input codes, while `Input` depends
* on `Surface`. The simplest solution is to keep the codes in a 3rd module and make both depend on
* it. (I did not want to give `Surface` the responsibility of defining input codes...)
*/
export module input.codes;
import preliminary;
export enum class Key: u16 {
none = 0,
left_button,
l_button = left_button,
right_button,
r_button = right_button,
middle_button,
m_button = middle_button,
// the buttons on the sidse of some mouses
x_button_1,
x_button_2,
backspace,
tab,
capslock,
enter,
space,
delete_,
shift,
left_shit = shift,
l_shift = shift,
right_shift,
r_shift = right_shift,
control,
left_control = control,
l_control = control,
ctrl = control,
left_ctrl = control,
l_ctrl = control,
right_control,
r_control = right_control,
right_ctrl = right_control,
r_ctrl = right_control,
alt,
left_alt = alt,
l_alt = alt,
right_alt,
r_alt = right_alt,
pageup,
pagedown,
home,
end,
left_arrow,
l_arrow = left_arrow,
up_arrow,
u_arrow = up_arrow,
right_arrow,
r_arrow = right_arrow,
down_arrow,
d_arrow = down_arrow,
cancel,
pause,
select,
print,
snapshot, // aka. print-screen
insert,
help,
sleep,
eep = sleep,
digit_0,
digit_1,
digit_2,
digit_3,
digit_4,
digit_5,
digit_6,
digit_7,
digit_8,
digit_9,
a,
b,
c,
d,
e,
f,
g,
h,
i,
j,
k,
l,
m,
n,
o,
p,
q,
r,
s,
t,
u,
v,
w,
x,
y,
z,
super,
left_super = super,
l_super = super,
right_super,
r_super = right_super,
kp_0,
kp_1,
kp_2,
kp_3,
kp_4,
kp_5,
kp_6,
kp_7,
kp_8,
kp_9,
kp_decimal,
kp_divide,
kp_multiply,
kp_subtract,
kp_add,
kp_enter,
kp_equal,
f1,
f2,
f3,
f4,
f5,
f6,
f7,
f8,
f9,
f10,
f11,
f12,
/** Input was received but was none of the above. */
unknown,
};
export [[nodiscard]] constexpr auto to_string(Key key) -> std::string
{
using enum Key;
switch (key)
{
case none: return "<none>";
case left_button: return "left_button";
case right_button: return "right_button";
case middle_button: return "middle_button";
case x_button_1: return "x_button_1";
case x_button_2: return "x_button_2";
case backspace: return "backspace";
case tab: return "tab";
case capslock: return "capslock";
case enter: return "enter";
case space: return "space";
case delete_: return "delete";
case shift: return "shift";
case control: return "control";
case right_control: return "right_control";
case alt: return "alt";
case right_alt: return "right_alt";
case pageup: return "pageup";
case pagedown: return "pagedown";
case home: return "home";
case end: return "end";
case left_arrow: return "left_arrow";
case up_arrow: return "up_arrow";
case right_arrow: return "right_arrow";
case down_arrow: return "down_arrow";
case cancel: return "cancel";
case pause: return "pause";
case select: return "select";
case print: return "print";
case snapshot: return "snapshot";
case insert: return "insert";
case help: return "help";
case sleep: return "sleep";
case digit_0: return "0";
case digit_1: return "1";
case digit_2: return "2";
case digit_3: return "3";
case digit_4: return "4";
case digit_5: return "5";
case digit_6: return "6";
case digit_7: return "7";
case digit_8: return "8";
case digit_9: return "9";
case a: return "a";
case b: return "b";
case c: return "c";
case d: return "d";
case e: return "e";
case f: return "f";
case g: return "g";
case h: return "h";
case i: return "i";
case j: return "j";
case k: return "k";
case l: return "l";
case m: return "m";
case n: return "n";
case o: return "o";
case p: return "p";
case q: return "q";
case r: return "r";
case s: return "s";
case t: return "t";
case u: return "u";
case v: return "v";
case w: return "w";
case x: return "x";
case y: return "y";
case z: return "z";
case super: return "super";
case right_super: return "right_super";
case kp_0: return "kp_0";
case kp_1: return "kp_1";
case kp_2: return "kp_2";
case kp_3: return "kp_3";
case kp_4: return "kp_4";
case kp_5: return "kp_5";
case kp_6: return "kp_6";
case kp_7: return "kp_7";
case kp_8: return "kp_8";
case kp_9: return "kp_9";
case kp_decimal: return "kp_decimal";
case kp_divide: return "kp_divide";
case kp_multiply: return "kp_multiply";
case kp_subtract: return "kp_subtract";
case kp_add: return "kp_add";
case kp_enter: return "kp_enter";
case kp_equal: return "kp_equal";
case f1: return "f1";
case f2: return "f2";
case f3: return "f3";
case f4: return "f4";
case f5: return "f5";
case f6: return "f6";
case f7: return "f7";
case f8: return "f8";
case f9: return "f9";
case f10: return "f10";
case f11: return "f11";
case f12: return "f12";
case unknown: return "<unknown>";
}
return "<invalid>";
}

View file

@ -0,0 +1 @@
add_library_module(logger logger.cpp)

View file

@ -1,230 +0,0 @@
export module logger;
import preliminary;
export namespace lt::log {
/** Severity of a log message. */
enum class Level : u8
{
/** Lowest and most vebose log level, for tracing execution paths and events */
trace = 0,
/** Vebose log level, for enabling temporarily to debug */
debug = 1,
/** General information */
info = 2,
/** Things we should to be aware of and edge cases */
warn = 3,
/** Defects, bugs and undesired behaviour */
error = 4,
/** Unrecoverable errors */
critical = 5,
/**
* Logs from the testing-framework.
* Highest so we still get them while turning off all logs from the code under test.
*
* @note: log::test does NOT include source_location
*/
test = 6,
/** No logging */
off = 7,
};
auto min_severity = Level::trace;
auto set_min_severity(Level severity)
{
min_severity = severity;
}
template<typename... Args>
struct [[maybe_unused]] print
{
[[maybe_unused]] print(
Level level,
const std::source_location &location,
std::format_string<Args...> format,
Args &&...arguments
) noexcept
{
if (std::to_underlying(level) < std::to_underlying(min_severity))
{
return;
}
constexpr auto to_string = [](Level level) {
// clang-format off
switch (level)
{
using enum ::lt::log::Level;
case trace : return "\033[1;37m| trc |\033[0m";
case debug : return "\033[1;36m| dbg |\033[0m";
case info : return "\033[1;32m| inf |\033[0m";
case warn : return "\033[1;33m| wrn |\033[0m";
case error : return "\033[1;31m| err |\033[0m";
case critical: return "\033[1;41m| crt |\033[0m";
case off: return "off";
}
// clang-format on
std::unreachable();
};
const auto path = std::filesystem::path { location.file_name() };
std::println(
"{} {} ==> {}",
to_string(level),
std::format("{}:{}", path.filename().string(), location.line()),
std::format(format, std::forward<Args>(arguments)...)
);
}
[[maybe_unused]] print(
Level level,
std::format_string<Args...> format,
Args &&...arguments
) noexcept
{
constexpr auto to_string = [](Level level) {
// clang-format off
switch (level)
{
using enum ::lt::log::Level;
case trace : return "\033[1;37m| trc |\033[0m";
case debug : return "\033[1;36m| dbg |\033[0m";
case info : return "\033[1;32m| inf |\033[0m";
case warn : return "\033[1;33m| wrn |\033[0m";
case error : return "\033[1;31m| err |\033[0m";
case critical: return "\033[1;41m| crt |\033[0m";
case test : return "\033[1;33m| test |\033[0m";
case off : return "";
}
// clang-format on
std::unreachable();
};
std::println(
"{} {}",
to_string(level),
std::format(format, std::forward<Args>(arguments)...)
);
}
};
template<typename... Args>
print(Level, const std::source_location &, std::format_string<Args...>, Args &&...) noexcept
-> print<Args...>;
template<typename... Args>
struct [[maybe_unused]] trace
{
[[maybe_unused]] trace(
std::format_string<Args...> format,
Args &&...arguments,
const std::source_location &location = std::source_location::current()
) noexcept
{
print(Level::trace, location, format, std::forward<Args>(arguments)...);
}
};
template<typename... Args>
trace(std::format_string<Args...>, Args &&...) noexcept -> trace<Args...>;
template<typename... Args>
struct [[maybe_unused]] debug
{
[[maybe_unused]] debug(
std::format_string<Args...> format,
Args &&...arguments,
const std::source_location &location = std::source_location::current()
) noexcept
{
print(Level::debug, location, format, std::forward<Args>(arguments)...);
}
};
template<typename... Args>
debug(std::format_string<Args...>, Args &&...) noexcept -> debug<Args...>;
template<typename... Args>
struct [[maybe_unused]] info
{
[[maybe_unused]] info(
std::format_string<Args...> format,
Args &&...arguments,
const std::source_location &location = std::source_location::current()
) noexcept
{
print(Level::info, location, format, std::forward<Args>(arguments)...);
}
};
template<typename... Args>
info(std::format_string<Args...>, Args &&...) noexcept -> info<Args...>;
template<typename... Args>
struct [[maybe_unused]] warn
{
[[maybe_unused]] warn(
std::format_string<Args...> format,
Args &&...arguments,
const std::source_location &location = std::source_location::current()
) noexcept
{
print(Level::warn, location, format, std::forward<Args>(arguments)...);
}
};
template<typename... Args>
warn(std::format_string<Args...>, Args &&...) noexcept -> warn<Args...>;
template<typename... Args>
struct [[maybe_unused]] error
{
[[maybe_unused]] error(
std::format_string<Args...> format,
Args &&...arguments,
const std::source_location &location = std::source_location::current()
) noexcept
{
print(Level::error, location, format, std::forward<Args>(arguments)...);
}
};
template<typename... Args>
error(std::format_string<Args...>, Args &&...) noexcept -> error<Args...>;
template<typename... Args>
struct [[maybe_unused]] critical
{
[[maybe_unused]] critical(
std::format_string<Args...> format,
Args &&...arguments,
const std::source_location &location = std::source_location::current()
) noexcept
{
print(Level::critical, location, format, std::forward<Args>(arguments)...);
}
};
template<typename... Args>
critical(std::format_string<Args...>, Args &&...) noexcept -> critical<Args...>;
template<typename... Args>
void test(std::format_string<Args...> format, Args &&...arguments) noexcept
{
print(Level::test, format, std::forward<Args>(arguments)...);
}
} // namespace lt::log

View file

@ -1,21 +0,0 @@
import test;
Suite suite = [] {
Case { "formatless" } = [] {
lt::log::trace("trace");
lt::log::debug("debug");
lt::log::info("info");
lt::log::warn("warn");
lt::log::error("error");
lt::log::critical("critical");
};
Case { "formatted" } = [] {
lt::log::trace("trace {}", 69);
lt::log::debug("debug {}", 69);
lt::log::info("info {}", 69);
lt::log::warn("warn {}", 69);
lt::log::error("error {}", 69);
lt::log::critical("critical {}", 69);
};
};

View file

@ -0,0 +1 @@
#include <logger/logger.hpp>

View file

@ -0,0 +1,89 @@
#pragma once
#include <format>
#include <print>
/** Severity of a log message. */
enum class LogLvl : uint8_t
{
/** Lowest and most vebose log level, for tracing execution paths and events */
trace = 0,
/** Vebose log level, for enabling temporarily to debug */
debug = 1,
/** General information */
info = 2,
/** Things we should to be aware of and edge cases */
warn = 3,
/** Defects, bugs and undesired behaviour */
error = 4,
/** Unrecoverable errors */
critical = 5,
/** No logging */
off = 6,
};
/** Simple console logger */
class Logger
{
public:
void static show_imgui_window();
template<typename... 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) noexcept
{
std::ignore = lvl;
std::println("{}", message);
}
private:
Logger() = default;
};
template<typename... 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) noexcept
{
Logger::log(LogLvl::debug, fmt, std::forward<Args>(args)...);
}
template<typename... 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) noexcept
{
Logger::log(LogLvl::warn, fmt, std::forward<Args>(args)...);
}
template<typename... 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) noexcept
{
Logger::log(LogLvl::critical, fmt, std::forward<Args>(args)...);
}

View file

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

View file

@ -1,17 +0,0 @@
export module math.components;
import preliminary;
import math.vec3;
namespace lt::math::components {
export struct Transform
{
math::vec3 translation;
math::vec3 scale;
math::vec3 rotation;
};
} // namespace lt::math::components

View file

@ -1,185 +0,0 @@
export module math.mat4;
import preliminary;
import math.vec2;
import math.vec3;
import math.vec4;
export namespace lt::math {
/** A 4 by 4 matrix, column major order
*
* @todo(Light): Use std::simd when it's implemented. */
template<typename T = f32>
requires(std::is_arithmetic_v<T>)
struct mat4_impl
{
using Column_T = vec4_impl<T>;
using Underlying_T = Column_T::Underlying_T;
static constexpr auto num_elements = 4u * 4u;
constexpr explicit mat4_impl(T scalar = T {})
: values(
{
Column_T { scalar },
Column_T { scalar },
Column_T { scalar },
Column_T { scalar },
}
)
{
}
constexpr mat4_impl(
// clang-format off
const T& x0, const T& x1, const T& x2, const T& x3,
const T& y0, const T& y1, const T& y2, const T& y3,
const T& z0, const T& z1, const T& z2, const T& z3,
const T& w0, const T& w1, const T& w2, const T& w3)
// clang-format on
: values({ { x0, x1, x2, x3 }, { y0, y1, y2, y3 }, { z0, z1, z2, z3 }, { w0, w1, w2, w3 } })
{
}
constexpr mat4_impl(
const Column_T &column_x,
const Column_T &column_y,
const Column_T &column_z,
const Column_T &column_w
)
: values({ column_x, column_y, column_z, column_w })
{
}
[[nodiscard]] static constexpr auto identity() -> mat4_impl<T>
{
return mat4_impl<T> {
{ 1 }, {}, {}, {}, //
{}, { 1 }, {}, {}, //
{}, {}, { 1 }, {}, //
{}, {}, {}, { 1 }, //
};
}
[[nodiscard]] constexpr auto operator*(const mat4_impl<T> &other) const -> mat4_impl<T>
{
const auto &[a_x, a_y, a_z, a_w] = values;
const auto &[b_x, b_y, b_z, b_w] = other.values;
return mat4_impl<T>(
// X column
a_x.x * b_x.x + a_y.x * b_x.y + a_z.x * b_x.z + a_w.x * b_x.w,
a_x.y * b_x.x + a_y.y * b_x.y + a_z.y * b_x.z + a_w.y * b_x.w,
a_x.z * b_x.x + a_y.z * b_x.y + a_z.z * b_x.z + a_w.z * b_x.w,
a_x.w * b_x.x + a_y.w * b_x.y + a_z.w * b_x.z + a_w.w * b_x.w,
// Y column
a_x.x * b_y.x + a_y.x * b_y.y + a_z.x * b_y.z + a_w.x * b_y.w,
a_x.y * b_y.x + a_y.y * b_y.y + a_z.y * b_y.z + a_w.y * b_y.w,
a_x.z * b_y.x + a_y.z * b_y.y + a_z.z * b_y.z + a_w.z * b_y.w,
a_x.w * b_y.x + a_y.w * b_y.y + a_z.w * b_y.z + a_w.w * b_y.w,
// Z column
a_x.x * b_z.x + a_y.x * b_z.y + a_z.x * b_z.z + a_w.x * b_z.w,
a_x.y * b_z.x + a_y.y * b_z.y + a_z.y * b_z.z + a_w.y * b_z.w,
a_x.z * b_z.x + a_y.z * b_z.y + a_z.z * b_z.z + a_w.z * b_z.w,
a_x.w * b_z.x + a_y.w * b_z.y + a_z.w * b_z.z + a_w.w * b_z.w,
// W column
a_x.x * b_w.x + a_y.x * b_w.y + a_z.x * b_w.z + a_w.x * b_w.w,
a_x.y * b_w.x + a_y.y * b_w.y + a_z.y * b_w.z + a_w.y * b_w.w,
a_x.z * b_w.x + a_y.z * b_w.y + a_z.z * b_w.z + a_w.z * b_w.w,
a_x.w * b_w.x + a_y.w * b_w.y + a_z.w * b_w.z + a_w.w * b_w.w
);
}
[[nodiscard]] constexpr auto operator[](size_t idx) -> Column_T &
{
debug_check(idx < num_elements, "mat4 out of bound access: {}", idx);
return values[idx];
}
[[nodiscard]] constexpr auto operator[](size_t idx) const -> const Column_T &
{
return values[idx];
}
[[nodiscard]] static constexpr auto transpose(const mat4_impl<T> &mat) -> mat4_impl<T>
{
const auto &[x, y, z, w] = mat.values;
return mat4_impl<T> {
x.x, y.x, z.x, w.x, x.y, y.y, z.y, w.y, x.z, y.z, z.z, w.z, x.w, y.w, z.w, w.w,
};
}
[[nodiscard]] static constexpr auto translate(const vec3_impl<T> &vec) -> mat4_impl<T>
{
return mat4_impl<T>(
T { 1 },
T { 0 },
T { 0 },
T { 0 },
T { 0 },
T { 1 },
T { 0 },
T { 0 },
T { 0 },
T { 0 },
T { 1 },
T { 0 },
vec.x,
vec.y,
vec.z,
T { 1 }
);
}
[[nodiscard]] static constexpr auto scale(const vec3_impl<T> &vec) -> mat4_impl<T>
{
return mat4_impl<T>(
vec.x,
T { 0 },
T { 0 },
T { 0 },
T { 0 },
vec.y,
T { 0 },
T { 0 },
T { 0 },
T { 0 },
vec.z,
T { 0 },
T { 0 },
T { 0 },
T { 0 },
T { 1 }
);
}
std::array<Column_T, 4u> values;
};
using mat4 = mat4_impl<f32>;
using mat4_f32 = mat4;
using mat4_f64 = mat4_impl<f64>;
using mat4_i8 = mat4_impl<i8>;
using mat4_i16 = mat4_impl<i16>;
using mat4_i32 = mat4_impl<i32>;
using mat4_i64 = mat4_impl<i64>;
using mat4_u8 = mat4_impl<u8>;
using mat4_u16 = mat4_impl<u16>;
using mat4_u32 = mat4_impl<u32>;
using mat4_u64 = mat4_impl<u64>;
} // namespace lt::math

View file

@ -1,413 +0,0 @@
import test;
import math.vec3;
import math.mat4;
using vec3 = ::lt::math::vec3;
using mat4 = ::lt::math::mat4;
Suite static_tests = "mat4_static_checks"_suite = [] {
constexpr auto num_elements = lt::math::mat4::num_elements;
static_assert(num_elements == 4u * 4u);
static_assert(std::is_same_v<lt::math::mat4, lt::math::mat4_f32>);
static_assert(sizeof(lt::math::mat4_f32) == sizeof(f32) * num_elements);
static_assert(sizeof(lt::math::mat4_f64) == sizeof(f64) * num_elements);
static_assert(sizeof(lt::math::mat4_i8) == sizeof(i8) * num_elements);
static_assert(sizeof(lt::math::mat4_i16) == sizeof(i16) * num_elements);
static_assert(sizeof(lt::math::mat4_i32) == sizeof(i32) * num_elements);
static_assert(sizeof(lt::math::mat4_i64) == sizeof(i64) * num_elements);
static_assert(sizeof(lt::math::mat4_u8) == sizeof(u8) * num_elements);
static_assert(sizeof(lt::math::mat4_u16) == sizeof(u16) * num_elements);
static_assert(sizeof(lt::math::mat4_u32) == sizeof(u32) * num_elements);
static_assert(sizeof(lt::math::mat4_u64) == sizeof(u64) * num_elements);
};
Suite raii = "mat4_raii"_suite = [] {
Case { "happy paths" } = [] {
ignore = mat4 {};
ignore = mat4 { 1.0 };
ignore = mat4 {
1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0,
};
ignore = mat4 {
mat4::Column_T { 1.0, 2.0, 3.0, 4.0 },
mat4::Column_T { 5.0, 6.0, 7.0, 8.0 },
mat4::Column_T { 9.0, 10.0, 11.0, 12.0 },
mat4::Column_T { 13.0, 14.0, 15.0, 16.0 },
};
};
Case { "unhappy paths" } = [] {
};
Case { "many" } = [] {
for (auto idx : std::views::iota(0, 1'000'000))
{
ignore = idx;
ignore = mat4 {};
ignore = mat4 { 1.0 };
ignore = mat4 {
1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0,
9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0,
};
ignore = mat4 {
mat4::Column_T { 1.0, 2.0, 3.0, 4.0 },
mat4::Column_T { 5.0, 6.0, 7.0, 8.0 },
mat4::Column_T { 9.0, 10.0, 11.0, 12.0 },
mat4::Column_T { 13.0, 14.0, 15.0, 16.0 },
};
}
};
Case { "post default construct has correct state" } = [] {
const auto [x, y, z, w] = mat4 {}.values;
expect_eq(x[0], mat4::Underlying_T {});
expect_eq(x[1], mat4::Underlying_T {});
expect_eq(x[2], mat4::Underlying_T {});
expect_eq(x[3], mat4::Underlying_T {});
expect_eq(y[0], mat4::Underlying_T {});
expect_eq(y[1], mat4::Underlying_T {});
expect_eq(y[2], mat4::Underlying_T {});
expect_eq(y[3], mat4::Underlying_T {});
expect_eq(z[0], mat4::Underlying_T {});
expect_eq(z[1], mat4::Underlying_T {});
expect_eq(z[2], mat4::Underlying_T {});
expect_eq(z[3], mat4::Underlying_T {});
expect_eq(w[0], mat4::Underlying_T {});
expect_eq(w[1], mat4::Underlying_T {});
expect_eq(w[2], mat4::Underlying_T {});
expect_eq(w[3], mat4::Underlying_T {});
};
Case { "post scalar construct has correct state" } = [] {
const auto [x, y, z, w] = mat4 { 69.0 }.values;
expect_eq(x[0], mat4::Underlying_T { 69.0 });
expect_eq(x[1], mat4::Underlying_T { 69.0 });
expect_eq(x[2], mat4::Underlying_T { 69.0 });
expect_eq(x[3], mat4::Underlying_T { 69.0 });
expect_eq(y[0], mat4::Underlying_T { 69.0 });
expect_eq(y[1], mat4::Underlying_T { 69.0 });
expect_eq(y[2], mat4::Underlying_T { 69.0 });
expect_eq(y[3], mat4::Underlying_T { 69.0 });
expect_eq(z[0], mat4::Underlying_T { 69.0 });
expect_eq(z[1], mat4::Underlying_T { 69.0 });
expect_eq(z[2], mat4::Underlying_T { 69.0 });
expect_eq(z[3], mat4::Underlying_T { 69.0 });
expect_eq(w[0], mat4::Underlying_T { 69.0 });
expect_eq(w[1], mat4::Underlying_T { 69.0 });
expect_eq(w[2], mat4::Underlying_T { 69.0 });
expect_eq(w[3], mat4::Underlying_T { 69.0 });
};
Case { "post construct with all values has correct state" } = [] {
const auto [x, y, z, w] = mat4 {
1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0,
}.values;
expect_eq(x[0], mat4::Underlying_T { 1.0 });
expect_eq(x[1], mat4::Underlying_T { 2.0 });
expect_eq(x[2], mat4::Underlying_T { 3.0 });
expect_eq(x[3], mat4::Underlying_T { 4.0 });
expect_eq(y[0], mat4::Underlying_T { 5.0 });
expect_eq(y[1], mat4::Underlying_T { 6.0 });
expect_eq(y[2], mat4::Underlying_T { 7.0 });
expect_eq(y[3], mat4::Underlying_T { 8.0 });
expect_eq(z[0], mat4::Underlying_T { 9.0 });
expect_eq(z[1], mat4::Underlying_T { 10.0 });
expect_eq(z[2], mat4::Underlying_T { 11.0 });
expect_eq(z[3], mat4::Underlying_T { 12.0 });
expect_eq(w[0], mat4::Underlying_T { 13.0 });
expect_eq(w[1], mat4::Underlying_T { 14.0 });
expect_eq(w[2], mat4::Underlying_T { 15.0 });
expect_eq(w[3], mat4::Underlying_T { 16.0 });
};
Case { "post construct with columns has correct state" } = [] {
const auto [x, y, z, w] = mat4 {
mat4::Column_T { 1.0, 2.0, 3.0, 4.0 },
mat4::Column_T { 5.0, 6.0, 7.0, 8.0 },
mat4::Column_T { 9.0, 10.0, 11.0, 12.0 },
mat4::Column_T { 13.0, 14.0, 15.0, 16.0 },
}.values;
expect_eq(x[0], mat4::Underlying_T { 1.0 });
expect_eq(x[1], mat4::Underlying_T { 2.0 });
expect_eq(x[2], mat4::Underlying_T { 3.0 });
expect_eq(x[3], mat4::Underlying_T { 4.0 });
expect_eq(y[0], mat4::Underlying_T { 5.0 });
expect_eq(y[1], mat4::Underlying_T { 6.0 });
expect_eq(y[2], mat4::Underlying_T { 7.0 });
expect_eq(y[3], mat4::Underlying_T { 8.0 });
expect_eq(z[0], mat4::Underlying_T { 9.0 });
expect_eq(z[1], mat4::Underlying_T { 10.0 });
expect_eq(z[2], mat4::Underlying_T { 11.0 });
expect_eq(z[3], mat4::Underlying_T { 12.0 });
expect_eq(w[0], mat4::Underlying_T { 13.0 });
expect_eq(w[1], mat4::Underlying_T { 14.0 });
expect_eq(w[2], mat4::Underlying_T { 15.0 });
expect_eq(w[3], mat4::Underlying_T { 16.0 });
};
Case { "post construct identity matrix has correct state" } = [] {
const auto [x, y, z, w] = mat4::identity().values;
expect_eq(x[0], mat4::Underlying_T { 1 });
expect_eq(x[1], mat4::Underlying_T {});
expect_eq(x[2], mat4::Underlying_T {});
expect_eq(x[3], mat4::Underlying_T {});
expect_eq(y[0], mat4::Underlying_T {});
expect_eq(y[1], mat4::Underlying_T { 1 });
expect_eq(y[2], mat4::Underlying_T {});
expect_eq(y[3], mat4::Underlying_T {});
expect_eq(z[0], mat4::Underlying_T {});
expect_eq(z[1], mat4::Underlying_T {});
expect_eq(z[2], mat4::Underlying_T { 1 });
expect_eq(z[3], mat4::Underlying_T {});
expect_eq(w[0], mat4::Underlying_T {});
expect_eq(w[1], mat4::Underlying_T {});
expect_eq(w[2], mat4::Underlying_T {});
expect_eq(w[3], mat4::Underlying_T { 1 });
};
};
Suite arithmetic_operators = "mat4_arithmetic_operators"_suite = [] {
Case { "operator *" } = [] {
const auto lhs = mat4 {
mat4::Column_T { 1.0, 2.0, 3.0, 4.0 },
mat4::Column_T { 5.0, 6.0, 7.0, 8.0 },
mat4::Column_T { 9.0, 10.0, 11.0, 12.0 },
mat4::Column_T { 13.0, 14.0, 15.0, 16.0 },
};
const auto rhs = mat4 {
mat4::Column_T { 17.0, 18.0, 19.0, 20.0 },
mat4::Column_T { 21.0, 22.0, 23.0, 24.0 },
mat4::Column_T { 25.0, 26.0, 27.0, 28.0 },
mat4::Column_T { 29.0, 30.0, 31.0, 32.0 },
};
const auto [x, y, z, w] = (lhs * rhs).values;
expect_eq(x[0], 538.0);
expect_eq(x[1], 612.0);
expect_eq(x[2], 686.0);
expect_eq(x[3], 760.0);
expect_eq(y[0], 650.0);
expect_eq(y[1], 740.0);
expect_eq(y[2], 830.0);
expect_eq(y[3], 920.0);
expect_eq(z[0], 762.0);
expect_eq(z[1], 868.0);
expect_eq(z[2], 974.0);
expect_eq(z[3], 1080.0);
expect_eq(w[0], 874.0);
expect_eq(w[1], 996.0);
expect_eq(w[2], 1118.0);
expect_eq(w[3], 1240.0);
};
};
Suite access_operators = "mat4_access_operators"_suite = [] {
Case { "operator []" } = [] {
auto mat = mat4 {
1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0,
};
expect_eq(mat[0][0], 1.0);
expect_eq(mat[0][1], 2.0);
expect_eq(mat[0][2], 3.0);
expect_eq(mat[0][3], 4.0);
expect_eq(mat[1][0], 5.0);
expect_eq(mat[1][1], 6.0);
expect_eq(mat[1][2], 7.0);
expect_eq(mat[1][3], 8.0);
expect_eq(mat[2][0], 9.0);
expect_eq(mat[2][1], 10.0);
expect_eq(mat[2][2], 11.0);
expect_eq(mat[2][3], 12.0);
expect_eq(mat[3][0], 13.0);
expect_eq(mat[3][1], 14.0);
expect_eq(mat[3][2], 15.0);
expect_eq(mat[3][3], 16.0);
};
Case { "operator [] const" } = [] {
const auto mat = mat4 {
1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0,
};
expect_eq(mat[0][0], 1.0);
expect_eq(mat[0][1], 2.0);
expect_eq(mat[0][2], 3.0);
expect_eq(mat[0][3], 4.0);
expect_eq(mat[1][0], 5.0);
expect_eq(mat[1][1], 6.0);
expect_eq(mat[1][2], 7.0);
expect_eq(mat[1][3], 8.0);
expect_eq(mat[2][0], 9.0);
expect_eq(mat[2][1], 10.0);
expect_eq(mat[2][2], 11.0);
expect_eq(mat[2][3], 12.0);
expect_eq(mat[3][0], 13.0);
expect_eq(mat[3][1], 14.0);
expect_eq(mat[3][2], 15.0);
expect_eq(mat[3][3], 16.0);
};
};
Suite transformations = "mat4_transformations"_suite = [] {
Case { "translate" } = [] {
const auto &[x, y, z, w] = mat4::translate(vec3 { 1, 2, 3 }).values;
// identity basis
expect_eq(x[0], 1);
expect_eq(x[1], 0);
expect_eq(x[2], 0);
expect_eq(x[3], 0);
expect_eq(y[0], 0);
expect_eq(y[1], 1);
expect_eq(y[2], 0);
expect_eq(y[3], 0);
expect_eq(z[0], 0);
expect_eq(z[1], 0);
expect_eq(z[2], 1);
expect_eq(z[3], 0);
// translation column
expect_eq(w[0], 1);
expect_eq(w[1], 2);
expect_eq(w[2], 3);
expect_eq(w[3], 1);
};
Case { "scale" } = [] {
const auto [x, y, z, w] = mat4::scale(vec3 { 2, 3, 4 }).values;
expect_eq(x[0], 2);
expect_eq(x[1], 0);
expect_eq(x[2], 0);
expect_eq(x[3], 0);
expect_eq(y[0], 0);
expect_eq(y[1], 3);
expect_eq(y[2], 0);
expect_eq(y[3], 0);
expect_eq(z[0], 0);
expect_eq(z[1], 0);
expect_eq(z[2], 4);
expect_eq(z[3], 0);
expect_eq(w[0], 0);
expect_eq(w[1], 0);
expect_eq(w[2], 0);
expect_eq(w[3], 1);
};
Case { "scale -> translate" } = [] {
const auto scale = mat4::scale(vec3 { 2, 2, 2 });
const auto translate = mat4::translate(vec3 { 1, 2, 3 });
const auto [x, y, z, w] = (scale * translate).values;
// scaled basis
expect_eq(x[0], 2);
expect_eq(x[1], 0);
expect_eq(x[2], 0);
expect_eq(x[3], 0);
expect_eq(y[0], 0);
expect_eq(y[1], 2);
expect_eq(y[2], 0);
expect_eq(y[3], 0);
expect_eq(z[0], 0);
expect_eq(z[1], 0);
expect_eq(z[2], 2);
expect_eq(z[3], 0);
// translation is scaled (local-space translation)
expect_eq(w[0], 2); // 1 * 2
expect_eq(w[1], 4); // 2 * 2
expect_eq(w[2], 6); // 3 * 2
expect_eq(w[3], 1);
};
Case { "transpose" } = [] {
const auto mat = mat4 {
mat4::Column_T { 1, 2, 3, 4 },
mat4::Column_T { 5, 6, 7, 8 },
mat4::Column_T { 9, 10, 11, 12 },
mat4::Column_T { 13, 14, 15, 16 },
};
const auto [x, y, z, w] = mat4::transpose(mat).values;
// rows become columns
expect_eq(x[0], 1);
expect_eq(x[1], 5);
expect_eq(x[2], 9);
expect_eq(x[3], 13);
expect_eq(y[0], 2);
expect_eq(y[1], 6);
expect_eq(y[2], 10);
expect_eq(y[3], 14);
expect_eq(z[0], 3);
expect_eq(z[1], 7);
expect_eq(z[2], 11);
expect_eq(z[3], 15);
expect_eq(w[0], 4);
expect_eq(w[1], 8);
expect_eq(w[2], 12);
expect_eq(w[3], 16);
};
Case { "transpose twice" } = [] {
const auto mat = mat4 {
mat4::Column_T { 1, 2, 3, 4 },
mat4::Column_T { 5, 6, 7, 8 },
mat4::Column_T { 9, 10, 11, 12 },
mat4::Column_T { 13, 14, 15, 16 },
};
const auto [x, y, z, w] = mat4::transpose(mat4::transpose(mat)).values;
expect_eq(x[0], 1);
expect_eq(x[1], 2);
expect_eq(x[2], 3);
expect_eq(x[3], 4);
expect_eq(y[0], 5);
expect_eq(y[1], 6);
expect_eq(y[2], 7);
expect_eq(y[3], 8);
expect_eq(z[0], 9);
expect_eq(z[1], 10);
expect_eq(z[2], 11);
expect_eq(z[3], 12);
expect_eq(w[0], 13);
expect_eq(w[1], 14);
expect_eq(w[2], 15);
expect_eq(w[3], 16);
};
};

View file

@ -1,9 +1,8 @@
export module math.algebra; #pragma once
import preliminary; #include <math/mat4.hpp>
import math.mat4;
export namespace lt::math { namespace lt::math {
/** /**
* let... * let...
@ -32,30 +31,25 @@ export namespace lt::math {
* *
* the 1 at [z][3] is to save the Z axis into the resulting W for perspective division. * the 1 at [z][3] is to save the Z axis into the resulting W for perspective division.
* *
* @ref Thanks to @pikuma for explaining the math behind this: * thanks to pikuma: https://www.youtube.com/watch?v=EqNcqBdrNyI
* https://www.youtube.com/watch?v=EqNcqBdrNyI
*/ */
template<typename T> template<typename T>
requires(std::is_arithmetic_v<T>) constexpr auto perspective(T field_of_view, T aspect_ratio, T z_near, T z_far)
constexpr auto perspective(T field_of_view, T aspect_ratio, T z_near, T z_far) -> mat4_impl<T>
{ {
const T half_fov_tan = std::tan(field_of_view / static_cast<T>(2)); const T half_fov_tan = std::tan(field_of_view / static_cast<T>(2));
auto result = mat4_impl<T>::identity(); auto result = mat4_impl<T> { T { 0 } };
result[0][0] = T { 1 } / (aspect_ratio * half_fov_tan); result[0][0] = T { 1 } / (aspect_ratio * half_fov_tan);
//
result[1][1] = T { 1 } / (half_fov_tan); result[1][1] = T { 1 } / (half_fov_tan);
//
// result[2][2] = -(z_far + z_near) / (z_far - z_near); result[2][2] = -(z_far + z_near) / (z_far - z_near);
//
result[2][2] = z_far / (z_far - z_near);
//
result[2][3] = -T { 1 }; result[2][3] = -T { 1 };
//
// result[3][2] = -(T { 2 } * z_far * z_near) / (z_far - z_near); result[3][2] = -(T { 2 } * z_far * z_near) / (z_far - z_near);
result[3][2] = -(z_far * z_near) / (z_far - z_near);
//
return result; return result;
} }

View file

@ -0,0 +1,109 @@
#pragma once
#include <math/vec3.hpp>
#include <math/vec4.hpp>
namespace lt::math {
template<typename T = float>
struct mat4_impl
{
using Column_T = vec4_impl<T>;
constexpr explicit mat4_impl(T scalar = 0)
: values(
{
Column_T { scalar },
Column_T { scalar },
Column_T { scalar },
Column_T { scalar },
}
)
{
}
// clang-format off
constexpr mat4_impl(
const T& x0, const T& y0, const T& z0, const T& w0,
const T& x1, const T& y1, const T& z1, const T& w1,
const T& x2, const T& y2, const T& z2, const T& w2,
const T& x3, const T& y3, const T& z3, const T& w3
)
// clang-format on
: values({ { x0, x1, x2, x3 }, { y0, y1, y2, y3 }, { z0, z1, z2, z3 }, { w0, w1, w2, w3 } })
{
}
constexpr mat4_impl(
const Column_T &column_x,
const Column_T &column_y,
const Column_T &column_z,
const Column_T &column_w
)
: values({ column_x, column_y, column_z, column_w })
{
}
[[nodiscard]] constexpr auto identity() -> mat4_impl<T>
{
return mat4_impl<T> {
{ 1 }, {}, {}, {}, //
{}, { 1 }, {}, {}, //
{}, {}, { 1 }, {}, //
{}, {}, {}, { 1 }, //
};
}
[[nodiscard]] constexpr auto operator[](size_t idx) -> Column_T &
{
return values[idx];
}
[[nodiscard]] constexpr auto operator[](size_t idx) const -> const Column_T &
{
return values[idx];
}
[[nodiscard]] constexpr auto operator*(const mat4_impl<T> &other) const -> mat4_impl<T>
{
return mat4_impl<T> {};
}
[[nodiscard]] constexpr auto operator*(const vec4_impl<T> &other) const -> vec4_impl<T>
{
return vec4_impl<T> {};
}
std::array<Column_T, 4> values; // NOLINT
};
template<typename T>
[[nodiscard]] inline auto translate(const vec3_impl<T> &value) -> mat4_impl<T>
{
return mat4_impl<T> {};
}
template<typename T>
[[nodiscard]] inline auto rotate(float value, const vec3_impl<T> &xyz) -> mat4_impl<T>
{
return mat4_impl<T> {};
}
template<typename T>
[[nodiscard]] inline auto scale(const vec3_impl<T> &value) -> mat4_impl<T>
{
return mat4_impl<T> {};
}
template<typename T>
[[nodiscard]] inline auto inverse(const mat4_impl<T> &value) -> mat4_impl<T>
{
return mat4_impl<T> {};
}
using mat4 = mat4_impl<float>;
using imat4 = mat4_impl<int32_t>;
using umat4 = mat4_impl<uint32_t>;
} // namespace lt::math

View file

@ -0,0 +1,26 @@
#pragma once
namespace lt::math {
[[nodiscard]] constexpr auto radians(float degrees) -> float
{
return degrees * 0.01745329251994329576923690768489f;
}
[[nodiscard]] constexpr auto radians(double degrees) -> double
{
return degrees * 0.01745329251994329576923690768489;
}
[[nodiscard]] constexpr auto degrees(float radians) -> float
{
return radians * 57.295779513082320876798154814105f;
}
[[nodiscard]] constexpr auto degrees(double radians) -> double
{
return radians * 57.295779513082320876798154814105;
}
} // namespace lt::math

View file

@ -0,0 +1,80 @@
#pragma once
namespace lt::math {
template<typename T = float>
struct vec2_impl
{
constexpr vec2_impl(): x(), y()
{
}
constexpr explicit vec2_impl(T scalar): x(scalar), y(scalar)
{
}
constexpr vec2_impl(T x, T y): x(x), y(y)
{
}
[[nodiscard]] auto operator==(const vec2_impl<T> &other) const -> bool
{
return x == other.x && y == other.y;
}
[[nodiscard]] auto operator!=(const vec2_impl<T> &other) const -> bool
{
return !(*this == other);
}
[[nodiscard]] auto operator*(const vec2_impl<T> &other) const -> vec2_impl
{
return {
x * other.x,
y * other.y,
};
}
[[nodiscard]] auto operator-(const vec2_impl<T> &other) const -> vec2_impl
{
return {
x - other.x,
y - other.y,
};
}
[[nodiscard]] auto operator*(float scalar) const -> vec2_impl
{
return {
x * scalar,
y * scalar,
};
}
T x; // NOLINT
T y; // NOLINT
};
using vec2 = vec2_impl<float>;
using ivec2 = vec2_impl<int32_t>;
using uvec2 = vec2_impl<uint32_t>;
} // namespace lt::math
template<typename T>
struct std::formatter<lt::math::vec2_impl<T>>
{
constexpr auto parse(std::format_parse_context &context)
{
return context.begin();
}
auto format(const lt::math::vec2_impl<T> &val, std::format_context &context) const
{
return std::format_to(context.out(), "{}, {}", val.x, val.y);
}
};

View file

@ -0,0 +1,84 @@
#pragma once
#include <cstdint>
#include <math/vec2.hpp>
namespace lt::math {
template<typename T = float>
struct vec3_impl
{
constexpr vec3_impl(): x(), y(), z()
{
}
constexpr explicit vec3_impl(T scalar): x(scalar), y(scalar), z(scalar)
{
}
constexpr vec3_impl(T x, T y, T z): x(x), y(y), z(z)
{
}
[[nodiscard]] auto operator==(const vec3_impl<T> &other) const -> bool
{
return x == other.x && y == other.y && z == other.z;
}
[[nodiscard]] auto operator!=(const vec3_impl<T> &other) const -> bool
{
return !(*this == other);
}
[[nodiscard]] constexpr auto operator-(const vec3_impl<T> &other) const -> vec3_impl
{
return {
x - other.x,
y - other.y,
z - other.z,
};
}
[[nodiscard]] constexpr auto operator*(const vec3_impl<T> &other) const -> vec3_impl
{
return {
x * other.x,
y * other.y,
z * other.z,
};
}
friend auto operator<<(std::ostream &stream, vec3_impl<T> value) -> std::ostream &
{
stream << value.x << ", " << value.y << ", " << value.z;
return stream;
}
T x; // NOLINT
T y; // NOLINT
T z; // NOLINT
};
using vec3 = vec3_impl<float>;
using ivec3 = vec3_impl<int32_t>;
using uvec3 = vec3_impl<uint32_t>;
} // namespace lt::math
template<typename T>
struct std::formatter<lt::math::vec3_impl<T>>
{
constexpr auto parse(std::format_parse_context &context)
{
return context.begin();
}
auto format(const lt::math::vec3_impl<T> &val, std::format_context &context) const
{
return std::format_to(context.out(), "{}, {}, {}", val.x, val.y, val.z);
}
};

View file

@ -0,0 +1,109 @@
#pragma once
#include <array>
#include <cstdint>
namespace lt::math {
template<typename T = float>
struct vec4_impl
{
constexpr vec4_impl(): x(), y(), z(), w()
{
}
constexpr explicit vec4_impl(T scalar): x(scalar), y(scalar), z(scalar), w(scalar)
{
}
constexpr vec4_impl(T x, T y, T z, T w): x(x), y(y), z(z), w(w)
{
}
[[nodiscard]] auto operator==(const vec4_impl<T> &other) const -> bool
{
return x == other.x && y == other.y && z == other.z && w == other.w;
}
[[nodiscard]] auto operator!=(const vec4_impl<T> &other) const -> bool
{
return !(*this == other);
}
[[nodiscard]] constexpr auto operator-(const vec4_impl<T> &other) const -> vec4_impl
{
return {
x - other.x,
y - other.y,
z - other.z,
w - other.w,
};
}
[[nodiscard]] constexpr auto operator[](size_t idx) -> T &
{
return values[idx];
}
[[nodiscard]] constexpr auto operator[](size_t idx) const -> const T &
{
return values[idx];
}
friend auto operator<<(std::ostream &stream, vec4_impl<T> value) -> std::ostream &
{
stream << value.x << ", " << value.y << ", " << value.z << ", " << value.w;
return stream;
}
// NOLINTNEXTLINE
union
{
struct
{
T x;
T y;
T z;
T w;
};
struct
{
T r;
T g;
T b;
T a;
};
struct
{
std::array<T, 4> values;
};
};
};
using vec4 = vec4_impl<float>;
using ivec4 = vec4_impl<int32_t>;
using uvec4 = vec4_impl<uint32_t>;
} // namespace lt::math
template<typename T>
struct std::formatter<lt::math::vec4_impl<T>>
{
constexpr auto parse(std::format_parse_context &context)
{
return context.begin();
}
auto format(const lt::math::vec4_impl<T> &val, std::format_context &context) const
{
return std::format_to(context.out(), "{}, {}, {}, {}", val.x, val.y, val.z, val.w);
}
};

View file

@ -1,27 +0,0 @@
export module math.trig;
import preliminary;
export namespace lt::math {
[[nodiscard]] constexpr auto to_radians(f32 degrees) -> f32
{
return degrees * 0.01745329251994329576923690768489f;
}
[[nodiscard]] constexpr auto to_radians(f64 degrees) -> f64
{
return degrees * 0.01745329251994329576923690768489;
}
[[nodiscard]] constexpr auto to_degrees(f32 radians) -> f32
{
return radians * 57.295779513082320876798154814105f;
}
[[nodiscard]] constexpr auto to_degrees(f64 radians) -> f64
{
return radians * 57.295779513082320876798154814105;
}
} // namespace lt::math

View file

@ -1,43 +0,0 @@
import test;
import math.trig;
Suite conversions = "trig_conversions"_suite = [] {
using ::lt::math::to_degrees;
using ::lt::math::to_radians;
Case { "to_radians <f32>" } = [] {
expect_eq(to_radians(f32 { 0.0f }), f32 { 0.0f });
expect_eq(to_radians(f32 { 90.0f }), f32 { 1.5707963267948966f });
expect_eq(to_radians(f32 { 180.0f }), f32 { 3.1415926535897932f });
expect_eq(to_radians(f32 { 360.0f }), f32 { 6.2831853071795864f });
};
Case { "to_radians <f64>" } = [] {
expect_eq(to_radians(f64 { 0.0 }), f64 { 0.0 });
expect_eq(to_radians(f64 { 90.0 }), f64 { 1.5707963267948966 });
expect_eq(to_radians(f64 { 180.0 }), f64 { 3.1415926535897932 });
expect_eq(to_radians(f64 { 360.0 }), f64 { 6.2831853071795864 });
};
Case { "to_degrees <f32>" } = [] {
expect_eq(to_degrees(f32 { 0.0f }), f32 { 0.0f });
expect_eq(to_degrees(f32 { 1.5707963267948966f }), f32 { 90.0f });
expect_eq(to_degrees(f32 { 3.1415926535897932f }), f32 { 180.0f });
expect_eq(to_degrees(f32 { 6.2831853071795864f }), f32 { 360.0f });
};
Case { "to_degrees <f64>" } = [] {
expect_eq(to_degrees(f64 { 0.0 }), f64 { 0.0 });
expect_eq(to_degrees(f64 { 1.5707963267948966 }), f64 { 90.0 });
expect_eq(to_degrees(f64 { 3.1415926535897932 }), f64 { 180.0 });
expect_eq(to_degrees(f64 { 6.2831853071795864 }), f64 { 360.0 });
};
Case { "to_degrees -> to_radians -> to_degrees <f32>" } = [] {
expect_eq(to_degrees(to_radians(f32 { 45.0f })), f32 { 45.0f });
};
Case { "to_degrees -> to_radians -> to_degrees <f64>" } = [] {
expect_eq(to_degrees(to_radians(f64 { 45.0 })), f64 { 45.0 });
};
};

View file

@ -1,115 +0,0 @@
export module math.vec2;
import preliminary;
export namespace lt::math {
template<typename T = f32>
requires(std::is_arithmetic_v<T>)
struct vec2_impl
{
using Underlying_T = T;
static constexpr auto num_elements = 2u;
constexpr vec2_impl(): x(), y()
{
}
constexpr explicit vec2_impl(T scalar): x(scalar), y(scalar)
{
}
constexpr vec2_impl(T x, T y): x(x), y(y)
{
}
[[nodiscard]] auto operator==(const vec2_impl<T> &other) const -> bool
{
return x == other.x && y == other.y;
}
[[nodiscard]] auto operator!=(const vec2_impl<T> &other) const -> bool
{
return !(*this == other);
}
[[nodiscard]] constexpr auto operator+(const vec2_impl<T> &other) const -> vec2_impl
{
return {
x + other.x,
y + other.y,
};
}
[[nodiscard]] constexpr auto operator-(const vec2_impl<T> &other) const -> vec2_impl
{
return {
x - other.x,
y - other.y,
};
}
[[nodiscard]] constexpr auto operator*(const vec2_impl<T> &other) const -> vec2_impl
{
return {
x * other.x,
y * other.y,
};
}
[[nodiscard]] constexpr auto operator/(const vec2_impl<T> &other) const -> vec2_impl
{
return {
x / other.x,
y / other.y,
};
}
[[nodiscard]] constexpr auto operator[](u8 idx) -> T &
{
debug_check(idx < num_elements, "vec2 out of bound access: {}", idx);
return ((T *)this)[idx];
}
[[nodiscard]] constexpr auto operator[](u8 idx) const -> const T &
{
debug_check(idx < num_elements, "vec2 out of bound access: {}", idx);
return ((T *)this)[idx];
}
T x;
T y;
};
using vec2 = vec2_impl<f32>;
using vec2_f32 = vec2;
using vec2_f64 = vec2_impl<f64>;
using vec2_i8 = vec2_impl<i8>;
using vec2_i16 = vec2_impl<i16>;
using vec2_i32 = vec2_impl<i32>;
using vec2_i64 = vec2_impl<i64>;
using vec2_u8 = vec2_impl<u8>;
using vec2_u16 = vec2_impl<u16>;
using vec2_u32 = vec2_impl<u32>;
using vec2_u64 = vec2_impl<u64>;
} // namespace lt::math
export template<typename T>
struct std::formatter<lt::math::vec2_impl<T>>
{
constexpr auto parse(std::format_parse_context &context)
{
return context.begin();
}
auto format(const lt::math::vec2_impl<T> &val, std::format_context &context) const
{
return std::format_to(context.out(), "{}, {}", val.x, val.y);
}
};

View file

@ -1,130 +0,0 @@
import test;
import math.vec2;
using vec2 = ::lt::math::vec2;
using ivec2 = ::lt::math::vec2_i32;
Suite static_tests = "vec3_static_checks"_suite = [] {
constexpr auto num_elements = lt::math::vec2::num_elements;
static_assert(num_elements == 2u);
static_assert(std::is_same_v<lt::math::vec2, lt::math::vec2_f32>);
static_assert(sizeof(lt::math::vec2_f32) == sizeof(f32) * num_elements);
static_assert(sizeof(lt::math::vec2_f64) == sizeof(f64) * num_elements);
static_assert(sizeof(lt::math::vec2_i8) == sizeof(i8) * num_elements);
static_assert(sizeof(lt::math::vec2_i16) == sizeof(i16) * num_elements);
static_assert(sizeof(lt::math::vec2_i32) == sizeof(i32) * num_elements);
static_assert(sizeof(lt::math::vec2_i64) == sizeof(i64) * num_elements);
static_assert(sizeof(lt::math::vec2_u8) == sizeof(u8) * num_elements);
static_assert(sizeof(lt::math::vec2_u16) == sizeof(u16) * num_elements);
static_assert(sizeof(lt::math::vec2_u32) == sizeof(u32) * num_elements);
static_assert(sizeof(lt::math::vec2_u64) == sizeof(u64) * num_elements);
};
Suite raii = "vec2_raii"_suite = [] {
Case { "happy paths" } = [] {
ignore = vec2 {};
ignore = vec2 { 2.0 };
ignore = vec2 { 2.0, 4.0 };
};
Case { "unhappy paths" } = [] {
};
Case { "many" } = [] {
for (auto idx : std::views::iota(0, 1'000'000))
{
ignore = idx;
ignore = vec2 {};
ignore = vec2 { 2.0 };
ignore = vec2 { 2.0, 4.0 };
}
};
Case { "post default construct has correct state" } = [] {
const auto vec = vec2 {};
expect_eq(vec.x, 0.0);
expect_eq(vec.y, 0.0);
};
Case { "post scalar construct has correct state" } = [] {
const auto vec = vec2 { 2.0 };
expect_eq(vec.x, 2.0);
expect_eq(vec.y, 2.0);
};
Case { "post construct with x,y has correct state" } = [] {
const auto vec = vec2 { 2.0, 3.0 };
expect_eq(vec.x, 2.0);
expect_eq(vec.y, 3.0);
};
};
Suite arithmetic_operators = "vec2_operators"_suite = [] {
Case { "operator ==" } = [] {
const auto lhs = vec2 { 1.0, 2.0 };
expect_false(lhs == vec2 { {}, 2.0 });
expect_false(lhs == vec2 { 1.0, {} });
expect_true(lhs == vec2 { 1.0, 2.0 });
};
Case { "operator !=" } = [] {
const auto lhs = vec2 { 1.0, 2.0 };
expect_true(lhs != vec2 { {}, 2.0 });
expect_true(lhs != vec2 { 1.0, {} });
expect_false(lhs != vec2 { 1.0, 2.0 });
};
Case { "operator +" } = [] {
const auto lhs = vec2 { 2.0, 3.0 };
const auto rhs = vec2 { 4.0, 5.0 };
expect_eq(lhs + rhs, vec2 { 6.0, 8.0 });
};
Case { "operator -" } = [] {
const auto lhs = vec2 { 2.0, 3.0 };
const auto rhs = vec2 { 4.0, 6.0 };
expect_eq(lhs - rhs, vec2 { -2.0, -3.0 });
};
Case { "operator *" } = [] {
const auto lhs = vec2 { 2.0, 3.0 };
const auto rhs = vec2 { 10.0, 20.0 };
expect_eq(lhs * rhs, vec2 { 20.0, 60.0 });
};
Case { "operator /" } = [] {
const auto lhs = vec2 { 10.0, 20.0 };
const auto rhs = vec2 { 2.0, 20.0 };
expect_eq(lhs / rhs, vec2 { 5.0, 1.0 });
};
Case { "operator []" } = [] {
auto vec = vec2 { 0.0, 1.0 };
expect_eq(vec[0], 0.0);
expect_eq(vec[1], 1.0);
};
Case { "operator [] const" } = [] {
const auto vec = vec2 { 0.0, 1.0 };
expect_eq(vec[0], 0.0);
expect_eq(vec[1], 1.0);
};
};
Suite utilities = "vec2_utilities"_suite = [] {
Case { "std::format float" } = [] {
auto str = std::format("{}", vec2 { 10.0000f, 30.0005f });
expect_eq(str, "10, 30.0005");
};
Case { "std::format int" } = [] {
auto str = std::format("{}", ivec2 { 10, 30 });
expect_eq(str, "10, 30");
};
};

View file

@ -1,136 +0,0 @@
export module math.vec3;
import preliminary;
import math.vec2;
export namespace lt::math {
template<typename T = f32>
requires(std::is_arithmetic_v<T>)
struct vec3_impl
{
using Underlying_T = T;
static constexpr auto num_elements = 3u;
constexpr vec3_impl(): x(), y(), z()
{
}
constexpr explicit vec3_impl(T scalar): x(scalar), y(scalar), z(scalar)
{
}
constexpr vec3_impl(T x, T y, T z): x(x), y(y), z(z)
{
}
constexpr vec3_impl(vec2_impl<T> xy, T z): x(xy.x), y(xy.y), z(z)
{
}
constexpr vec3_impl(T x, vec2_impl<T> yz): x(x), y(yz.x), z(yz.y)
{
}
[[nodiscard]] auto operator==(const vec3_impl<T> &other) const -> bool
{
return x == other.x && y == other.y && z == other.z;
}
[[nodiscard]] auto operator!=(const vec3_impl<T> &other) const -> bool
{
return !(*this == other);
}
[[nodiscard]] constexpr auto operator+(const vec3_impl<T> &other) const -> vec3_impl
{
return {
x + other.x,
y + other.y,
z + other.z,
};
}
[[nodiscard]] constexpr auto operator-(const vec3_impl<T> &other) const -> vec3_impl
{
return {
x - other.x,
y - other.y,
z - other.z,
};
}
[[nodiscard]] constexpr auto operator*(const vec3_impl<T> &other) const -> vec3_impl
{
return {
x * other.x,
y * other.y,
z * other.z,
};
}
[[nodiscard]] constexpr auto operator/(const vec3_impl<T> &other) const -> vec3_impl
{
return {
x / other.x,
y / other.y,
z / other.z,
};
}
[[nodiscard]] constexpr auto operator[](u8 idx) -> T &
{
debug_check(idx < num_elements, "vec3 out of bound access: {}", idx);
return ((T *)this)[idx];
}
[[nodiscard]] constexpr auto operator[](u8 idx) const -> const T &
{
debug_check(idx < num_elements, "vec3 out of bound access: {}", idx);
return ((T *)this)[idx];
}
friend auto operator<<(std::ostream &stream, vec3_impl<T> value) -> std::ostream &
{
stream << value.x << ", " << value.y << ", " << value.z;
return stream;
}
T x;
T y;
T z;
};
using vec3 = vec3_impl<f32>;
using vec3_f32 = vec3;
using vec3_f64 = vec3_impl<f64>;
using vec3_i8 = vec3_impl<i8>;
using vec3_i16 = vec3_impl<i16>;
using vec3_i32 = vec3_impl<i32>;
using vec3_i64 = vec3_impl<i64>;
using vec3_u8 = vec3_impl<u8>;
using vec3_u16 = vec3_impl<u16>;
using vec3_u32 = vec3_impl<u32>;
using vec3_u64 = vec3_impl<u64>;
} // namespace lt::math
export template<typename T>
struct std::formatter<lt::math::vec3_impl<T>>
{
constexpr auto parse(std::format_parse_context &context)
{
return context.begin();
}
auto format(const lt::math::vec3_impl<T> &val, std::format_context &context) const
{
return std::format_to(context.out(), "{}, {}, {}", val.x, val.y, val.z);
}
};

View file

@ -1,157 +0,0 @@
import test;
import math.vec2;
import math.vec3;
using vec2 = ::lt::math::vec2;
using vec3 = ::lt::math::vec3;
using ivec3 = ::lt::math::vec3_i32;
Suite static_tests = "vec3_static_checks"_suite = [] {
constexpr auto num_elements = lt::math::vec3::num_elements;
static_assert(num_elements == 3u);
static_assert(std::is_same_v<lt::math::vec3, lt::math::vec3_f32>);
static_assert(sizeof(lt::math::vec3_f32) == sizeof(f32) * num_elements);
static_assert(sizeof(lt::math::vec3_f64) == sizeof(f64) * num_elements);
static_assert(sizeof(lt::math::vec3_i8) == sizeof(i8) * num_elements);
static_assert(sizeof(lt::math::vec3_i16) == sizeof(i16) * num_elements);
static_assert(sizeof(lt::math::vec3_i32) == sizeof(i32) * num_elements);
static_assert(sizeof(lt::math::vec3_i64) == sizeof(i64) * num_elements);
static_assert(sizeof(lt::math::vec3_u8) == sizeof(u8) * num_elements);
static_assert(sizeof(lt::math::vec3_u16) == sizeof(u16) * num_elements);
static_assert(sizeof(lt::math::vec3_u32) == sizeof(u32) * num_elements);
static_assert(sizeof(lt::math::vec3_u64) == sizeof(u64) * num_elements);
};
Suite raii = "vec3_raii"_suite = [] {
Case { "happy paths" } = [] {
ignore = vec3 {};
ignore = vec3 { 2.0 };
ignore = vec3 { 2.0, 4.0, 6.0 };
ignore = vec3 { vec2 { 2.0, 4.0 }, 6.0 };
ignore = vec3 { 2.0, vec2 { 4.0, 6.0 } };
};
Case { "unhappy paths" } = [] {
};
Case { "many" } = [] {
for (auto idx : std::views::iota(0, 1'000'000))
{
ignore = idx;
ignore = vec3 {};
ignore = vec3 { 2.0 };
ignore = vec3 { 2.0, 4.0, 6.0 };
ignore = vec3 { vec2 { 2.0, 4.0 }, 6.0 };
ignore = vec3 { 2.0, vec2 { 4.0, 6.0 } };
}
};
Case { "post default construct has correct state" } = [] {
const auto vec = vec3 {};
expect_eq(vec.x, 0.0);
expect_eq(vec.y, 0.0);
expect_eq(vec.z, 0.0);
};
Case { "post scalar construct has correct state" } = [] {
const auto vec = vec3 { 2.0 };
expect_eq(vec.x, 2.0);
expect_eq(vec.y, 2.0);
expect_eq(vec.z, 2.0);
};
Case { "post construct with x,y,z has correct state" } = [] {
const auto vec = vec3 { 1.0, 2.0, 3.0 };
expect_eq(vec.x, 1.0);
expect_eq(vec.y, 2.0);
expect_eq(vec.y, 3.0);
};
Case { "post construct with xy,z has correct state" } = [] {
const auto vec = vec3 { vec2 { 1.0, 2.0 }, 3.0 };
expect_eq(vec.x, 1.0);
expect_eq(vec.y, 2.0);
expect_eq(vec.z, 3.0);
};
Case { "post construct with x,yz has correct state" } = [] {
const auto vec = vec3 { 1.0, vec2 { 2.0, 3.0 } };
expect_eq(vec.x, 1.0);
expect_eq(vec.y, 2.0);
expect_eq(vec.z, 3.0);
};
};
Suite arithmetic_operators = "vec3_operators"_suite = [] {
Case { "operator ==" } = [] {
const auto lhs = vec3 { 1.0, 2.0, 3.0 };
expect_false(lhs == vec3 { {}, 2.0, 3.0 });
expect_false(lhs == vec3 { 1.0, {}, 3.0 });
expect_false(lhs == vec3 { 1.0, 2.0, {} });
expect_true(lhs == vec3 { 1.0, 2.0, 3.0 });
};
Case { "operator !=" } = [] {
const auto lhs = vec3 { 1.0, 2.0, 3.0 };
expect_true(lhs != vec3 { {}, 2.0, 3.0 });
expect_true(lhs != vec3 { 1.0, {}, 3.0 });
expect_true(lhs != vec3 { 1.0, 2.0, {} });
expect_false(lhs != vec3 { 1.0, 2.0, 3.0 });
};
Case { "operator +" } = [] {
const auto lhs = vec3 { 1.0, 2.0, 3.0 };
const auto rhs = vec3 { 4.0, 5.0, 6.0 };
expect_eq(lhs + rhs, vec3 { 5.0, 7.0, 9.0 });
};
Case { "operator -" } = [] {
const auto lhs = vec3 { 1.0, 2.0, 3.0 };
const auto rhs = vec3 { 4.0, 5.0, 7.0 };
expect_eq(lhs - rhs, vec3 { -2.0, -3.0, -4.0 });
};
Case { "operator *" } = [] {
const auto lhs = vec3 { 1.0, 2.0, 3.0 };
const auto rhs = vec3 { 4.0, 5.0, 6.0 };
expect_eq(lhs * rhs, vec3 { 4.0, 10.0, 18.0 });
};
Case { "operator /" } = [] {
const auto lhs = vec3 { 4.0, 10.0, 30.0 };
const auto rhs = vec3 { 1.0, 2.0, 5.0 };
expect_eq(lhs / rhs, vec3 { 4.0, 5.0, 6.0 });
};
Case { "operator []" } = [] {
auto vec = vec3 { 0.0, 1.0, 2.0 };
expect_eq(vec[0], 0.0);
expect_eq(vec[1], 1.0);
expect_eq(vec[2], 2.0);
};
Case { "operator [] const" } = [] {
const auto vec = vec3 { 0.0, 1.0, 2.0 };
expect_eq(vec[0], 0.0);
expect_eq(vec[1], 1.0);
expect_eq(vec[2], 2.0);
};
};
Suite utilities = "vec3_utilities"_suite = [] {
Case { "std::format float" } = [] {
auto str = std::format("{}", vec3 { 10.0000f, 30.0005f, 40.00005f });
expect_eq(str, "10, 30.0005, 40.00005");
};
Case { "std::format int" } = [] {
auto str = std::format("{}", ivec3 { 10, 30, 3'000'000 });
expect_eq(str, "10, 30, 3000000");
};
};

View file

@ -1,159 +0,0 @@
export module math.vec4;
import preliminary;
import math.vec2;
import math.vec3;
export namespace lt::math {
template<typename T = f32>
requires(std::is_arithmetic_v<T>)
struct vec4_impl
{
using Underlying_T = T;
static constexpr auto num_elements = 4u;
constexpr vec4_impl(): x(), y(), z(), w()
{
}
constexpr explicit vec4_impl(T scalar): x(scalar), y(scalar), z(scalar), w(scalar)
{
}
constexpr vec4_impl(T x, T y, T z, T w): x(x), y(y), z(z), w(w)
{
}
constexpr vec4_impl(vec2_impl<T> xy, T z, T w): x(xy.x), y(xy.y), z(z), w(w)
{
}
constexpr vec4_impl(T x, vec2_impl<T> yz, T w): x(x), y(yz.x), z(yz.y), w(w)
{
}
constexpr vec4_impl(T x, T y, vec2_impl<T> zw): x(x), y(y), z(zw.x), w(zw.y)
{
}
constexpr vec4_impl(vec2_impl<T> xy, vec2_impl<T> zw): x(xy.x), y(xy.y), z(zw.x), w(zw.y)
{
}
constexpr vec4_impl(vec3_impl<T> xyz, T w): x(xyz.x), y(xyz.y), z(xyz.z), w(w)
{
}
constexpr vec4_impl(T x, vec3_impl<T> yzw): x(x), y(yzw.x), z(yzw.y), w(yzw.z)
{
}
[[nodiscard]] auto operator==(const vec4_impl<T> &other) const -> bool
{
return x == other.x && y == other.y && z == other.z && w == other.w;
}
[[nodiscard]] auto operator!=(const vec4_impl<T> &other) const -> bool
{
return !(*this == other);
}
[[nodiscard]] constexpr auto operator+(const vec4_impl<T> &other) const -> vec4_impl
{
return {
x + other.x,
y + other.y,
z + other.z,
w + other.w,
};
}
[[nodiscard]] constexpr auto operator-(const vec4_impl<T> &other) const -> vec4_impl
{
return {
x - other.x,
y - other.y,
z - other.z,
w - other.w,
};
}
[[nodiscard]] constexpr auto operator*(const vec4_impl<T> &other) const -> vec4_impl
{
return {
x * other.x,
y * other.y,
z * other.z,
w * other.w,
};
}
[[nodiscard]] constexpr auto operator/(const vec4_impl<T> &other) const -> vec4_impl
{
return {
x / other.x,
y / other.y,
z / other.z,
w / other.w,
};
}
[[nodiscard]] constexpr auto operator[](u8 idx) -> T &
{
debug_check(idx < num_elements, "vec4 out of bound access: {}", idx);
return ((T *)this)[idx];
}
[[nodiscard]] constexpr auto operator[](u8 idx) const -> const T &
{
debug_check(idx < num_elements, "vec4 out of bound access: {}", idx);
return ((T *)this)[idx];
}
friend auto operator<<(std::ostream &stream, vec4_impl<T> value) -> std::ostream &
{
stream << value.x << ", " << value.y << ", " << value.z << ", " << value.w;
return stream;
}
T x;
T y;
T z;
T w;
};
using vec4 = vec4_impl<f32>;
using vec4_f32 = vec4;
using vec4_f64 = vec4_impl<f64>;
using vec4_i8 = vec4_impl<i8>;
using vec4_i16 = vec4_impl<i16>;
using vec4_i32 = vec4_impl<i32>;
using vec4_i64 = vec4_impl<i64>;
using vec4_u8 = vec4_impl<u8>;
using vec4_u16 = vec4_impl<u16>;
using vec4_u32 = vec4_impl<u32>;
using vec4_u64 = vec4_impl<u64>;
} // namespace lt::math
export template<typename T>
struct std::formatter<lt::math::vec4_impl<T>>
{
constexpr auto parse(std::format_parse_context &context)
{
return context.begin();
}
auto format(const lt::math::vec4_impl<T> &val, std::format_context &context) const
{
return std::format_to(context.out(), "{}, {}, {}, {}", val.x, val.y, val.z, val.w);
}
};

View file

@ -1,215 +0,0 @@
import test;
import math.vec2;
import math.vec3;
import math.vec4;
import logger;
using vec2 = ::lt::math::vec2;
using vec3 = ::lt::math::vec3;
using vec4 = ::lt::math::vec4;
using ivec4 = ::lt::math::vec4_i32;
Suite static_tests = "vec4_static_checks"_suite = [] {
constexpr auto num_elements = lt::math::vec4::num_elements;
static_assert(num_elements == 4u);
static_assert(std::is_same_v<lt::math::vec4, lt::math::vec4_f32>);
static_assert(sizeof(lt::math::vec4_f32) == sizeof(f32) * num_elements);
static_assert(sizeof(lt::math::vec4_f64) == sizeof(f64) * num_elements);
static_assert(sizeof(lt::math::vec4_i8) == sizeof(i8) * num_elements);
static_assert(sizeof(lt::math::vec4_i16) == sizeof(i16) * num_elements);
static_assert(sizeof(lt::math::vec4_i32) == sizeof(i32) * num_elements);
static_assert(sizeof(lt::math::vec4_i64) == sizeof(i64) * num_elements);
static_assert(sizeof(lt::math::vec4_u8) == sizeof(u8) * num_elements);
static_assert(sizeof(lt::math::vec4_u16) == sizeof(u16) * num_elements);
static_assert(sizeof(lt::math::vec4_u32) == sizeof(u32) * num_elements);
static_assert(sizeof(lt::math::vec4_u64) == sizeof(u64) * num_elements);
};
Suite raii = "vec4_raii"_suite = [] {
Case { "happy paths" } = [] {
ignore = vec4 {};
ignore = vec4 { 2.0 };
ignore = vec4 { 2.0, 4.0, 6.0, 8.0 };
ignore = vec4 { vec2 { 2.0, 4.0 }, 6.0, 8.0 };
ignore = vec4 { 2.0, 4.0, vec2 { 6.0, 8.0 } };
ignore = vec4 { vec2 { 2.0, 4.0 }, vec2 { 6.0, 8.0 } };
ignore = vec4 { vec3 { 2.0, 4.0, 6.0 }, 8.0 };
ignore = vec4 { 2.0, vec3 { 4.0, 6.0, 8.0 } };
};
Case { "unhappy paths" } = [] {
};
Case { "many" } = [] {
for (auto idx : std::views::iota(0, 1'000'000))
{
ignore = idx;
ignore = vec4 {};
ignore = vec4 { 2.0 };
ignore = vec4 { 2.0, 4.0, 6.0, 8.0 };
ignore = vec4 { vec2 { 2.0, 4.0 }, 6.0, 8.0 };
ignore = vec4 { 2.0, 4.0, vec2 { 6.0, 8.0 } };
ignore = vec4 { vec2 { 2.0, 4.0 }, vec2 { 6.0, 8.0 } };
ignore = vec4 { vec3 { 2.0, 4.0, 6.0 }, 8.0 };
ignore = vec4 { 2.0, vec3 { 4.0, 6.0, 8.0 } };
}
};
Case { "post default construct has correct state" } = [] {
const auto vec = vec4 {};
expect_eq(vec.x, 0.0);
expect_eq(vec.y, 0.0);
expect_eq(vec.z, 0.0);
expect_eq(vec.w, 0.0);
};
Case { "post scalar construct has correct state" } = [] {
const auto vec = vec4 { 2.0 };
expect_eq(vec.x, 2.0);
expect_eq(vec.y, 2.0);
expect_eq(vec.z, 2.0);
expect_eq(vec.w, 2.0);
};
Case { "post construct with x,y,z,w has correct state" } = [] {
const auto vec = vec4 { 1.0, 2.0, 3.0, 4.0 };
expect_eq(vec.x, 1.0);
expect_eq(vec.y, 2.0);
expect_eq(vec.y, 3.0);
expect_eq(vec.z, 4.0);
};
Case { "post construct with xy,z,w has correct state" } = [] {
const auto vec = vec4 { vec2 { 1.0, 2.0 }, 3.0, 4.0 };
expect_eq(vec.x, 1.0);
expect_eq(vec.y, 2.0);
expect_eq(vec.z, 3.0);
expect_eq(vec.w, 4.0);
};
Case { "post construct with x,y,zw has correct state" } = [] {
const auto vec = vec4 { 1.0, 2.0, vec2 { 3.0, 4.0 } };
expect_eq(vec.x, 1.0);
expect_eq(vec.y, 2.0);
expect_eq(vec.z, 3.0);
expect_eq(vec.w, 4.0);
};
Case { "post construct with x,yz,w has correct state" } = [] {
const auto vec = vec4 { 1.0, vec2 { 2.0, 3.0 }, 4.0 };
expect_eq(vec.x, 1.0);
expect_eq(vec.y, 2.0);
expect_eq(vec.z, 3.0);
expect_eq(vec.w, 4.0);
};
Case { "post construct with x,y,zw has correct state" } = [] {
const auto vec = vec4 { 1.0, 2.0, vec2 { 3.0, 4.0 } };
expect_eq(vec.x, 1.0);
expect_eq(vec.y, 2.0);
expect_eq(vec.z, 3.0);
expect_eq(vec.w, 4.0);
};
Case { "post construct with xy,zw has correct state" } = [] {
const auto vec = vec4 { vec2 { 1.0, 2.0 }, vec2 { 3.0, 4.0 } };
expect_eq(vec.x, 1.0);
expect_eq(vec.y, 2.0);
expect_eq(vec.z, 3.0);
expect_eq(vec.w, 4.0);
};
Case { "post construct with xyz,w has correct state" } = [] {
const auto vec = vec4 { vec3 { 1.0, 2.0, 3.0 }, 4.0 };
expect_eq(vec.x, 1.0);
expect_eq(vec.y, 2.0);
expect_eq(vec.z, 3.0);
expect_eq(vec.w, 4.0);
};
Case { "post construct with x,yzw has correct state" } = [] {
const auto vec = vec4 { 1.0, vec3 { 2.0, 3.0, 4.0 } };
expect_eq(vec.x, 1.0);
expect_eq(vec.y, 2.0);
expect_eq(vec.z, 3.0);
expect_eq(vec.w, 4.0);
};
};
Suite arithmetic_operators = "vec4_operators"_suite = [] {
Case { "operator ==" } = [] {
const auto lhs = vec4 { 1.0, 2.0, 3.0, 4.0 };
expect_false(lhs == vec4 { {}, 2.0, 3.0, 4.0 });
expect_false(lhs == vec4 { 1.0, {}, 3.0, 4.0 });
expect_false(lhs == vec4 { 1.0, 2.0, {}, 4.0 });
expect_false(lhs == vec4 { 1.0, 2.0, 3.0, {} });
expect_true(lhs == vec4 { 1.0, 2.0, 3.0, 4.0 });
};
Case { "operator !=" } = [] {
const auto lhs = vec4 { 1.0, 2.0, 3.0, 4.0 };
expect_true(lhs != vec4 { {}, 2.0, 3.0, 4.0 });
expect_true(lhs != vec4 { 1.0, {}, 3.0, 4.0 });
expect_true(lhs != vec4 { 1.0, 2.0, {}, 4.0 });
expect_true(lhs != vec4 { 1.0, 2.0, 3.0, {} });
expect_false(lhs != vec4 { 1.0, 2.0, 3.0, 4.0 });
};
Case { "operator +" } = [] {
const auto lhs = vec4 { 1.0, 2.0, 3.0, 4.0 };
const auto rhs = vec4 { 5.0, 6.0, 7.0, 8.0 };
expect_eq(lhs + rhs, vec4 { 6.0, 8.0, 10.0, 12.0 });
};
Case { "operator -" } = [] {
const auto lhs = vec4 { 1.0, 2.0, 3.0, 4.0 };
const auto rhs = vec4 { 5.0, 10.0, 15.0, 20.0 };
expect_eq(lhs - rhs, vec4 { -4.0, -8.0, -12.0, -16.0 });
};
Case { "operator *" } = [] {
const auto lhs = vec4 { 1.0, 2.0, 3.0, 4.0 };
const auto rhs = vec4 { 5.0, 6.0, 7.0, 8.0 };
expect_eq(lhs * rhs, vec4 { 5.0, 12.0, 21.0, 32.0 });
};
Case { "operator /" } = [] {
const auto lhs = vec4 { 5.0, 6.0, 30.0, 8.0 };
const auto rhs = vec4 { 1.0, 2.0, 3.0, 4.0 };
expect_eq(lhs / rhs, vec4 { 5.0, 3.0, 10.0, 2.0 });
};
Case { "operator []" } = [] {
auto vec = vec4 { 0.0, 1.0, 2.0, 3.0 };
expect_eq(vec[0], 0.0);
expect_eq(vec[1], 1.0);
expect_eq(vec[2], 2.0);
expect_eq(vec[3], 3.0);
};
Case { "operator [] const" } = [] {
const auto vec = vec4 { 0.0, 1.0, 2.0, 3.0 };
expect_eq(vec[0], 0.0);
expect_eq(vec[1], 1.0);
expect_eq(vec[2], 2.0);
expect_eq(vec[3], 3.0);
};
};
Suite utilities = "vec4_utilities"_suite = [] {
Case { "std::format float" } = [] {
auto str = std::format("{}", vec4 { 10.0000f, 30.0005f, 40.00005f, 0.0 });
expect_eq(str, "10, 30.0005, 40.00005, 0");
};
Case { "std::format int" } = [] {
auto str = std::format("{}", ivec4 { 10, 30, 3'000'000, 13 });
expect_eq(str, "10, 30, 3000000, 13");
};
};

View file

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

View file

@ -1,17 +1,13 @@
export module memory.null_on_move; #pragma once
import logger;
import preliminary;
namespace lt::memory { namespace lt::memory {
/** Holds an `Underlying_T`, assigns it to `null_value` when this object is moved. /** 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 * @note For avoiding the need to explicitly implement the move constructor for objects that hold
* non-raii-handles (eg. Vulkan, Wayland). * Vulkan objects. But may server other purposes, hence why I kept the implementation generic.
*/ */
export template<typename Underlying_T, Underlying_T null_value = nullptr> template<typename Underlying_T, Underlying_T null_value = nullptr>
class NullOnMove class NullOnMove
{ {
public: public:
@ -81,12 +77,12 @@ public:
return m_value; return m_value;
} }
[[nodiscard]] auto get() -> Underlying_T & operator uint64_t() const
{ {
return m_value; return (uint64_t)m_value;
} }
[[nodiscard]] auto get() const -> const Underlying_T & [[nodiscard]] auto get() -> Underlying_T
{ {
return m_value; return m_value;
} }

View file

@ -1,6 +1,7 @@
export module memory.reference; #pragma once
import preliminary; #include <memory/reference.hpp>
#include <memory>
namespace lt::memory { namespace lt::memory {
@ -9,21 +10,21 @@ namespace lt::memory {
* @note Currently just an alias, might turn into an implementation later. * @note Currently just an alias, might turn into an implementation later.
* @ref https://en.cppreference.com/w/cpp/memory/shared_ptr.html * @ref https://en.cppreference.com/w/cpp/memory/shared_ptr.html
*/ */
export template<typename T> template<typename t>
using Ref = std::shared_ptr<T>; using Ref = std::shared_ptr<t>;
/** Allocates memory for an `Underlying_T` and directly constructs it there. /** Allocates memory for an `Underlying_T` and directly constructs it there.
* *
* @return A Ref<Underlying_T> to the constructed object. * @return A Ref<Underlying_T> to the constructed object.
*/ */
export template<typename Underlying_T, typename... Args> template<typename Underlying_T, typename... Args>
constexpr Ref<Underlying_T> create_ref(Args &&...args) constexpr Ref<Underlying_T> create_ref(Args &&...args)
{ {
return std::make_shared<Underlying_T>(std::forward<Args>(args)...); return std::make_shared<Underlying_T>(std::forward<Args>(args)...);
} }
/** Converts c-style pointer of type `Underlying_T` to a `Ref<Underlying_T>`. */ /** Converts c-style pointer of type `Underlying_T` to a `Ref<Underlying_T>`. */
export template<typename Underlying_T> template<typename Underlying_T>
constexpr Ref<Underlying_T> make_ref(Underlying_T *raw_pointer) constexpr Ref<Underlying_T> make_ref(Underlying_T *raw_pointer)
{ {
return Ref<Underlying_T>(raw_pointer); return Ref<Underlying_T>(raw_pointer);

View file

@ -1,29 +1,30 @@
export module memory.scope; #pragma once
import preliminary; #include <memory/scope.hpp>
#include <memory>
namespace lt::memory { namespace lt::memory {
/** @brief Wrapper around std::unique_ptr. /** Wrapper around std::unique_ptr.
* *
* @note Currently just an alias, might turn into an implementation later. * @note Currently just an alias, might turn into an implementation later.
* @ref https://en.cppreference.com/w/cpp/memory/unique_ptr.html * @ref https://en.cppreference.com/w/cpp/memory/unique_ptr.html
*/ */
export template<typename t> template<typename t>
using Scope = std::unique_ptr<t>; using Scope = std::unique_ptr<t>;
/** Allocates memory for an `Underlying_T` and directly constructs it there. /** Allocates memory for an `Underlying_T` and directly constructs it there.
* *
* @return A Scope<Underlying_T> to the constructed object. * @return A Scope<Underlying_T> to the constructed object.
*/ */
export template<typename Underlying_T, typename... Args> template<typename Underlying_T, typename... Args>
constexpr Scope<Underlying_T> create_scope(Args &&...args) constexpr Scope<Underlying_T> create_scope(Args &&...args)
{ {
return std::make_unique<Underlying_T>(std::forward<Args>(args)...); return std::make_unique<Underlying_T>(std::forward<Args>(args)...);
} }
/** Converts c-style pointer of type `Underlying_T` to a `Scope<Underlying_T>`. */ /** Converts c-style pointer of type `Underlying_T` to a `Scope<Underlying_T>`. */
export template<typename Underlying_T> template<typename Underlying_T>
constexpr Scope<Underlying_T> make_scope(Underlying_T *raw_pointer) constexpr Scope<Underlying_T> make_scope(Underlying_T *raw_pointer)
{ {
return Scope<Underlying_T>(raw_pointer); return Scope<Underlying_T>(raw_pointer);

View file

@ -0,0 +1,9 @@
add_library_module(libmirror)
target_link_libraries(libmirror INTERFACE app time input surface renderer)
add_test_module(
libmirror layers/editor_layer.test.cpp panels/asset_browser.test.cpp
panels/properties.test.cpp panels/scene_hierarchy.test.cpp)
add_executable_module(mirror entrypoint/mirror.cpp)
target_link_libraries(mirror PRIVATE libmirror input)

View file

@ -1,33 +0,0 @@
import app;
import app.system;
import std;
import logger;
import memory.scope;
import mirror.system;
import renderer.factory;
/** The ultimate entrypoint. */
auto main(i32 argc, char *argv[]) -> i32
{
try
{
ignore = argc;
ignore = argv;
auto application = lt::memory::create_scope<lt::Mirror>();
if (!application)
{
throw std::runtime_error { "Failed to create application\n" };
}
application->game_loop();
return 0;
}
catch (const std::exception &exp)
{
lt::log::critical("Terminating due to uncaught exception:");
lt::log::critical("\texception.what(): {}", exp.what());
return 1;
}
}

Some files were not shown because too many files have changed in this diff Show more