Compare commits

..

7 commits

Author SHA1 Message Date
f9b1ac8c84
wip
Some checks reported errors
continuous-integration/drone/pr Build was killed
2025-10-21 13:19:20 +03:30
368ea7e1d0
refactor: remove msan specific code
Some checks reported errors
continuous-integration/drone/pr Build was killed
2025-10-21 13:14:28 +03:30
8c0dbdd52a
wip
Some checks failed
continuous-integration/drone/pr Build is failing
2025-10-16 15:06:50 +03:30
5a922cac71
wip
Some checks failed
continuous-integration/drone/pr Build is failing
2025-10-16 14:55:49 +03:30
2f7c729f88
wip
Some checks failed
continuous-integration/drone/pr Build is failing
2025-10-16 14:52:32 +03:30
bcb1acbf75
wip
Some checks failed
continuous-integration/drone/pr Build is failing
2025-10-16 14:46:14 +03:30
6aa03cc05b squash wip
Some checks reported errors
continuous-integration/drone/pr Build was killed
2025-10-16 14:45:35 +03:30
226 changed files with 7498 additions and 11049 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,42 +1,42 @@
--- # ---
kind: pipeline # kind: pipeline
type: exec # type: exec
name: amd64 — msvc # name: amd64 — msvc
trigger: # trigger:
branch: # branch:
- main # - main
platform: # platform:
os: windows # os: windows
arch: amd64 # arch: amd64
#
steps: # steps:
- name: unit tests # - name: unit tests
shell: powershell # shell: powershell
commands: # commands:
- ./tools/ci/amd64/msvc/unit_tests.ps1 # - ./tools/ci/amd64/msvc/unit_tests.ps1
#
--- # ---
kind: pipeline # kind: pipeline
type: docker # type: docker
name: amd64 — gcc # name: amd64 — gcc
trigger: # trigger:
branch: # branch:
- main # - main
#
steps: # steps:
- name: unit tests # - name: unit tests
image: ci:latest # image: ci:latest
pull: if-not-exists # pull: if-not-exists
commands: # commands:
- ./tools/ci/amd64/gcc/unit_tests.sh # - ./tools/ci/amd64/gcc/unit_tests.sh
#
- name: valgrind # - name: valgrind
image: ci:latest # image: ci:latest
pull: if-not-exists # pull: if-not-exists
commands: # commands:
- ./tools/ci/amd64/gcc/valgrind.sh # - ./tools/ci/amd64/gcc/valgrind.sh
#
--- # ---
kind: pipeline kind: pipeline
type: docker type: docker
name: amd64 — clang name: amd64 — clang
@ -45,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,25 +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)
vec3 positions[3] = vec3[](
vec3(0.0, -0.5, 0.5),
vec3(0.5, 0.5, 0.5),
vec3(-0.5, 0.5, 0.5)
); );
vec3 colors[3] = vec3[]( vec3 colors[3] = vec3[](
vec3(0.0, 0.0, 0.0), vec3(1.0, 0.0, 0.0),
vec3(0.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0),
vec3(0.0, 0.0, 0.0) vec3(0.0, 0.0, 1.0)
); );
layout(location = 0) out vec3 out_frag_color; layout(location = 0) out vec3 out_frag_color;
void main() void main()
{ {
gl_Position = view_projection * vec4(positions[gl_VertexIndex], 1.0); gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
out_frag_color = colors[gl_VertexIndex]; out_frag_color = colors[gl_VertexIndex];
} }

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,259 +1,26 @@
add_module(NAME logger INTERFACES logger.cppm TESTS logger.test.cpp) # engine
add_module(NAME bitwise INTERFACES operations.cppm) add_subdirectory(./std)
add_module(NAME env INTERFACES constants.cppm) add_subdirectory(./bitwise)
add_module(NAME memory INTERFACES null_on_move.cppm reference.cppm scope.cppm) add_subdirectory(./env)
add_module(NAME time INTERFACES timer.cppm TESTS timer.test.cpp) 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( # apps
NAME add_subdirectory(./mirror)
test add_subdirectory(test)
INTERFACES
test.cppm
expects.cppm
registry.cppm
SOURCES
entrypoint.cpp
DEPENDENCIES
logger
TESTS
test.test.cpp
)
add_module(
NAME
lt_debug
ROOT_DIR
${CMAKE_CURRENT_SOURCE_DIR}/debug
INTERFACES
instrumentor.cppm
assertions.cppm
DEPENDENCIES
logger
)
add_module(
NAME
math
INTERFACES
algebra.cppm
mat4.cppm
trig.cppm
vec2.cppm
vec3.cppm
vec4.cppm
components.cppm
)
add_module(
NAME
assets
INTERFACES
shader.cppm
metadata.cppm
DEPENDENCIES
logger
lt_debug
TESTS
shader.test.cpp
)
add_module(
NAME
asset_baker
ROOT_DIR
${CMAKE_CURRENT_SOURCE_DIR}/asset_baker
INTERFACES
bakers.cppm
ENTRYPOINT
entrypoint.cpp
DEPENDENCIES
assets
logger
lt_debug
TESTS
bakers.test.cpp
)
# add_executable(asset_baker entrypoint.cpp) target_link_libraries(asset_baker
# PRIVATE libasset_baker)
add_module(NAME camera INTERFACES components.cppm DEPENDENCIES math)
add_module(
NAME
app
INTERFACES
application.cppm
system.cppm
DEPENDENCIES
memory
PRIVATE_DEPENDENCIES
lt_debug
)
add_module(
NAME
ecs
INTERFACES
sparse_set.cppm
registry.cppm
entity.cppm
DEPENDENCIES
logger
lt_debug
memory
TESTS
registry.test.cpp
sparse_set.test.cpp
)
add_module(NAME input_codes INTERFACES input_codes.cppm)
if(WIN32)
add_module(
NAME
surface
INTERFACES
constants.cppm
system.cppm
requests.cppm
events.cppm
components.cppm
SOURCES
platform_windows.cpp
DEPENDENCIES
ecs
app
math
memory
input_codes
PRIVATE_DEPENDENCIES
logger
lt_debug
time
)
elseif(UNIX)
add_module(
NAME
surface
INTERFACES
constants.cppm
system.cppm
requests.cppm
events.cppm
components.cppm
SOURCES
platform_linux.cpp
DEPENDENCIES
ecs
app
math
memory
input_codes
PRIVATE_DEPENDENCIES
X11
logger
lt_debug
time
TESTS
system.test.cpp
)
else()
message(FATAL "Failed to generate cmake: unsupported platform")
endif()
add_module(
NAME
input
INTERFACES
system.cppm
components.cppm
events.cppm
DEPENDENCIES
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
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
TEST_INTERFACES
_tests/utils.cppm
# _tests/renderer.cpp _tests/surface.cpp _tests/system.cpp
)
add_module(
NAME
mirror
ROOT_DIR
${CMAKE_CURRENT_SOURCE_DIR}/mirror
INTERFACES
system.cppm
DEPENDENCIES
memory
app
time
input
surface
renderer
camera
# TESTS system.test.cpp
)
add_executable(exectest ${CMAKE_CURRENT_SOURCE_DIR}/mirror/entrypoint.cpp)
target_link_libraries(
exectest
PRIVATE mirror
app
time
input
surface
renderer
camera
)
# add_executable_module(mirror entrypoint/mirror.cpp)
# target_link_libraries(mirror PRIVATE libmirror input)

View file

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

View file

@ -1,94 +0,0 @@
export module app;
import app.system;
import memory.reference;
import memory.scope;
import std;
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".
*/
export 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 {
void Application::game_loop()
{
while (true)
{
for (auto &system : m_systems)
{
const auto &last_tick = system->get_last_tick_result();
const auto now = std::chrono::steady_clock::now();
system->tick(TickInfo {
.delta_time = now - last_tick.end_time,
.budget = std::chrono::milliseconds { 10 },
.start_time = now,
});
}
for (auto &system : m_systems_to_be_registered)
{
m_systems.emplace_back(system)->on_register();
}
for (auto &system : m_systems_to_be_unregistered)
{
m_systems.erase(
std::remove(m_systems.begin(), m_systems.end(), system),
m_systems.end()
);
}
if (m_systems.empty())
{
return;
}
}
}
void Application::register_system(memory::Ref<app::ISystem> system)
{
m_systems.emplace_back(std::move(system));
}
void Application::unregister_system(memory::Ref<app::ISystem> system)
{
m_systems_to_be_unregistered.emplace_back(std::move(system));
}
} // namespace lt::app

View file

@ -0,0 +1,55 @@
#include <app/application.hpp>
#include <app/system.hpp>
#include <memory/reference.hpp>
namespace lt::app {
void Application::game_loop()
{
while (true)
{
for (auto &system : m_systems)
{
const auto &last_tick = system->get_last_tick_result();
const auto now = std::chrono::steady_clock::now();
system->tick(
TickInfo {
.delta_time = now - last_tick.end_time,
.budget = std::chrono::milliseconds { 10 },
.start_time = now,
}
);
}
for (auto &system : m_systems_to_be_registered)
{
m_systems.emplace_back(system)->on_register();
}
for (auto &system : m_systems_to_be_unregistered)
{
m_systems.erase(
std::remove(m_systems.begin(), m_systems.end(), system),
m_systems.end()
);
}
if (m_systems.empty())
{
return;
}
}
}
void Application::register_system(memory::Ref<app::ISystem> system)
{
m_systems.emplace_back(std::move(system));
}
void Application::unregister_system(memory::Ref<app::ISystem> system)
{
m_systems_to_be_unregistered.emplace_back(std::move(system));
}
} // namespace lt::app

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,13 +1,13 @@
export module app.system; #pragma once
import logger;
import std; #include <chrono>
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)
*/ */
export struct TickInfo struct TickInfo
{ {
using Timepoint_T = std::chrono::time_point<std::chrono::steady_clock>; using Timepoint_T = std::chrono::time_point<std::chrono::steady_clock>;
@ -30,7 +30,7 @@ export struct TickInfo
}; };
/** Information about how a system's tick performed */ /** Information about how a system's tick performed */
export struct TickResult struct TickResult
{ {
using Timepoint_T = std::chrono::time_point<std::chrono::steady_clock>; using Timepoint_T = std::chrono::time_point<std::chrono::steady_clock>;
@ -46,9 +46,10 @@ export struct TickResult
Timepoint_T end_time; Timepoint_T end_time;
}; };
export struct SystemDiagnosis
struct SystemDiagnosis
{ {
enum class Severity : std::uint8_t enum class Severity : uint8_t
{ {
verbose, verbose,
info, info,
@ -64,14 +65,14 @@ export struct SystemDiagnosis
Severity severity; Severity severity;
}; };
export class SystemStats 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
@ -83,7 +84,7 @@ private:
std::vector<SystemDiagnosis> m_diagnosis; std::vector<SystemDiagnosis> m_diagnosis;
}; };
export class ISystem class ISystem
{ {
public: public:
ISystem() = default; ISystem() = default;

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,10 +1,7 @@
import assets.shader; #include <asset_baker/bakers.hpp>
import logger; #include <assets/shader.hpp>
import bakers;
import std;
auto main(int argc, char *argv[]) -> int32_t
auto main(int argc, char *argv[]) -> std::int32_t
try try
{ {
if (argc != 2) if (argc != 2)
@ -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,12 +1,8 @@
export module bakers; #pragma once
import debug.assertions; #include <assets/shader.hpp>
import assets.metadata;
import assets.shader;
import logger;
import std;
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
@ -15,27 +11,29 @@ 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(std::format( system(
"glslc --target-env=vulkan1.4 -std=450core -fshader-stage={} {} -o {}", std::format(
type == vertex ? "vert" : "frag", "glslc --target-env=vulkan1.4 -std=450core -fshader-stage={} {} -o {}",
glsl_path, type == vertex ? "vert" : "frag",
spv_path glsl_path,
) spv_path
.c_str()); )
.c_str()
);
auto stream = std::ifstream(spv_path, std::ios::binary); auto stream = std::ifstream(spv_path, std::ios::binary);
lt::debug::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",
@ -48,7 +46,7 @@ export void bake_shader(
auto bytes = std::vector<std::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
lt::log::debug("BYTES: {}", bytes.size()); 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 assets.metadata;
import debug.assertions;
import std;
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 : std::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 &
{
debug::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 {
@ -90,14 +14,14 @@ constexpr auto total_metadata_size = //
ShaderAsset::ShaderAsset(const std::filesystem::path &path): m_stream(path) ShaderAsset::ShaderAsset(const std::filesystem::path &path): m_stream(path)
{ {
debug::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) {
m_stream.read(std::bit_cast<char *>(&field), sizeof(field)); m_stream.read(std::bit_cast<char *>(&field), sizeof(field));
}; };
m_stream.seekg(0, std::ifstream::end); m_stream.seekg(0, std::ifstream::end);
const auto file_size = static_cast<std::size_t>(m_stream.tellg()); const auto file_size = static_cast<size_t>(m_stream.tellg());
debug::ensure( ensure(
file_size > total_metadata_size, file_size > total_metadata_size,
"Failed to open shader asset at: {}, file smaller than metadata: {} < {}", "Failed to open shader asset at: {}, file smaller than metadata: {} < {}",
path.string(), path.string(),
@ -115,7 +39,7 @@ ShaderAsset::ShaderAsset(const std::filesystem::path &path): m_stream(path)
read(m_code_blob_metadata.compressed_size); read(m_code_blob_metadata.compressed_size);
read(m_code_blob_metadata.uncompressed_size); read(m_code_blob_metadata.uncompressed_size);
debug::ensure( ensure(
m_asset_metadata.type == asset_type_identifier, m_asset_metadata.type == asset_type_identifier,
"Failed to open shader asset at: {}, incorrect asset type: {} != {}", "Failed to open shader asset at: {}, incorrect asset type: {} != {}",
path.string(), path.string(),
@ -123,7 +47,7 @@ ShaderAsset::ShaderAsset(const std::filesystem::path &path): m_stream(path)
asset_type_identifier asset_type_identifier
); );
debug::ensure( ensure(
m_asset_metadata.version == current_version, m_asset_metadata.version == current_version,
"Failed to open shader asset at: {}, version mismatch: {} != {}", "Failed to open shader asset at: {}, version mismatch: {} != {}",
path.string(), path.string(),
@ -131,21 +55,21 @@ ShaderAsset::ShaderAsset(const std::filesystem::path &path): m_stream(path)
current_version current_version
); );
debug::ensure( ensure(
std::to_underlying(m_metadata.type) <= std::to_underlying(Type::compute), std::to_underlying(m_metadata.type) <= std::to_underlying(Type::compute),
"Failed to open shader asset at: {}, invalid shader type: {}", "Failed to open shader asset at: {}, invalid shader type: {}",
path.string(), path.string(),
std::to_underlying(m_metadata.type) std::to_underlying(m_metadata.type)
); );
debug::ensure( ensure(
m_code_blob_metadata.tag == std::to_underlying(BlobTag::code), m_code_blob_metadata.tag == std::to_underlying(BlobTag::code),
"Failed to open shader asset at: {}, invalid blob tag: {}", "Failed to open shader asset at: {}, invalid blob tag: {}",
path.string(), path.string(),
m_code_blob_metadata.tag m_code_blob_metadata.tag
); );
debug::ensure( ensure(
m_code_blob_metadata.offset + m_code_blob_metadata.compressed_size <= file_size, m_code_blob_metadata.offset + m_code_blob_metadata.compressed_size <= file_size,
"Failed to open shader asset at: {}, file smaller than blob: {} > {} + {}", "Failed to open shader asset at: {}, file smaller than blob: {} > {} + {}",
path.string(), path.string(),
@ -175,7 +99,7 @@ ShaderAsset::ShaderAsset(const std::filesystem::path &path): m_stream(path)
.uncompressed_size = code_blob.size(), .uncompressed_size = code_blob.size(),
}; };
debug::ensure(stream.is_open(), "Failed to pack shader asset to {}", destination.string()); ensure(stream.is_open(), "Failed to pack shader asset to {}", destination.string());
const auto write = [&stream](auto &field) { const auto write = [&stream](auto &field) {
stream.write(std::bit_cast<char *>(&field), sizeof(field)); stream.write(std::bit_cast<char *>(&field), sizeof(field));
}; };
@ -192,18 +116,14 @@ ShaderAsset::ShaderAsset(const std::filesystem::path &path): m_stream(path)
void ShaderAsset::unpack_to(BlobTag tag, std::span<std::byte> destination) const void ShaderAsset::unpack_to(BlobTag tag, std::span<std::byte> destination) const
{ {
debug::ensure( ensure(tag == BlobTag::code, "Invalid blob tag for shader asset: {}", std::to_underlying(tag));
tag == BlobTag::code,
"Invalid blob tag for shader asset: {}",
std::to_underlying(tag)
);
debug::ensure( ensure(
destination.size() >= m_code_blob_metadata.uncompressed_size, destination.size() >= m_code_blob_metadata.uncompressed_size,
"Failed to unpack shader blob {} to destination ({}) of size {} since it's smaller " "Failed to unpack shader blob {} to destination ({}) of size {} since it's smaller "
"than the blobl's uncompressed size: {}", "than the blobl's uncompressed size: {}",
std::to_underlying(tag), std::to_underlying(tag),
std::bit_cast<std::size_t>(destination.data()), std::bit_cast<size_t>(destination.data()),
destination.size(), destination.size(),
m_code_blob_metadata.uncompressed_size m_code_blob_metadata.uncompressed_size
); );
@ -217,11 +137,7 @@ void ShaderAsset::unpack_to(BlobTag tag, std::span<std::byte> destination) const
[[nodiscard]] auto ShaderAsset::unpack(BlobTag tag) const -> Blob [[nodiscard]] auto ShaderAsset::unpack(BlobTag tag) const -> Blob
{ {
debug::ensure( ensure(tag == BlobTag::code, "Invalid blob tag for shader asset: {}", std::to_underlying(tag));
tag == BlobTag::code,
"Invalid blob tag for shader asset: {}",
std::to_underlying(tag)
);
auto blob = Blob(m_code_blob_metadata.uncompressed_size); auto blob = Blob(m_code_blob_metadata.uncompressed_size);
unpack_to(tag, blob); unpack_to(tag, blob);

View file

@ -1,8 +1,6 @@
import assets.metadata; #include <assets/shader.hpp>
import assets.shader; #include <ranges>
import test.test; #include <test/test.hpp>
import test.expects;
import std;
using ::lt::assets::AssetMetadata; using ::lt::assets::AssetMetadata;
using ::lt::assets::BlobMetadata; using ::lt::assets::BlobMetadata;
@ -12,7 +10,6 @@ using ::lt::test::expect_eq;
using ::lt::test::expect_throw; using ::lt::test::expect_throw;
using ::lt::test::expect_true; using ::lt::test::expect_true;
using ::lt::test::Suite; using ::lt::test::Suite;
using ::lt::test::operator""_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/" };
@ -73,7 +70,7 @@ Suite packing = "shader_pack"_suite = [] {
expect_true(stream.is_open()); expect_true(stream.is_open());
stream.seekg(0, std::ios::end); stream.seekg(0, std::ios::end);
const auto file_size = static_cast<std::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();

View file

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

View file

@ -1,19 +1,18 @@
export module assets.metadata; #pragma once
import std;
export namespace lt::assets { namespace lt::assets {
using Type_T = std::array<const char, 16>; using Type_T = std::array<const char, 16>;
using Tag_T = std::uint8_t; using Tag_T = uint8_t;
using Version = std::uint8_t; using Version = uint8_t;
using Blob = std::vector<std::byte>; using Blob = std::vector<std::byte>;
constexpr auto current_version = Version { 1u }; constexpr auto current_version = Version { 1u };
enum class CompressionType : std::uint8_t enum class CompressionType : uint8_t
{ {
none, none,
lz4, lz4,
@ -31,13 +30,13 @@ struct BlobMetadata
{ {
Tag_T tag; Tag_T tag;
std::size_t offset; size_t offset;
CompressionType compression_type; CompressionType compression_type;
std::size_t compressed_size; size_t compressed_size;
std::size_t uncompressed_size; size_t uncompressed_size;
}; };
} // namespace lt::assets } // namespace lt::assets

View file

@ -0,0 +1,74 @@
#pragma once
#include <assets/metadata.hpp>
namespace lt::assets {
class ShaderAsset
{
public:
static constexpr auto asset_type_identifier = Type_T { "SHADER_________" };
enum class BlobTag : Tag_T
{
code,
};
enum class Type : uint8_t
{
vertex,
fragment,
geometry,
compute,
};
struct Metadata
{
Type type;
};
static void pack(
const std::filesystem::path &destination,
AssetMetadata asset_metadata,
Metadata metadata,
Blob code_blob
);
ShaderAsset(const std::filesystem::path &path);
void unpack_to(BlobTag tag, std::span<std::byte> destination) const;
[[nodiscard]] auto unpack(BlobTag tag) const -> Blob;
[[nodiscard]] auto get_asset_metadata() const -> const AssetMetadata &
{
return m_asset_metadata;
}
[[nodiscard]] auto get_metadata() const -> const Metadata &
{
return m_metadata;
}
[[nodiscard]] auto get_blob_metadata(BlobTag tag) const -> const BlobMetadata &
{
ensure(
tag == BlobTag::code,
"Invalid blob tag for shader asset: {}",
std::to_underlying(tag)
);
return m_code_blob_metadata;
}
private:
AssetMetadata m_asset_metadata {};
Metadata m_metadata {};
BlobMetadata m_code_blob_metadata {};
mutable std::ifstream m_stream;
};
} // namespace lt::assets

View file

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

View file

@ -1,12 +0,0 @@
export module bitwise;
import std;
namespace lt::bitwise {
/* bit-wise */
export constexpr auto bit(std::uint32_t x) -> std::uint32_t
{
return 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,21 +0,0 @@
export module camera.components;
import math.vec4;
namespace lt::camera::components {
export struct PerspectiveCamera
{
float vertical_fov {};
float near_plane {};
float far_plane {};
float aspect_ratio {};
math::vec4 background_color;
bool is_primary {};
};
} // namespace lt::camera::components

View file

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

@ -1,47 +0,0 @@
export module debug.assertions;
import std;
namespace lt::debug {
///////////////////////////////////////
// ----------* INTERFACE *--------- //
/////////////////////////////////////
export 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()
);
};
export template<typename Expression_T, typename... Args_T>
ensure(Expression_T, std::format_string<Args_T...>, Args_T &&...)
-> ensure<Expression_T, Args_T...>;
///////////////////////////////////////
// * IMPLEMENTATION -- TEMPLATES * //
/////////////////////////////////////
template<typename Expression_T, typename... Args_T>
ensure<Expression_T, Args_T...>::ensure(
const Expression_T &expression,
std::format_string<Args_T...> fmt,
Args_T &&...args,
const std::source_location &location
)
{
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()
) };
}
}
} // namespace lt::debug

View file

@ -1,146 +0,0 @@
export module debug.instrumentor;
import std;
import logger;
namespace lt::debug {
struct ScopeProfileResult
{
std::string name;
long long start, duration;
std::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::debug
/* 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()
module :private;
namespace lt::debug {
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::warn("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::debug

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,17 +1,19 @@
import ecs.registry; #include <ecs/registry.hpp>
import test.test; #include <ranges>
import test.expects; #include <test/expects.hpp>
import std; #include <test/test.hpp>
using ::lt::ecs::EntityId; using lt::test::Case;
using ::lt::ecs::Registry; using lt::test::expect_unreachable;
using ::lt::test::Case; using lt::test::Suite;
using ::lt::test::expect_eq;
using ::lt::test::expect_false; using lt::test::expect_eq;
using ::lt::test::expect_true;
using ::lt::test::expect_unreachable; using lt::test::expect_false;
using ::lt::test::Suite; using lt::test::expect_true;
using ::lt::test::operator""_suite;
using lt::ecs::EntityId;
using lt::ecs::Registry;
struct Component struct Component
{ {

View file

@ -1,16 +1,16 @@
import ecs.sparse_set; #include <ecs/sparse_set.hpp>
import test.test; #include <ranges>
import test.expects; #include <test/expects.hpp>
import std; #include <test/test.hpp>
using ::lt::test::Case; using lt::test::Case;
using ::lt::test::expect_eq; using lt::test::Suite;
using ::lt::test::expect_false;
using ::lt::test::expect_ne; using lt::test::expect_eq;
using ::lt::test::expect_throw; using lt::test::expect_false;
using ::lt::test::expect_true; using lt::test::expect_ne;
using ::lt::test::Suite; using lt::test::expect_throw;
using ::lt::test::operator""_suite; using lt::test::expect_true;
using Set = lt::ecs::SparseSet<int>; using Set = lt::ecs::SparseSet<int>;
constexpr auto capacity = 100; constexpr auto capacity = 100;

View file

@ -1,20 +1,19 @@
export module ecs.entity; #pragma once
import debug.assertions;
import memory.reference; #include <ecs/registry.hpp>
import ecs.registry; #include <memory/reference.hpp>
import std;
namespace lt::ecs { namespace lt::ecs {
/** High-level entity convenience wrapper */ /** High-level entity convenience wrapper */
export class Entity class Entity
{ {
public: public:
Entity(memory::Ref<Registry> registry, EntityId identifier) Entity(memory::Ref<Registry> registry, EntityId identifier)
: m_registry(std::move(registry)) : m_registry(std::move(registry))
, m_identifier(identifier) , m_identifier(identifier)
{ {
debug::ensure(m_registry, "Failed to create Entity ({}): null registry", m_identifier); ensure(m_registry, "Failed to create Entity ({}): null registry", m_identifier);
} }
template<typename Component_T> template<typename Component_T>

View file

@ -1,14 +1,13 @@
export module ecs.registry; #pragma once
import debug.assertions;
import ecs.sparse_set; #include <ecs/sparse_set.hpp>
import memory.scope; #include <memory/scope.hpp>
import std;
namespace lt::ecs { namespace lt::ecs {
export using EntityId = std::uint32_t; using EntityId = uint32_t;
export constexpr auto null_entity = std::numeric_limits<EntityId>::max(); constexpr auto null_entity = std::numeric_limits<EntityId>::max();
/** A registry of components, the heart of an ECS architecture. /** A registry of components, the heart of an ECS architecture.
* *
@ -23,7 +22,7 @@ export constexpr auto null_entity = std::numeric_limits<EntityId>::max();
* @ref https://github.com/skypjack/entt * @ref https://github.com/skypjack/entt
* @ref https://github.com/SanderMertens/flecs * @ref https://github.com/SanderMertens/flecs
*/ */
export class Registry class Registry
{ {
public: public:
using UnderlyingSparseSet_T = TypeErasedSparseSet<EntityId>; using UnderlyingSparseSet_T = TypeErasedSparseSet<EntityId>;
@ -190,25 +189,25 @@ public:
} }
}; };
[[nodiscard]] auto get_entity_count() const -> std::size_t [[nodiscard]] auto get_entity_count() const -> size_t
{ {
return static_cast<std::size_t>(m_entity_count); return static_cast<size_t>(m_entity_count);
} }
private: private:
using TypeId = std::size_t; using TypeId = size_t;
static consteval auto hash_cstr(const char *str) -> TypeId static consteval auto hash_cstr(const char *str) -> TypeId
{ {
constexpr auto fnv_offset_basis = std::size_t { 14695981039346656037ull }; constexpr auto fnv_offset_basis = size_t { 14695981039346656037ull };
constexpr auto fnv_prime = std::size_t { 1099511628211ull }; constexpr auto fnv_prime = size_t { 1099511628211ull };
auto hash = fnv_offset_basis; auto hash = fnv_offset_basis;
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<std::uint8_t>(ch); hash ^= static_cast<uint8_t>(ch);
} }
return hash; return hash;
@ -242,7 +241,7 @@ private:
auto *base_set = m_sparsed_sets[type_id].get(); auto *base_set = m_sparsed_sets[type_id].get();
auto *derived_set = dynamic_cast<SparseSet<T, EntityId> *>(base_set); auto *derived_set = dynamic_cast<SparseSet<T, EntityId> *>(base_set);
debug::ensure(derived_set, "Failed to downcast to derived set"); ensure(derived_set, "Failed to downcast to derived set");
return *derived_set; return *derived_set;
} }
@ -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 debug.assertions;
import std;
namespace lt::ecs { namespace lt::ecs {
/** /**
*
* @ref https://programmingpraxis.com/2012/03/09/sparse-sets/ * @ref https://programmingpraxis.com/2012/03/09/sparse-sets/
*/ */
export template<typename Identifier_T = std::uint32_t> template<typename Identifier_T = uint32_t>
class TypeErasedSparseSet class TypeErasedSparseSet
{ {
public: public:
@ -26,19 +25,19 @@ public:
virtual void remove(Identifier_T identifier) = 0; virtual void remove(Identifier_T identifier) = 0;
}; };
export template<typename Value_T, typename Identifier_T = std::uint32_t> template<typename Value_T, typename Identifier_T = uint32_t>
class SparseSet: public TypeErasedSparseSet<Identifier_T> class SparseSet: public TypeErasedSparseSet<Identifier_T>
{ {
public: public:
using Dense_T = std::pair<Identifier_T, Value_T>; using Dense_T = std::pair<Identifier_T, Value_T>;
static constexpr auto max_capacity = std::size_t { 1'000'000 }; static constexpr auto max_capacity = size_t { 1'000'000 };
static constexpr auto null_identifier = std::numeric_limits<Identifier_T>().max(); static constexpr auto null_identifier = std::numeric_limits<Identifier_T>().max();
explicit SparseSet(std::size_t initial_capacity = 1) explicit SparseSet(size_t initial_capacity = 1)
{ {
debug::ensure( ensure(
initial_capacity <= max_capacity, initial_capacity <= max_capacity,
"Failed to create SparseSet: capacity too large ({} > {})", "Failed to create SparseSet: capacity too large ({} > {})",
initial_capacity, initial_capacity,
@ -53,16 +52,13 @@ public:
{ {
if (m_sparse.size() < identifier + 1) if (m_sparse.size() < identifier + 1)
{ {
auto new_capacity = std::max( auto new_capacity = std::max(static_cast<size_t>(identifier + 1), m_sparse.size() * 2);
static_cast<std::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::debug("Increasing sparse vector size:", m_dead_count); // log_dbg("Increasing sparse vector size:", m_dead_count);
// log::debug("\tdead_count: {}", m_dead_count); // log_dbg("\tdead_count: {}", m_dead_count);
// log::debug("\talive_count: {}", m_alive_count); // log_dbg("\talive_count: {}", m_alive_count);
// log::debug("\tsparse.size: {} -> {}", m_sparse.size(), new_capacity); // log_dbg("\tsparse.size: {} -> {}", m_sparse.size(), new_capacity);
m_sparse.resize(new_capacity, null_identifier); m_sparse.resize(new_capacity, null_identifier);
} }
@ -149,12 +145,12 @@ public:
return std::forward<Self_T>(self).m_dense[std::forward<Self_T>(self).m_sparse[identifier]]; return std::forward<Self_T>(self).m_dense[std::forward<Self_T>(self).m_sparse[identifier]];
} }
[[nodiscard]] auto get_size() const noexcept -> std::size_t [[nodiscard]] auto get_size() const noexcept -> size_t
{ {
return m_alive_count; return m_alive_count;
} }
[[nodiscard]] auto get_capacity() const noexcept -> std::size_t [[nodiscard]] auto get_capacity() const noexcept -> size_t
{ {
return m_sparse.capacity(); return m_sparse.capacity();
} }
@ -169,9 +165,9 @@ private:
std::vector<Identifier_T> m_sparse; std::vector<Identifier_T> m_sparse;
std::size_t m_alive_count {}; size_t m_alive_count {};
std::size_t m_dead_count {}; size_t m_dead_count {};
}; };
} // namespace lt::ecs } // namespace lt::ecs

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

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

View file

@ -1,10 +1,8 @@
export module env; #pragma once
import std;
namespace lt { namespace lt {
enum class Platform : std::uint8_t enum class Platform : uint8_t
{ {
/** The GNU/Linux platform. /** The GNU/Linux platform.
* Tested on the following distros: arch-x86_64 * Tested on the following distros: arch-x86_64
@ -26,7 +24,7 @@ enum class Platform : std::uint8_t
}; };
/** The compiler that was used for compiling the project. */ /** The compiler that was used for compiling the project. */
enum class Compiler : std::uint8_t enum class Compiler : uint8_t
{ {
clang, clang,
gcc, gcc,

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::uvec2 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::uvec2
{
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::uvec2 m_pointer_position;
};
} // namespace lt::input

View file

@ -1,66 +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 debug.assertions;
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...
{ {
@ -69,7 +12,7 @@ struct overloads: Ts...
System::System(memory::Ref<ecs::Registry> registry): m_registry(std::move(registry)) System::System(memory::Ref<ecs::Registry> registry): m_registry(std::move(registry))
{ {
debug::ensure(m_registry, "Failed to initialize input system: null registry"); ensure(m_registry, "Failed to initialize input system: null registry");
} }
void System::tick(app::TickInfo tick) void System::tick(app::TickInfo tick)
@ -90,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)
@ -156,28 +99,32 @@ 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::debug("Key code larger than key container size, implement platform-dependant " log_dbg(
"key-code-mapping!"); "Key code larger than key container size, implement platform-dependant "
"key-code-mapping!"
);
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::debug("Key code larger than key container size, implement platform-dependant " log_dbg(
"key-code-mapping!"); "Key code larger than key container size, implement platform-dependant "
"key-code-mapping!"
);
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)
@ -187,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,17 +1,11 @@
import std; #include <ecs/entity.hpp>
import input.system; #include <input/components.hpp>
import input.codes; #include <input/system.hpp>
import std; #include <memory/reference.hpp>
import test.test; #include <memory/scope.hpp>
import test.expects; #include <ranges>
import surface.events; #include <surface/system.hpp>
import memory.scope; #include <test/test.hpp>
import memory.reference;
import app.system;
import ecs.entity;
import ecs.registry;
import surface.system;
// NOLINTBEGIN // NOLINTBEGIN
using namespace lt; using namespace lt;
@ -23,7 +17,6 @@ using test::expect_eq;
using test::expect_false; using test::expect_false;
using test::expect_ne; using test::expect_ne;
using test::expect_not_nullptr; using test::expect_not_nullptr;
using test::operator""_suite;
using test::expect_throw; using test::expect_throw;
using test::Suite; using test::Suite;
// NOLINTEND // NOLINTEND
@ -162,7 +155,7 @@ 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 },
} }
); );
@ -170,7 +163,7 @@ Suite tick = "tick"_suite = [] {
system.tick(tick_info()); system.tick(tick_info());
expect_eq(input.get_action(action_key).state, input::InputAction::State::inactive); expect_eq(input.get_action(action_key).state, input::InputAction::State::inactive);
surface.push_event(surface::KeyPressedEvent(Key::A)); surface.push_event(surface::KeyPressedEvent(69));
system.tick(tick_info()); system.tick(tick_info());
expect_eq(input.get_action(action_key).state, input::InputAction::State::triggered); expect_eq(input.get_action(action_key).state, input::InputAction::State::triggered);
@ -182,7 +175,7 @@ Suite tick = "tick"_suite = [] {
system.tick(tick_info()); system.tick(tick_info());
expect_eq(input.get_action(action_key).state, input::InputAction::State::active); expect_eq(input.get_action(action_key).state, input::InputAction::State::active);
surface.push_event(surface::KeyReleasedEvent(Key::A)); surface.push_event(surface::KeyReleasedEvent(69));
system.tick(tick_info()); system.tick(tick_info());
expect_eq(input.get_action(action_key).state, input::InputAction::State::inactive); expect_eq(input.get_action(action_key).state, input::InputAction::State::inactive);
}; };
@ -202,7 +195,7 @@ 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 },
} }
); );
}; };

View file

@ -1,17 +1,19 @@
export module input.system:components; #pragma once
import input.codes;
import std; #include <vector>
namespace lt::input { namespace lt::input {
export struct Trigger struct Trigger
{ {
Key mapped_keycode; uint32_t mapped_keycode;
}; };
export struct InputAction struct InputAction
{ {
enum class State : std::uint8_t using Key = size_t;
enum class State : uint8_t
{ {
inactive, inactive,
active, active,
@ -26,18 +28,18 @@ export struct InputAction
Trigger trigger; Trigger trigger;
}; };
export class InputComponent class InputComponent
{ {
public: public:
InputComponent() = default; InputComponent() = default;
auto add_action(InputAction action) -> std::size_t auto add_action(InputAction action) -> size_t
{ {
m_actions.emplace_back(std::move(action)); m_actions.emplace_back(std::move(action));
return m_actions.size() - 1; return m_actions.size() - 1;
} }
auto get_action(std::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

@ -1,7 +1,11 @@
export module input.codes; #pragma once
import std;
export enum class Key: std::uint16_t { #include <cstdint>
namespace lt::Key {
enum : uint16_t
{
/* digits */ /* digits */
D0 = 48, D0 = 48,
D1 = 49, D1 = 49,
@ -170,4 +174,7 @@ export enum class Key: std::uint16_t {
Pause = 284, Pause = 284,
Menu = 348, 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

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

View file

@ -1,180 +0,0 @@
export module logger;
import std;
namespace lt::log {
/** Severity of a log message. */
enum class Level : std::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,
};
namespace details {
inline auto thread_hash_id() noexcept -> std::uint64_t
{
return static_cast<std::uint64_t>(std::hash<std::thread::id> {}(std::this_thread::get_id()));
}
} // namespace details
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
{
constexpr auto to_string = [](Level level, auto location) {
// 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, location),
std::format("{}:{}", path.filename().string(), location.line()),
std::format(format, std::forward<Args>(arguments)...)
);
}
};
template<typename... Args>
print(Level, const std::source_location &, std::format_string<Args...>, Args &&...) noexcept
-> print<Args...>;
export 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)...);
}
};
export template<typename... Args>
trace(std::format_string<Args...>, Args &&...) noexcept -> trace<Args...>;
export 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)...);
}
};
export template<typename... Args>
debug(std::format_string<Args...>, Args &&...) noexcept -> debug<Args...>;
export 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)...);
}
};
export template<typename... Args>
info(std::format_string<Args...>, Args &&...) noexcept -> info<Args...>;
export 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)...);
}
};
export template<typename... Args>
warn(std::format_string<Args...>, Args &&...) noexcept -> warn<Args...>;
export 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)...);
}
};
export template<typename... Args>
error(std::format_string<Args...>, Args &&...) noexcept -> error<Args...>;
export 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)...);
}
};
export template<typename... Args>
critical(std::format_string<Args...>, Args &&...) noexcept -> critical<Args...>;
} // namespace lt::log

View file

@ -1,25 +0,0 @@
import logger;
import test.test;
using ::lt::test::Case;
using ::lt::test::Suite;
Suite suite = [] {
Case { "no format" } = [] {
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,16 +0,0 @@
export module math.components;
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,8 +1,8 @@
export module math.algebra; #pragma once
import math.mat4;
import std;
export namespace lt::math { #include <math/mat4.hpp>
namespace lt::math {
/** /**
* let... * let...
@ -31,29 +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>
constexpr auto perspective(T field_of_view, T aspect_ratio, T z_near, T z_far) constexpr auto perspective(T field_of_view, T aspect_ratio, T z_near, T z_far)
{ {
const T half_fov_tan = std::tan(field_of_view / static_cast<T>(2)); const T half_fov_tan = std::tan(field_of_view / static_cast<T>(2));
auto result = mat4_impl<T>::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

@ -1,15 +1,14 @@
export module math.mat4; #pragma once
import math.vec3;
import math.vec4; #include <math/vec3.hpp>
import std; #include <math/vec4.hpp>
namespace lt::math { namespace lt::math {
export template<typename T = float> template<typename T = float>
struct mat4_impl struct mat4_impl
{ {
using Column_T = vec4_impl<T>; using Column_T = vec4_impl<T>;
constexpr explicit mat4_impl(T scalar = 0) constexpr explicit mat4_impl(T scalar = 0)
: values( : values(
{ {
@ -44,7 +43,7 @@ struct mat4_impl
{ {
} }
[[nodiscard]] static constexpr auto identity() -> mat4_impl<T> [[nodiscard]] constexpr auto identity() -> mat4_impl<T>
{ {
return mat4_impl<T> { return mat4_impl<T> {
{ 1 }, {}, {}, {}, // { 1 }, {}, {}, {}, //
@ -54,12 +53,12 @@ struct mat4_impl
}; };
} }
[[nodiscard]] constexpr auto operator[](std::size_t idx) -> Column_T & [[nodiscard]] constexpr auto operator[](size_t idx) -> Column_T &
{ {
return values[idx]; return values[idx];
} }
[[nodiscard]] constexpr auto operator[](std::size_t idx) const -> const Column_T & [[nodiscard]] constexpr auto operator[](size_t idx) const -> const Column_T &
{ {
return values[idx]; return values[idx];
} }
@ -77,34 +76,34 @@ struct mat4_impl
std::array<Column_T, 4> values; // NOLINT std::array<Column_T, 4> values; // NOLINT
}; };
export template<typename T> template<typename T>
[[nodiscard]] auto translate(const vec3_impl<T> &value) -> mat4_impl<T> [[nodiscard]] inline auto translate(const vec3_impl<T> &value) -> mat4_impl<T>
{ {
return mat4_impl<T> {}; return mat4_impl<T> {};
} }
export template<typename T> template<typename T>
[[nodiscard]] auto rotate(float value, const vec3_impl<T> &xyz) -> mat4_impl<T> [[nodiscard]] inline auto rotate(float value, const vec3_impl<T> &xyz) -> mat4_impl<T>
{ {
return mat4_impl<T> {}; return mat4_impl<T> {};
} }
export template<typename T> template<typename T>
[[nodiscard]] auto scale(const vec3_impl<T> &value) -> mat4_impl<T> [[nodiscard]] inline auto scale(const vec3_impl<T> &value) -> mat4_impl<T>
{ {
return mat4_impl<T> {}; return mat4_impl<T> {};
} }
export template<typename T> template<typename T>
[[nodiscard]] auto inverse(const mat4_impl<T> &value) -> mat4_impl<T> [[nodiscard]] inline auto inverse(const mat4_impl<T> &value) -> mat4_impl<T>
{ {
return mat4_impl<T> {}; return mat4_impl<T> {};
} }
export using mat4 = mat4_impl<float>; using mat4 = mat4_impl<float>;
export using imat4 = mat4_impl<std::int32_t>; using imat4 = mat4_impl<int32_t>;
export using umat4 = mat4_impl<std::uint32_t>; using umat4 = mat4_impl<uint32_t>;
} // namespace lt::math } // namespace lt::math

View file

@ -1,6 +1,6 @@
export module math.trig; #pragma once
export namespace lt::math { namespace lt::math {
[[nodiscard]] constexpr auto radians(float degrees) -> float [[nodiscard]] constexpr auto radians(float degrees) -> float
{ {

View file

@ -1,10 +1,8 @@
export module math.vec2; #pragma once
import std;
namespace lt::math { namespace lt::math {
export template<typename T = float> template<typename T = float>
struct vec2_impl struct vec2_impl
{ {
constexpr vec2_impl(): x(), y() constexpr vec2_impl(): x(), y()
@ -59,15 +57,15 @@ struct vec2_impl
}; };
export using vec2 = vec2_impl<float>; using vec2 = vec2_impl<float>;
export using ivec2 = vec2_impl<std::int32_t>; using ivec2 = vec2_impl<int32_t>;
export using uvec2 = vec2_impl<std::uint32_t>; using uvec2 = vec2_impl<uint32_t>;
} // namespace lt::math } // namespace lt::math
export template<typename T> template<typename T>
struct std::formatter<lt::math::vec2_impl<T>> struct std::formatter<lt::math::vec2_impl<T>>
{ {
constexpr auto parse(std::format_parse_context &context) constexpr auto parse(std::format_parse_context &context)

View file

@ -1,11 +1,11 @@
export module math.vec3; #pragma once
import math.vec2; #include <cstdint>
import std; #include <math/vec2.hpp>
namespace lt::math { namespace lt::math {
export template<typename T = float> template<typename T = float>
struct vec3_impl struct vec3_impl
{ {
constexpr vec3_impl(): x(), y(), z() constexpr vec3_impl(): x(), y(), z()
@ -61,11 +61,11 @@ struct vec3_impl
T z; // NOLINT T z; // NOLINT
}; };
export using vec3 = vec3_impl<float>; using vec3 = vec3_impl<float>;
export using ivec3 = vec3_impl<std::int32_t>; using ivec3 = vec3_impl<int32_t>;
export using uvec3 = vec3_impl<std::uint32_t>; using uvec3 = vec3_impl<uint32_t>;
} // namespace lt::math } // namespace lt::math

View file

@ -1,11 +1,11 @@
export module math.vec4; #pragma once
import math.vec2;
import math.vec3; #include <array>
import std; #include <cstdint>
namespace lt::math { namespace lt::math {
export template<typename T = float> template<typename T = float>
struct vec4_impl struct vec4_impl
{ {
constexpr vec4_impl(): x(), y(), z(), w() constexpr vec4_impl(): x(), y(), z(), w()
@ -40,12 +40,12 @@ struct vec4_impl
}; };
} }
[[nodiscard]] constexpr auto operator[](std::size_t idx) -> T & [[nodiscard]] constexpr auto operator[](size_t idx) -> T &
{ {
return values[idx]; return values[idx];
} }
[[nodiscard]] constexpr auto operator[](std::size_t idx) const -> const T & [[nodiscard]] constexpr auto operator[](size_t idx) const -> const T &
{ {
return values[idx]; return values[idx];
} }
@ -86,15 +86,15 @@ struct vec4_impl
}; };
}; };
export using vec4 = vec4_impl<float>; using vec4 = vec4_impl<float>;
export using ivec4 = vec4_impl<std::int32_t>; using ivec4 = vec4_impl<int32_t>;
export using uvec4 = vec4_impl<std::uint32_t>; using uvec4 = vec4_impl<uint32_t>;
} // namespace lt::math } // namespace lt::math
export template<typename T> template<typename T>
struct std::formatter<lt::math::vec4_impl<T>> struct std::formatter<lt::math::vec4_impl<T>>
{ {
constexpr auto parse(std::format_parse_context &context) constexpr auto parse(std::format_parse_context &context)

View file

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

View file

@ -1,15 +1,13 @@
export module memory.null_on_move; #pragma once
import std;
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
* Vulkan handles. But may serve other purposes, hence why I kept the implementation generic. * 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:
@ -79,17 +77,12 @@ public:
return m_value; return m_value;
} }
operator std::uint64_t() const operator uint64_t() const
{ {
return (std::uint64_t)m_value; return (uint64_t)m_value;
} }
[[nodiscard]] auto get() -> Underlying_T & [[nodiscard]] auto get() -> Underlying_T
{
return m_value;
}
[[nodiscard]] auto get() const -> const Underlying_T &
{ {
return m_value; return m_value;
} }

View file

@ -1,6 +1,7 @@
export module memory.reference; #pragma once
import std; #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 std; #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(int argc, char *argv[]) -> std::int32_t
{
try
{
std::ignore = argc;
std::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;
}
}

View file

@ -1,35 +1,26 @@
export module mirror.system; #include <X11/keysym.h>
import math.vec3; #include <app/application.hpp>
import camera.components; #include <app/entrypoint.hpp>
import surface.requests; #include <app/system.hpp>
import logger; #include <ecs/entity.hpp>
import surface.system; #include <input/components.hpp>
import math.vec2; #include <input/system.hpp>
import math.vec4; #include <math/vec2.hpp>
import math.trig; #include <memory/reference.hpp>
import input.codes; #include <memory/scope.hpp>
import input.events; #include <renderer/components/messenger.hpp>
import input.system; #include <renderer/system.hpp>
import math.components; #include <surface/events/keyboard.hpp>
import memory.reference; #include <surface/events/surface.hpp>
import memory.scope; #include <surface/system.hpp>
import renderer.components; #include <time/timer.hpp>
import renderer.system;
import renderer.frontend;
import surface.events;
import time;
import app;
import app.system;
import ecs.entity;
import ecs.registry;
import std;
namespace lt { namespace lt {
void renderer_callback( void renderer_callback(
renderer::IDebugger::MessageSeverity message_severity, renderer::IMessenger::MessageSeverity message_severity,
renderer::IDebugger::MessageType message_type, renderer::IMessenger::MessageType message_type,
renderer::IDebugger::MessageData data, renderer::IMessenger::MessageData data,
std::any &user_data std::any &user_data
) )
{ {
@ -37,7 +28,7 @@ void renderer_callback(
std::ignore = message_type; std::ignore = message_type;
std::ignore = user_data; std::ignore = user_data;
log::debug("RENDERER CALLBACK: {}", std::string { data.message }); log_dbg("RENDERER CALLBACK: {}", data.message);
} }
class MirrorSystem: public lt::app::ISystem class MirrorSystem: public lt::app::ISystem
@ -45,8 +36,8 @@ class MirrorSystem: public lt::app::ISystem
public: public:
MirrorSystem( MirrorSystem(
memory::Ref<ecs::Registry> registry, memory::Ref<ecs::Registry> registry,
std::size_t quit_action_key, lt::input::InputAction::Key quit_action_key,
std::array<std::size_t, 4ul> debug_action_keys std::array<lt::input::InputAction::Key, 4> debug_action_keys
) )
: m_registry(std::move(registry)) : m_registry(std::move(registry))
, m_quit_action_key(quit_action_key) , m_quit_action_key(quit_action_key)
@ -81,11 +72,7 @@ public:
} }
if (input.get_action(m_debug_action_keys[0]).state == State::active) if (input.get_action(m_debug_action_keys[0]).state == State::active)
{ {
for (auto &[id, camera] : surface.push_request(surface::ModifyPositionRequest({ x + 5, y + 5 }));
m_registry->view<lt::camera::components::PerspectiveCamera>())
{
camera.vertical_fov += (static_cast<float>(tick.delta_time.count()) * 40.0f);
}
} }
if (input.get_action(m_debug_action_keys[1]).state == State::active) if (input.get_action(m_debug_action_keys[1]).state == State::active)
@ -128,14 +115,15 @@ public:
private: private:
memory::Ref<ecs::Registry> m_registry; memory::Ref<ecs::Registry> m_registry;
std::size_t m_quit_action_key;
std::array<std::size_t, 4ul> m_debug_action_keys {}; lt::input::InputAction::Key m_quit_action_key;
std::array<lt::input::InputAction::Key, 4> m_debug_action_keys {};
app::TickResult m_last_tick_result {}; app::TickResult m_last_tick_result {};
}; };
export class Mirror: public app::Application class Mirror: public app::Application
{ {
public: public:
Mirror() Mirror()
@ -150,7 +138,7 @@ public:
void on_window_close() void on_window_close()
{ {
log::info("Window close requested..."); log_inf("Window close requested...");
unregister_system(m_input_system); unregister_system(m_input_system);
unregister_system(m_surface_system); unregister_system(m_surface_system);
@ -176,31 +164,41 @@ public:
); );
auto &input = m_editor_registry->add<InputComponent>(m_window, {}); auto &input = m_editor_registry->add<InputComponent>(m_window, {});
auto quit_action_key = input.add_action(input::InputAction { auto quit_action_key = input.add_action(
.name = "quit", input::InputAction {
.trigger = input::Trigger { .mapped_keycode = Key::Q }, .name = "quit",
}); .trigger = input::Trigger { .mapped_keycode = XK_q },
}
);
auto debug_action_keys = std::array<std::size_t, 4ul> {}; auto debug_action_keys = std::array<lt::input::InputAction::Key, 4> {};
debug_action_keys[0] = input.add_action(input::InputAction { debug_action_keys[0] = input.add_action(
.name = "debug_1", input::InputAction {
.trigger = input::Trigger { .mapped_keycode = Key::D1 }, .name = "debug_1",
}); .trigger = input::Trigger { .mapped_keycode = XK_1 },
}
);
debug_action_keys[1] = input.add_action(input::InputAction { debug_action_keys[1] = input.add_action(
.name = "debug_2", input::InputAction {
.trigger = input::Trigger { .mapped_keycode = Key::D2 }, .name = "debug_2",
}); .trigger = input::Trigger { .mapped_keycode = XK_2 },
}
);
debug_action_keys[2] = input.add_action(input::InputAction { debug_action_keys[2] = input.add_action(
.name = "debug_3", input::InputAction {
.trigger = input::Trigger { .mapped_keycode = Key::D3 }, .name = "debug_3",
}); .trigger = input::Trigger { .mapped_keycode = XK_3 },
}
);
debug_action_keys[3] = input.add_action(input::InputAction { debug_action_keys[3] = input.add_action(
.name = "debug_4", input::InputAction {
.trigger = input::Trigger { .mapped_keycode = Key::D4 }, .name = "debug_4",
}); .trigger = input::Trigger { .mapped_keycode = XK_4 },
}
);
m_input_system = memory::create_ref<input::System>(m_editor_registry); m_input_system = memory::create_ref<input::System>(m_editor_registry);
m_mirror_system = memory::create_ref<MirrorSystem>( m_mirror_system = memory::create_ref<MirrorSystem>(
@ -215,43 +213,12 @@ public:
.config = { .target_api = renderer::Api::vulkan, .max_frames_in_flight = 3u }, .config = { .target_api = renderer::Api::vulkan, .max_frames_in_flight = 3u },
.registry = m_editor_registry, .registry = m_editor_registry,
.surface_entity = entity, .surface_entity = entity,
.debug_callback_info = renderer::IDebugger::CreateInfo { .debug_callback_info = renderer::IMessenger::CreateInfo {
.severities = renderer::IDebugger::MessageSeverity::all, .severities = renderer::IMessenger::MessageSeverity::all,
.types = renderer::IDebugger::MessageType::all, .types = renderer::IMessenger::MessageType::all,
.callback = &renderer_callback, .callback = &renderer_callback,
.user_data = this, .user_data = this,
} }); } });
m_sprite_id = m_editor_registry->create_entity();
m_editor_registry->add(
m_sprite_id,
renderer::components::Sprite {
.color = lt::math::vec3 { 1.0f, 0.0f, 0.0f },
}
);
m_editor_registry->add(
m_sprite_id,
math::components::Transform {
.translation = { -5.0, -5.0, 0.5 },
.scale = { 5.0, 5.0, 1.0 },
.rotation = {},
}
);
m_camera_id = m_editor_registry->create_entity();
m_editor_registry->add(
m_camera_id,
camera::components::PerspectiveCamera {
.vertical_fov = math::radians(90.0f),
.near_plane = 0.1f,
.far_plane = 30.0,
.aspect_ratio = 1.0f,
.background_color = math::vec4(1.0, 0.0, 0.0, 1.0),
.is_primary = true,
}
);
} }
void setup_input_system() void setup_input_system()
@ -278,10 +245,11 @@ private:
memory::Ref<MirrorSystem> m_mirror_system; memory::Ref<MirrorSystem> m_mirror_system;
lt::ecs::EntityId m_window = lt::ecs::null_entity; lt::ecs::EntityId m_window = lt::ecs::null_entity;
lt::ecs::EntityId m_camera_id = lt::ecs::null_entity;
lt::ecs::EntityId m_sprite_id = lt::ecs::null_entity;
}; };
auto app::create_application() -> memory::Scope<app::Application>
{
return memory::create_scope<Mirror>();
}
} // namespace lt } // namespace lt

View file

@ -1,64 +1,3 @@
#pragma once
#include <app/layer.hpp>
#include <imgui.h>
#include <math/vec2.hpp>
#include <memory/reference.hpp>
#include <mirror/panels/asset_browser.hpp>
#include <mirror/panels/properties.hpp>
#include <mirror/panels/scene_hierarchy.hpp>
#include <renderer/texture.hpp>
namespace lt {
class Scene;
class EditorLayer: public Layer
{
public:
EditorLayer(const std::string &name);
~EditorLayer() override;
EditorLayer(EditorLayer &&) = delete;
EditorLayer(const EditorLayer &) = delete;
auto operator=(EditorLayer &&) const -> EditorLayer & = delete;
auto operator=(const EditorLayer &) const -> EditorLayer & = delete;
void on_update(float delta_time) override;
void on_render() override;
void on_user_interface_update() override;
private:
std::string m_scene_dir;
math::vec2 m_direction;
float m_speed = 1000.0f;
memory::Ref<Scene> m_scene;
memory::Ref<SceneHierarchyPanel> m_sceneHierarchyPanel;
memory::Ref<PropertiesPanel> m_properties_panel;
memory::Ref<AssetBrowserPanel> m_content_browser_panel;
memory::Ref<Framebuffer> m_framebuffer;
Entity m_camera_entity;
ImVec2 m_available_content_region_prev;
};
} // namespace lt
#include <app/application.hpp> #include <app/application.hpp>
#include <asset_manager/asset_manager.hpp> #include <asset_manager/asset_manager.hpp>
#include <camera/component.hpp> #include <camera/component.hpp>

View file

@ -1,51 +1,3 @@
#pragma once
#include <filesystem>
#include <memory/reference.hpp>
#include <mirror/panels/panel.hpp>
#include <renderer/texture.hpp>
namespace lt {
class Scene;
class AssetBrowserPanel: public Panel
{
public:
AssetBrowserPanel(memory::Ref<Scene> active_scene);
void on_user_interface_update();
private:
enum class AssetType
{
none = 0,
scene,
directory,
text,
image,
};
std::filesystem::path m_current_directory;
const std::filesystem::path m_assets_path;
float m_file_size = 128.0f;
float m_file_padding = 8.0f;
memory::Ref<Scene> m_active_scene;
memory::Ref<Texture> m_directory_texture;
memory::Ref<Texture> m_scene_texture;
memory::Ref<Texture> m_image_texture;
memory::Ref<Texture> m_text_texture;
};
} // namespace lt
#include <asset_manager/asset_manager.hpp> #include <asset_manager/asset_manager.hpp>
#include <ecs/registry.hpp> #include <ecs/registry.hpp>
#include <ecs/serializer.hpp> #include <ecs/serializer.hpp>
@ -157,7 +109,7 @@ void AssetBrowserPanel::on_user_interface_update()
)) ))
{ {
auto serializer = SceneSerializer { m_active_scene }; auto serializer = SceneSerializer { m_active_scene };
log::info("Attempting to deserialize: {}", path.string()); log_inf("Attempting to deserialize: {}", path.string());
serializer.deserialize(path.string()); serializer.deserialize(path.string());
} }
break; break;

View file

@ -1,36 +1,3 @@
#pragma once
#include <ecs/entity.hpp>
#include <math/vec3.hpp>
#include <mirror/panels/panel.hpp>
namespace lt {
class PropertiesPanel: public Panel
{
public:
PropertiesPanel() = default;
void on_user_interface_update();
void set_entity_context(const Entity &entity);
private:
void draw_vec3_control(
const std::string &label,
math::vec3 &values,
float reset_value = 0.0f,
float column_width = 100.0f
);
template<typename ComponentType, typename UIFunction>
void draw_component(const std::string &name, Entity entity, UIFunction function);
Entity m_entity_context;
};
} // namespace lt
#include <asset_manager/asset_manager.hpp> #include <asset_manager/asset_manager.hpp>
#include <camera/component.hpp> #include <camera/component.hpp>
#include <ecs/components.hpp> #include <ecs/components.hpp>

View file

@ -1,42 +1,3 @@
#pragma once
#include <ecs/entity.hpp>
#include <ecs/registry.hpp>
#include <memory/reference.hpp>
#include <mirror/panels/panel.hpp>
namespace lt {
class PropertiesPanel;
class SceneHierarchyPanel: public Panel
{
public:
SceneHierarchyPanel();
SceneHierarchyPanel(
memory::Ref<Scene> context,
memory::Ref<PropertiesPanel> properties_panel = nullptr
);
void on_user_interface_update();
void set_context(
memory::Ref<Scene> context,
memory::Ref<PropertiesPanel> properties_panel = nullptr
);
private:
void draw_node(Entity entity, const std::string &label);
memory::Ref<Scene> m_context;
memory::Ref<PropertiesPanel> m_properties_panel_context;
Entity m_selection_context;
};
} // namespace lt
#include <ecs/components.hpp> #include <ecs/components.hpp>
#include <imgui.h> #include <imgui.h>
#include <memory/reference.hpp> #include <memory/reference.hpp>

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