Compare commits

..

1 commit

Author SHA1 Message Date
4ce413a1d7
wip
Some checks are pending
continuous-integration/drone/pr Build is running
2025-10-21 15:37:33 +03:30
228 changed files with 7644 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
type: exec
name: amd64 — msvc
trigger:
branch:
- main
platform:
os: windows
arch: amd64
steps:
- name: unit tests
shell: powershell
commands:
- ./tools/ci/amd64/msvc/unit_tests.ps1
---
kind: pipeline
type: docker
name: amd64 — gcc
trigger:
branch:
- main
steps:
- name: unit tests
image: ci:latest
pull: if-not-exists
commands:
- ./tools/ci/amd64/gcc/unit_tests.sh
- name: valgrind
image: ci:latest
pull: if-not-exists
commands:
- ./tools/ci/amd64/gcc/valgrind.sh
---
# ---
# kind: pipeline
# type: exec
# name: amd64 — msvc
# trigger:
# branch:
# - main
# platform:
# os: windows
# arch: amd64
#
# steps:
# - name: unit tests
# shell: powershell
# commands:
# - ./tools/ci/amd64/msvc/unit_tests.ps1
#
# ---
# kind: pipeline
# type: docker
# name: amd64 — gcc
# trigger:
# branch:
# - main
#
# steps:
# - name: unit tests
# image: ci:latest
# pull: if-not-exists
# commands:
# - ./tools/ci/amd64/gcc/unit_tests.sh
#
# - name: valgrind
# image: ci:latest
# pull: if-not-exists
# commands:
# - ./tools/ci/amd64/gcc/valgrind.sh
#
# ---
kind: pipeline
type: docker
name: amd64 — clang
@ -45,110 +45,114 @@ trigger:
- main
steps:
- name: code coverage
image: ci:latest
pull: if-not-exists
environment:
CODECOV_TOKEN:
from_secret: CODECOV_TOKEN
commands:
- ./tools/ci/amd64/clang/coverage.sh
- name: leak sanitizer
image: ci:latest
pull: if-not-exists
commands:
- ./tools/ci/amd64/clang/lsan.sh
# - name: code coverage
# image: ci:latest
# pull: if-not-exists
# environment:
# CODECOV_TOKEN:
# from_secret: CODECOV_TOKEN
# commands:
# - ./tools/ci/amd64/clang/coverage.sh
#
# - name: leak sanitizer
# image: ci:latest
# pull: if-not-exists
# commands:
# - ./tools/ci/amd64/clang/lsan.sh
#
- name: memory sanitizer
image: ci:latest
pull: if-not-exists
commands:
- ./tools/ci/amd64/clang/msan.sh
---
kind: pipeline
type: docker
name: static analysis
trigger:
branch:
- main
steps:
- name: clang tidy
image: ci:latest
pull: if-not-exists
privileged: true
commands:
- ./tools/ci/static_analysis/clang_tidy.sh
- name: shell check
image: ci:latest
pull: if-not-exists
commands:
- ./tools/ci/static_analysis/shell_check.sh
- name: clang format
image: ci:latest
pull: if-not-exists
commands:
- ./tools/ci/static_analysis/clang_format.sh
- name: cmake format
image: ci:latest
pull: if-not-exists
commands:
- ./tools/ci/static_analysis/cmake_format.sh
- name: shell format
image: ci:latest
pull: if-not-exists
commands:
- ./tools/ci/static_analysis/shell_format.sh
---
kind: pipeline
type: docker
name: documentation — development
node:
environment: ryali
trigger:
branch:
- main
steps:
- name: build and deploy
image: documentation:latest
pull: if-not-exists
commands:
- cd docs
- sphinx-build -M html . .
- rm -rf /light_docs_dev/*
- mv ./html/* /light_docs_dev/
---
kind: pipeline
type: docker
name: documentation — production
node:
environment: ryali
trigger:
event:
- tag
steps:
- name: build and deploy
image: documentation:latest
pull: if-not-exists
commands:
- cd docs
- mkdir generated
- touch generated/changelogs.rst
- touch generated/api.rst
- sphinx-build -M html . .
- rm -rf /light_docs/*
- mv ./html/* /light_docs/
#
# ---
# kind: pipeline
# type: docker
# name: static analysis
# trigger:
# branch:
# - main
#
# steps:
# - name: clang tidy
# image: ci:latest
# pull: if-not-exists
# privileged: true
# commands:
# - ./tools/ci/static_analysis/clang_tidy.sh
#
# - name: shell check
# image: ci:latest
# pull: if-not-exists
# commands:
# - ./tools/ci/static_analysis/shell_check.sh
#
# - name: clang format
# image: ci:latest
# pull: if-not-exists
# commands:
# - ./tools/ci/static_analysis/clang_format.sh
#
# - name: cmake format
# image: ci:latest
# pull: if-not-exists
# commands:
# - ./tools/ci/static_analysis/cmake_format.sh
#
# - name: shell format
# image: ci:latest
# pull: if-not-exists
# commands:
# - ./tools/ci/static_analysis/shell_format.sh
#
# ---
# kind: pipeline
# type: docker
# name: documentation — development
# node:
# environment: ryali
# trigger:
# branch:
# - main
#
# steps:
# - name: build and deploy
# image: documentation:latest
# pull: if-not-exists
# commands:
# - pwd
# - cd docs
# - mkdir generated
# - touch generated/changelogs.rst
# - touch generated/api.rst
# - sphinx-build -M html . .
#
# - rm -rf /light_docs_dev/*
# - mv ./html/* /light_docs_dev/
#
# ---
#
# kind: pipeline
# type: docker
# name: documentation — production
# node:
# environment: ryali
# trigger:
# event:
# - tag
#
# steps:
# - name: build and deploy
# image: documentation:latest
# pull: if-not-exists
# commands:
# - cd docs
# - mkdir generated
# - touch generated/changelogs.rst
# - 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)
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)
cmake_minimum_required(VERSION 3.14)
project(Light)
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
layout(push_constant ) uniform pc {
mat4 view_projection;
};
vec3 positions[3] = vec3[](
vec3(0.0, -0.5, 0.5),
vec3(0.5, 0.5, 0.5),
vec3(-0.5, 0.5, 0.5)
vec2 positions[3] = vec2[](
vec2(0.0, -0.5),
vec2(0.5, 0.5),
vec2(-0.5, 0.5)
);
vec3 colors[3] = vec3[](
vec3(0.0, 0.0, 0.0),
vec3(0.0, 0.0, 0.0),
vec3(0.0, 0.0, 0.0)
vec3(1.0, 0.0, 0.0),
vec3(0.0, 1.0, 0.0),
vec3(0.0, 0.0, 1.0)
);
layout(location = 0) out vec3 out_frag_color;
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];
}

Binary file not shown.

3
docs/.gitignore vendored
View file

@ -1,4 +1,3 @@
_build/
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 ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
extensions = ['breathe']
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'
extensions = []
templates_path = ['_templates']
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# -- 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
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**.
- A **pipeline** for making changes ---should **minimize ambiguity**.
- A way for **distributing work** and **tracking productivity**.
Such a plan should:
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::
**Forgejo issues** is used as the project-management tool.
No need for fancy boards or 3rd party apps. Simple tags, titles, milestones, etc... should suffice.
These are the **management** aspects of the project, which help the development goals to be more **pragmatic**
---by pulling my mind out of its **engineering dreamland**, and make it focus on the **broader picture**.
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
- Write the commit title.
- Write the commit message.
- 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**.
- Make an issue.
- Git is a **version-control** tool, not a **project-management** tool.
- Preferably, provide a very brief description ---This may be used in the commit message's body.
- Make a ticket.
- Version control (git) is a **development-tool**, not a **management-tool**.
- Provide a very brief description ---This may be used in the commit message's body.
2 - Make it work
- Write high-level tests that confirms the cycle's requirements are met.
- That is, specify requirements in a programming language instead of English.
- You're done when all the tests pass.
- 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 **best practices** and **proven swe principles**.
- Enable **warnings as errors**.
- Enable **static analysis**.
- Don't break existing tests.
- Have the overall picture in mind.
- Don't break any pre-existing-tests.
- Have the over-all picture in mind.
3 - Make it right
- 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.
- Avoid premature optimizations, be certain what you change has performance benefits.
Sprint
---------------------------------------------------------------------------------------------------
A sprint is the collection of all the finished cycles in **one week**.
They start from **Monday mornings**, and end on **Sunday nights**,
where we'll do a **12hr coding marathon** (streamed on Discord) to wrap everything up.
A sprint is the collection of all the finished cycles in one week.
It's meant to provide insight on development speed and help projecting the future.
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
---------------------------------------------------------------------------------------------------
@ -115,10 +116,6 @@ With the following commit types:
- For changes to the documentations.
- Does not affect the version.
.. note::
Since we're in beta, any commit might change the api, no need for ! (breaking) tags.
Semantic Versioning
---------------------------------------------------------------------------------------------------
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:
``git rev-parse --short=5 <commit_hexsha>``

View file

@ -23,10 +23,10 @@
guidelines/conventions.rst
.. toctree::
:maxdepth: 3
:caption: API
:maxdepth: 2
:caption: Generated Docs
api/app.rst
api/renderer.rst
generated/api.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)
add_module(NAME bitwise INTERFACES operations.cppm)
add_module(NAME env INTERFACES constants.cppm)
add_module(NAME memory INTERFACES null_on_move.cppm reference.cppm scope.cppm)
add_module(NAME time INTERFACES timer.cppm TESTS timer.test.cpp)
# engine
add_subdirectory(./std)
add_subdirectory(./bitwise)
add_subdirectory(./env)
add_subdirectory(./memory)
add_subdirectory(./time)
add_subdirectory(./logger)
add_subdirectory(./debug)
add_subdirectory(./math)
#
add_subdirectory(./asset_baker)
add_subdirectory(./assets)
#
add_subdirectory(./camera)
add_subdirectory(./input)
# add_subdirectory(./ui)
#
add_subdirectory(./surface)
add_subdirectory(./renderer)
add_subdirectory(./ecs)
#
add_subdirectory(./app)
add_module(
NAME
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)
# apps
add_subdirectory(./mirror)
add_subdirectory(test)

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;
import logger;
import std;
#pragma once
#include <chrono>
namespace lt::app {
/** Information required to tick a system.
* @note May be used across an entire application-frame (consisting of multiple systems ticking)
*/
export struct TickInfo
struct TickInfo
{
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 */
export struct TickResult
struct TickResult
{
using Timepoint_T = std::chrono::time_point<std::chrono::steady_clock>;
@ -46,9 +46,10 @@ export struct TickResult
Timepoint_T end_time;
};
export struct SystemDiagnosis
struct SystemDiagnosis
{
enum class Severity : std::uint8_t
enum class Severity : uint8_t
{
verbose,
info,
@ -64,14 +65,14 @@ export struct SystemDiagnosis
Severity severity;
};
export class SystemStats
class SystemStats
{
public:
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
@ -83,7 +84,7 @@ private:
std::vector<SystemDiagnosis> m_diagnosis;
};
export class ISystem
class ISystem
{
public:
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;
import logger;
import bakers;
import std;
#include <asset_baker/bakers.hpp>
#include <assets/shader.hpp>
auto main(int argc, char *argv[]) -> std::int32_t
auto main(int argc, char *argv[]) -> int32_t
try
{
if (argc != 2)
@ -33,12 +30,12 @@ try
}
}
return 0;
return EXIT_SUCCESS;
}
catch (const std::exception &exp)
{
lt::log::critical("Terminating due to uncaught exception:");
lt::log::critical("\texception.what: {}:", exp.what());
log_crt("Terminating due to uncaught exception:");
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;
import assets.metadata;
import assets.shader;
import logger;
import std;
#include <assets/shader.hpp>
export void bake_shader(
inline void bake_shader(
const std::filesystem::path &in_path,
const std::filesystem::path &out_path,
lt::assets::ShaderAsset::Type type
@ -15,27 +11,29 @@ export void bake_shader(
using lt::assets::ShaderAsset;
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);
lt::log::trace(
log_trc(
"Compiling {} shader {} -> {}",
type == vertex ? "vertex" : "fragment",
std::string { glsl_path },
std::string { spv_path }
glsl_path,
spv_path
);
// Don't bother linking to shaderc, just invoke the command with a system call.
// NOLINTNEXTLINE(concurrency-mt-unsafe)
std::system(std::format(
"glslc --target-env=vulkan1.4 -std=450core -fshader-stage={} {} -o {}",
type == vertex ? "vert" : "frag",
glsl_path,
spv_path
)
.c_str());
system(
std::format(
"glslc --target-env=vulkan1.4 -std=450core -fshader-stage={} {} -o {}",
type == vertex ? "vert" : "frag",
glsl_path,
spv_path
)
.c_str()
);
auto stream = std::ifstream(spv_path, std::ios::binary);
lt::debug::ensure(
lt::ensure(
stream.is_open(),
"Failed to open compiled {} shader at: {}",
type == vertex ? "vert" : "frag",
@ -48,7 +46,7 @@ export void bake_shader(
auto bytes = std::vector<std::byte>(size);
stream.seekg(0, std::ios::beg);
stream.read((char *)bytes.data(), size); // NOLINT
lt::log::debug("BYTES: {}", bytes.size());
log_dbg("BYTES: {}", bytes.size());
stream.close();
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;
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
#include <assets/shader.hpp>
namespace lt::assets {
@ -90,14 +14,14 @@ constexpr auto total_metadata_size = //
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) {
m_stream.read(std::bit_cast<char *>(&field), sizeof(field));
};
m_stream.seekg(0, std::ifstream::end);
const auto file_size = static_cast<std::size_t>(m_stream.tellg());
debug::ensure(
const auto file_size = static_cast<size_t>(m_stream.tellg());
ensure(
file_size > total_metadata_size,
"Failed to open shader asset at: {}, file smaller than metadata: {} < {}",
path.string(),
@ -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.uncompressed_size);
debug::ensure(
ensure(
m_asset_metadata.type == asset_type_identifier,
"Failed to open shader asset at: {}, incorrect asset type: {} != {}",
path.string(),
@ -123,7 +47,7 @@ ShaderAsset::ShaderAsset(const std::filesystem::path &path): m_stream(path)
asset_type_identifier
);
debug::ensure(
ensure(
m_asset_metadata.version == current_version,
"Failed to open shader asset at: {}, version mismatch: {} != {}",
path.string(),
@ -131,21 +55,21 @@ ShaderAsset::ShaderAsset(const std::filesystem::path &path): m_stream(path)
current_version
);
debug::ensure(
ensure(
std::to_underlying(m_metadata.type) <= std::to_underlying(Type::compute),
"Failed to open shader asset at: {}, invalid shader type: {}",
path.string(),
std::to_underlying(m_metadata.type)
);
debug::ensure(
ensure(
m_code_blob_metadata.tag == std::to_underlying(BlobTag::code),
"Failed to open shader asset at: {}, invalid blob tag: {}",
path.string(),
m_code_blob_metadata.tag
);
debug::ensure(
ensure(
m_code_blob_metadata.offset + m_code_blob_metadata.compressed_size <= file_size,
"Failed to open shader asset at: {}, file smaller than blob: {} > {} + {}",
path.string(),
@ -175,7 +99,7 @@ ShaderAsset::ShaderAsset(const std::filesystem::path &path): m_stream(path)
.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) {
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
{
debug::ensure(
tag == BlobTag::code,
"Invalid blob tag for shader asset: {}",
std::to_underlying(tag)
);
ensure(tag == BlobTag::code, "Invalid blob tag for shader asset: {}", std::to_underlying(tag));
debug::ensure(
ensure(
destination.size() >= m_code_blob_metadata.uncompressed_size,
"Failed to unpack shader blob {} to destination ({}) of size {} since it's smaller "
"than the blobl's uncompressed size: {}",
std::to_underlying(tag),
std::bit_cast<std::size_t>(destination.data()),
std::bit_cast<size_t>(destination.data()),
destination.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
{
debug::ensure(
tag == BlobTag::code,
"Invalid blob tag for shader asset: {}",
std::to_underlying(tag)
);
ensure(tag == BlobTag::code, "Invalid blob tag for shader asset: {}", std::to_underlying(tag));
auto blob = Blob(m_code_blob_metadata.uncompressed_size);
unpack_to(tag, blob);

View file

@ -1,8 +1,6 @@
import assets.metadata;
import assets.shader;
import test.test;
import test.expects;
import std;
#include <assets/shader.hpp>
#include <ranges>
#include <test/test.hpp>
using ::lt::assets::AssetMetadata;
using ::lt::assets::BlobMetadata;
@ -12,7 +10,6 @@ using ::lt::test::expect_eq;
using ::lt::test::expect_throw;
using ::lt::test::expect_true;
using ::lt::test::Suite;
using ::lt::test::operator""_suite;
const auto test_data_path = std::filesystem::path { "./data/test_assets" };
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());
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);
stream.close();

View file

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

View file

@ -1,19 +1,18 @@
export module assets.metadata;
import std;
#pragma once
export namespace lt::assets {
namespace lt::assets {
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>;
constexpr auto current_version = Version { 1u };
enum class CompressionType : std::uint8_t
enum class CompressionType : uint8_t
{
none,
lz4,
@ -31,13 +30,13 @@ struct BlobMetadata
{
Tag_T tag;
std::size_t offset;
size_t offset;
CompressionType compression_type;
std::size_t compressed_size;
size_t compressed_size;
std::size_t uncompressed_size;
size_t uncompressed_size;
};
} // namespace lt::assets

View file

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

View file

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

View file

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

View file

@ -1,20 +1,19 @@
export module ecs.entity;
import debug.assertions;
import memory.reference;
import ecs.registry;
import std;
#pragma once
#include <ecs/registry.hpp>
#include <memory/reference.hpp>
namespace lt::ecs {
/** High-level entity convenience wrapper */
export class Entity
class Entity
{
public:
Entity(memory::Ref<Registry> registry, EntityId identifier)
: m_registry(std::move(registry))
, 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>

View file

@ -1,14 +1,13 @@
export module ecs.registry;
import debug.assertions;
import ecs.sparse_set;
import memory.scope;
import std;
#pragma once
#include <ecs/sparse_set.hpp>
#include <memory/scope.hpp>
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.
*
@ -23,7 +22,7 @@ export constexpr auto null_entity = std::numeric_limits<EntityId>::max();
* @ref https://github.com/skypjack/entt
* @ref https://github.com/SanderMertens/flecs
*/
export class Registry
class Registry
{
public:
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:
using TypeId = std::size_t;
using TypeId = size_t;
static consteval auto hash_cstr(const char *str) -> TypeId
{
constexpr auto fnv_offset_basis = std::size_t { 14695981039346656037ull };
constexpr auto fnv_prime = std::size_t { 1099511628211ull };
constexpr auto fnv_offset_basis = size_t { 14695981039346656037ull };
constexpr auto fnv_prime = size_t { 1099511628211ull };
auto hash = fnv_offset_basis;
for (const auto &ch : std::string_view { str })
{
hash *= fnv_prime;
hash ^= static_cast<std::uint8_t>(ch);
hash ^= static_cast<uint8_t>(ch);
}
return hash;
@ -242,7 +241,7 @@ private:
auto *base_set = m_sparsed_sets[type_id].get();
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;
}
@ -251,16 +250,11 @@ private:
TypeId m_entity_count;
/** MSVC DOES NOT SUPPORT FLAT MAP!!
* IT"S YEAR ~2026, great...
* using ::std::map for the time being.
*/
std::flat_map<TypeId, memory::Scope<UnderlyingSparseSet_T>> m_sparsed_sets;
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::map<TypeId, Callback_T> m_on_destruct_hooks;
std::flat_map<TypeId, Callback_T> m_on_destruct_hooks;
};
} // namespace lt::ecs

View file

@ -1,13 +1,12 @@
export module ecs.sparse_set;
import debug.assertions;
import std;
#pragma once
namespace lt::ecs {
/**
*
* @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
{
public:
@ -26,19 +25,19 @@ public:
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>
{
public:
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();
explicit SparseSet(std::size_t initial_capacity = 1)
explicit SparseSet(size_t initial_capacity = 1)
{
debug::ensure(
ensure(
initial_capacity <= max_capacity,
"Failed to create SparseSet: capacity too large ({} > {})",
initial_capacity,
@ -53,16 +52,13 @@ public:
{
if (m_sparse.size() < identifier + 1)
{
auto new_capacity = std::max(
static_cast<std::size_t>(identifier + 1),
m_sparse.size() * 2
);
auto new_capacity = std::max(static_cast<size_t>(identifier + 1), m_sparse.size() * 2);
new_capacity = std::min(new_capacity, max_capacity);
// log::debug("Increasing sparse vector size:", m_dead_count);
// log::debug("\tdead_count: {}", m_dead_count);
// log::debug("\talive_count: {}", m_alive_count);
// log::debug("\tsparse.size: {} -> {}", m_sparse.size(), new_capacity);
// log_dbg("Increasing sparse vector size:", m_dead_count);
// log_dbg("\tdead_count: {}", m_dead_count);
// log_dbg("\talive_count: {}", m_alive_count);
// log_dbg("\tsparse.size: {} -> {}", m_sparse.size(), new_capacity);
m_sparse.resize(new_capacity, null_identifier);
}
@ -149,12 +145,12 @@ public:
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;
}
[[nodiscard]] auto get_capacity() const noexcept -> std::size_t
[[nodiscard]] auto get_capacity() const noexcept -> size_t
{
return m_sparse.capacity();
}
@ -169,9 +165,9 @@ private:
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

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;
import std;
#pragma once
namespace lt {
enum class Platform : std::uint8_t
enum class Platform : uint8_t
{
/** The GNU/Linux platform.
* 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. */
enum class Compiler : std::uint8_t
enum class Compiler : uint8_t
{
clang,
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;
export import :components;
import logger;
import app.system;
import debug.assertions;
import ecs.registry;
import memory.reference;
import surface.system;
import surface.events;
import math.vec2;
import std;
#include <input/components.hpp>
#include <input/system.hpp>
#include <memory/reference.hpp>
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>
struct overloads: Ts...
{
@ -69,7 +12,7 @@ struct overloads: Ts...
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)
@ -90,7 +33,7 @@ void System::tick(app::TickInfo tick)
// instead of brute-force checking all of them.
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 (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)
{
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 "
"key-code-mapping!");
log_dbg(
"Key code larger than key container size, implement platform-dependant "
"key-code-mapping!"
);
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)
{
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 "
"key-code-mapping!");
log_dbg(
"Key code larger than key container size, implement platform-dependant "
"key-code-mapping!"
);
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)
@ -187,12 +134,12 @@ void System::on_pointer_move(const lt::surface::MouseMovedEvent &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)
{
m_buttons[std::to_underlying(event.get_button())] = false;
m_buttons[event.get_button()] = false;
}
} // namespace lt::input

View file

@ -1,17 +1,11 @@
import std;
import input.system;
import input.codes;
import std;
import test.test;
import test.expects;
import surface.events;
import memory.scope;
import memory.reference;
import app.system;
import ecs.entity;
import ecs.registry;
import surface.system;
#include <ecs/entity.hpp>
#include <input/components.hpp>
#include <input/system.hpp>
#include <memory/reference.hpp>
#include <memory/scope.hpp>
#include <ranges>
#include <surface/system.hpp>
#include <test/test.hpp>
// NOLINTBEGIN
using namespace lt;
@ -23,7 +17,6 @@ using test::expect_eq;
using test::expect_false;
using test::expect_ne;
using test::expect_not_nullptr;
using test::operator""_suite;
using test::expect_throw;
using test::Suite;
// NOLINTEND
@ -162,7 +155,7 @@ Suite tick = "tick"_suite = [] {
auto action_key = input.add_action(
{
.name { "test" },
.trigger = { .mapped_keycode = Key::A },
.trigger = { .mapped_keycode = 69 },
}
);
@ -170,7 +163,7 @@ Suite tick = "tick"_suite = [] {
system.tick(tick_info());
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());
expect_eq(input.get_action(action_key).state, input::InputAction::State::triggered);
@ -182,7 +175,7 @@ Suite tick = "tick"_suite = [] {
system.tick(tick_info());
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());
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(
{
.name { "test" },
.trigger = { .mapped_keycode = Key::A },
.trigger = { .mapped_keycode = 69 },
}
);
};

View file

@ -1,17 +1,19 @@
export module input.system:components;
import input.codes;
import std;
#pragma once
#include <vector>
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,
active,
@ -26,18 +28,18 @@ export struct InputAction
Trigger trigger;
};
export class InputComponent
class InputComponent
{
public:
InputComponent() = default;
auto add_action(InputAction action) -> std::size_t
auto add_action(InputAction action) -> size_t
{
m_actions.emplace_back(std::move(action));
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];
}

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

View file

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

View file

@ -0,0 +1,55 @@
#pragma once
#include <app/system.hpp>
#include <ecs/registry.hpp>
#include <memory/reference.hpp>
#include <surface/components.hpp>
#include <surface/events/keyboard.hpp>
#include <surface/events/mouse.hpp>
namespace lt::input {
class System: public app::ISystem
{
public:
System(memory::Ref<ecs::Registry> registry);
void tick(app::TickInfo tick) override;
void on_register() override;
void on_unregister() override;
[[nodiscard]] auto get_last_tick_result() const -> const app::TickResult & override
{
return m_last_tick_result;
}
private:
void handle_event(const surface::SurfaceComponent::Event &event);
void on_surface_lost_focus();
void on_key_press(const lt::surface::KeyPressedEvent &event);
void on_key_release(const lt::surface::KeyReleasedEvent &event);
void on_pointer_move(const lt::surface::MouseMovedEvent &event);
void on_button_press(const lt::surface::ButtonPressedEvent &event);
void on_button_release(const lt::surface::ButtonReleasedEvent &event);
memory::Ref<ecs::Registry> m_registry;
std::array<bool, 512> m_keys {};
std::array<bool, 512> m_buttons {};
math::vec2 m_pointer_position;
app::TickResult m_last_tick_result {};
};
} // namespace lt::input

View file

@ -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;
import math.mat4;
import std;
#pragma once
export namespace lt::math {
#include <math/mat4.hpp>
namespace lt::math {
/**
* 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.
*
* @ref Thanks to pikuma for explaining the math behind this:
* https://www.youtube.com/watch?v=EqNcqBdrNyI
* thanks to pikuma: https://www.youtube.com/watch?v=EqNcqBdrNyI
*/
template<typename T>
constexpr auto perspective(T field_of_view, T aspect_ratio, T z_near, T z_far)
{
const T half_fov_tan = std::tan(field_of_view / static_cast<T>(2));
auto result = mat4_impl<T>::identity();
auto result = mat4_impl<T> { T { 0 } };
result[0][0] = T { 1 } / (aspect_ratio * half_fov_tan);
//
result[1][1] = T { 1 } / (half_fov_tan);
//
// result[2][2] = -(z_far + z_near) / (z_far - z_near);
//
result[2][2] = z_far / (z_far - z_near);
//
result[2][2] = -(z_far + z_near) / (z_far - z_near);
result[2][3] = -T { 1 };
//
// result[3][2] = -(T { 2 } * z_far * z_near) / (z_far - z_near);
result[3][2] = -(z_far * z_near) / (z_far - z_near);
//
result[3][2] = -(T { 2 } * z_far * z_near) / (z_far - z_near);
return result;
}

View file

@ -1,15 +1,14 @@
export module math.mat4;
import math.vec3;
import math.vec4;
import std;
#pragma once
#include <math/vec3.hpp>
#include <math/vec4.hpp>
namespace lt::math {
export template<typename T = float>
template<typename T = float>
struct mat4_impl
{
using Column_T = vec4_impl<T>;
constexpr explicit mat4_impl(T scalar = 0)
: 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> {
{ 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];
}
[[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];
}
@ -77,34 +76,34 @@ struct mat4_impl
std::array<Column_T, 4> values; // NOLINT
};
export template<typename T>
[[nodiscard]] auto translate(const vec3_impl<T> &value) -> mat4_impl<T>
template<typename T>
[[nodiscard]] inline auto translate(const vec3_impl<T> &value) -> mat4_impl<T>
{
return mat4_impl<T> {};
}
export template<typename T>
[[nodiscard]] auto rotate(float value, const vec3_impl<T> &xyz) -> mat4_impl<T>
template<typename T>
[[nodiscard]] inline auto rotate(float value, const vec3_impl<T> &xyz) -> mat4_impl<T>
{
return mat4_impl<T> {};
}
export template<typename T>
[[nodiscard]] auto scale(const vec3_impl<T> &value) -> mat4_impl<T>
template<typename T>
[[nodiscard]] inline auto scale(const vec3_impl<T> &value) -> mat4_impl<T>
{
return mat4_impl<T> {};
}
export template<typename T>
[[nodiscard]] auto inverse(const mat4_impl<T> &value) -> mat4_impl<T>
template<typename T>
[[nodiscard]] inline auto inverse(const mat4_impl<T> &value) -> 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

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
{

View file

@ -1,10 +1,8 @@
export module math.vec2;
import std;
#pragma once
namespace lt::math {
export template<typename T = float>
template<typename T = float>
struct vec2_impl
{
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
export template<typename T>
template<typename T>
struct std::formatter<lt::math::vec2_impl<T>>
{
constexpr auto parse(std::format_parse_context &context)

View file

@ -1,11 +1,11 @@
export module math.vec3;
#pragma once
import math.vec2;
import std;
#include <cstdint>
#include <math/vec2.hpp>
namespace lt::math {
export template<typename T = float>
template<typename T = float>
struct vec3_impl
{
constexpr vec3_impl(): x(), y(), z()
@ -61,11 +61,11 @@ struct vec3_impl
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

View file

@ -1,11 +1,11 @@
export module math.vec4;
import math.vec2;
import math.vec3;
import std;
#pragma once
#include <array>
#include <cstdint>
namespace lt::math {
export template<typename T = float>
template<typename T = float>
struct vec4_impl
{
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];
}
[[nodiscard]] constexpr auto operator[](std::size_t idx) const -> const T &
[[nodiscard]] constexpr auto operator[](size_t idx) const -> const T &
{
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
export template<typename T>
template<typename T>
struct std::formatter<lt::math::vec4_impl<T>>
{
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;
import std;
#pragma once
namespace lt::memory {
/** Holds an `Underlying_T`, assigns it to `null_value` when this object is moved.
*
* @note For avoiding the need to explicitly implement the move constructor for objects that hold
* Vulkan 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
{
public:
@ -79,17 +77,12 @@ public:
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 &
{
return m_value;
}
[[nodiscard]] auto get() const -> const Underlying_T &
[[nodiscard]] auto get() -> Underlying_T
{
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 {
@ -9,21 +10,21 @@ namespace lt::memory {
* @note Currently just an alias, might turn into an implementation later.
* @ref https://en.cppreference.com/w/cpp/memory/shared_ptr.html
*/
export template<typename T>
using Ref = std::shared_ptr<T>;
template<typename t>
using Ref = std::shared_ptr<t>;
/** Allocates memory for an `Underlying_T` and directly constructs it there.
*
* @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)
{
return std::make_shared<Underlying_T>(std::forward<Args>(args)...);
}
/** 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)
{
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 {
/** @brief Wrapper around std::unique_ptr.
/** Wrapper around std::unique_ptr.
*
* @note Currently just an alias, might turn into an implementation later.
* @ref https://en.cppreference.com/w/cpp/memory/unique_ptr.html
*/
export template<typename t>
template<typename t>
using Scope = std::unique_ptr<t>;
/** Allocates memory for an `Underlying_T` and directly constructs it there.
*
* @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)
{
return std::make_unique<Underlying_T>(std::forward<Args>(args)...);
}
/** 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)
{
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;
import math.vec3;
import camera.components;
import surface.requests;
import logger;
import surface.system;
import math.vec2;
import math.vec4;
import math.trig;
import input.codes;
import input.events;
import input.system;
import math.components;
import memory.reference;
import memory.scope;
import renderer.components;
import renderer.system;
import renderer.frontend;
import surface.events;
import time;
import app;
import app.system;
import ecs.entity;
import ecs.registry;
import std;
#include <X11/keysym.h>
#include <app/application.hpp>
#include <app/entrypoint.hpp>
#include <app/system.hpp>
#include <ecs/entity.hpp>
#include <input/components.hpp>
#include <input/system.hpp>
#include <math/vec2.hpp>
#include <memory/reference.hpp>
#include <memory/scope.hpp>
#include <renderer/components/messenger.hpp>
#include <renderer/system.hpp>
#include <surface/events/keyboard.hpp>
#include <surface/events/surface.hpp>
#include <surface/system.hpp>
#include <time/timer.hpp>
namespace lt {
void renderer_callback(
renderer::IDebugger::MessageSeverity message_severity,
renderer::IDebugger::MessageType message_type,
renderer::IDebugger::MessageData data,
renderer::IMessenger::MessageSeverity message_severity,
renderer::IMessenger::MessageType message_type,
renderer::IMessenger::MessageData data,
std::any &user_data
)
{
@ -37,7 +28,7 @@ void renderer_callback(
std::ignore = message_type;
std::ignore = user_data;
log::debug("RENDERER CALLBACK: {}", std::string { data.message });
log_dbg("RENDERER CALLBACK: {}", data.message);
}
class MirrorSystem: public lt::app::ISystem
@ -45,8 +36,8 @@ class MirrorSystem: public lt::app::ISystem
public:
MirrorSystem(
memory::Ref<ecs::Registry> registry,
std::size_t quit_action_key,
std::array<std::size_t, 4ul> debug_action_keys
lt::input::InputAction::Key quit_action_key,
std::array<lt::input::InputAction::Key, 4> debug_action_keys
)
: m_registry(std::move(registry))
, m_quit_action_key(quit_action_key)
@ -81,11 +72,7 @@ public:
}
if (input.get_action(m_debug_action_keys[0]).state == State::active)
{
for (auto &[id, camera] :
m_registry->view<lt::camera::components::PerspectiveCamera>())
{
camera.vertical_fov += (static_cast<float>(tick.delta_time.count()) * 40.0f);
}
surface.push_request(surface::ModifyPositionRequest({ x + 5, y + 5 }));
}
if (input.get_action(m_debug_action_keys[1]).state == State::active)
@ -128,14 +115,15 @@ public:
private:
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 {};
};
export class Mirror: public app::Application
class Mirror: public app::Application
{
public:
Mirror()
@ -150,7 +138,7 @@ public:
void on_window_close()
{
log::info("Window close requested...");
log_inf("Window close requested...");
unregister_system(m_input_system);
unregister_system(m_surface_system);
@ -176,31 +164,41 @@ public:
);
auto &input = m_editor_registry->add<InputComponent>(m_window, {});
auto quit_action_key = input.add_action(input::InputAction {
.name = "quit",
.trigger = input::Trigger { .mapped_keycode = Key::Q },
});
auto quit_action_key = input.add_action(
input::InputAction {
.name = "quit",
.trigger = input::Trigger { .mapped_keycode = XK_q },
}
);
auto debug_action_keys = std::array<std::size_t, 4ul> {};
debug_action_keys[0] = input.add_action(input::InputAction {
.name = "debug_1",
.trigger = input::Trigger { .mapped_keycode = Key::D1 },
});
auto debug_action_keys = std::array<lt::input::InputAction::Key, 4> {};
debug_action_keys[0] = input.add_action(
input::InputAction {
.name = "debug_1",
.trigger = input::Trigger { .mapped_keycode = XK_1 },
}
);
debug_action_keys[1] = input.add_action(input::InputAction {
.name = "debug_2",
.trigger = input::Trigger { .mapped_keycode = Key::D2 },
});
debug_action_keys[1] = input.add_action(
input::InputAction {
.name = "debug_2",
.trigger = input::Trigger { .mapped_keycode = XK_2 },
}
);
debug_action_keys[2] = input.add_action(input::InputAction {
.name = "debug_3",
.trigger = input::Trigger { .mapped_keycode = Key::D3 },
});
debug_action_keys[2] = input.add_action(
input::InputAction {
.name = "debug_3",
.trigger = input::Trigger { .mapped_keycode = XK_3 },
}
);
debug_action_keys[3] = input.add_action(input::InputAction {
.name = "debug_4",
.trigger = input::Trigger { .mapped_keycode = Key::D4 },
});
debug_action_keys[3] = input.add_action(
input::InputAction {
.name = "debug_4",
.trigger = input::Trigger { .mapped_keycode = XK_4 },
}
);
m_input_system = memory::create_ref<input::System>(m_editor_registry);
m_mirror_system = memory::create_ref<MirrorSystem>(
@ -215,43 +213,12 @@ public:
.config = { .target_api = renderer::Api::vulkan, .max_frames_in_flight = 3u },
.registry = m_editor_registry,
.surface_entity = entity,
.debug_callback_info = renderer::IDebugger::CreateInfo {
.severities = renderer::IDebugger::MessageSeverity::all,
.types = renderer::IDebugger::MessageType::all,
.debug_callback_info = renderer::IMessenger::CreateInfo {
.severities = renderer::IMessenger::MessageSeverity::all,
.types = renderer::IMessenger::MessageType::all,
.callback = &renderer_callback,
.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()
@ -278,10 +245,11 @@ private:
memory::Ref<MirrorSystem> m_mirror_system;
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

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 <asset_manager/asset_manager.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 <ecs/registry.hpp>
#include <ecs/serializer.hpp>
@ -157,7 +109,7 @@ void AssetBrowserPanel::on_user_interface_update()
))
{
auto serializer = SceneSerializer { m_active_scene };
log::info("Attempting to deserialize: {}", path.string());
log_inf("Attempting to deserialize: {}", path.string());
serializer.deserialize(path.string());
}
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 <camera/component.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 <imgui.h>
#include <memory/reference.hpp>

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