From 103793204d3d9adf3c40e3698fa107428ce17411 Mon Sep 17 00:00:00 2001 From: "Zed A. Shaw" Date: Mon, 5 May 2025 11:42:21 -0400 Subject: [PATCH] Brought over a bunch of stuff to get started, but only lel.cpp compiles. --- .gitignore | 10 + .vimrc_proj | 1 + Makefile | 58 ++ dbc.cpp | 47 ++ dbc.hpp | 50 ++ guecs.cpp | 354 ++++++++++ guecs.hpp | 340 ++++++++++ guecstra.cpp | 18 + guecstra.hpp | 7 + lel.cpp | 117 ++++ lel.hpp | 54 ++ lel_parser.cpp | 261 ++++++++ lel_parser.rl | 66 ++ meson.build | 91 +++ scripts/coverage_report.ps1 | 13 + scripts/coverage_reset.ps1 | 7 + scripts/coverage_reset.sh | 11 + scripts/gcovr_patched_coverage.py | 1020 +++++++++++++++++++++++++++++ scripts/reset_build.ps1 | 7 + scripts/reset_build.sh | 10 + scripts/win_installer.ifp | Bin 0 -> 2117 bytes tests/guecs.cpp | 32 + tests/lel.cpp | 52 ++ wraps/catch2.wrap | 11 + wraps/flac.wrap | 13 + wraps/fmt.wrap | 13 + wraps/freetype2.wrap | 11 + wraps/libpng.wrap | 13 + wraps/nlohmann_json.wrap | 11 + wraps/ogg.wrap | 13 + wraps/sfml.wrap | 14 + wraps/tracy.wrap | 7 + wraps/vorbis.wrap | 14 + 33 files changed, 2746 insertions(+) create mode 100644 .vimrc_proj create mode 100644 Makefile create mode 100644 dbc.cpp create mode 100644 dbc.hpp create mode 100644 guecs.cpp create mode 100644 guecs.hpp create mode 100644 guecstra.cpp create mode 100644 guecstra.hpp create mode 100644 lel.cpp create mode 100644 lel.hpp create mode 100644 lel_parser.cpp create mode 100644 lel_parser.rl create mode 100644 meson.build create mode 100644 scripts/coverage_report.ps1 create mode 100644 scripts/coverage_reset.ps1 create mode 100644 scripts/coverage_reset.sh create mode 100644 scripts/gcovr_patched_coverage.py create mode 100644 scripts/reset_build.ps1 create mode 100644 scripts/reset_build.sh create mode 100644 scripts/win_installer.ifp create mode 100644 tests/guecs.cpp create mode 100644 tests/lel.cpp create mode 100644 wraps/catch2.wrap create mode 100644 wraps/flac.wrap create mode 100644 wraps/fmt.wrap create mode 100644 wraps/freetype2.wrap create mode 100644 wraps/libpng.wrap create mode 100644 wraps/nlohmann_json.wrap create mode 100644 wraps/ogg.wrap create mode 100644 wraps/sfml.wrap create mode 100644 wraps/tracy.wrap create mode 100644 wraps/vorbis.wrap diff --git a/.gitignore b/.gitignore index ec2481e..ca37570 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,13 @@ tags # Persistent undo [._]*.un~ +subprojects +builddir +ttassets +backup +*.exe +*.dll +*.world +coverage +coverage/* +.venv diff --git a/.vimrc_proj b/.vimrc_proj new file mode 100644 index 0000000..2b745b4 --- /dev/null +++ b/.vimrc_proj @@ -0,0 +1 @@ +set makeprg=meson\ compile\ -C\ . diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..fa86918 --- /dev/null +++ b/Makefile @@ -0,0 +1,58 @@ +all: build test + +reset: +ifeq '$(OS)' 'Windows_NT' + powershell -executionpolicy bypass .\scripts\reset_build.ps1 +else + sh -x ./scripts/reset_build.sh +endif + +%.cpp : %.rl + ragel -o $@ $< + +build: lel_parser.cpp + meson compile -j 10 -C builddir + +release_build: + meson --wipe builddir -Db_ndebug=true --buildtype release + meson compile -j 10 -C builddir + +debug_build: + meson setup --wipe builddir -Db_ndebug=true --buildtype debugoptimized + meson compile -j 10 -C builddir + +tracy_build: + meson setup --wipe builddir --buildtype debugoptimized -Dtracy_enable=true -Dtracy:on_demand=true + meson compile -j 10 -C builddir + +test: build + ./builddir/runtests + +run: build test +ifeq '$(OS)' 'Windows_NT' + powershell "cp ./builddir/calculator.exe ." + ./calculator +else + ./builddir/calculator +endif + +debug: build + gdb --nx -x .gdbinit --ex run --args builddir/calculator + +debug_run: build + gdb --nx -x .gdbinit --batch --ex run --ex bt --ex q --args builddir/calculator + +debug_walk: build test + gdb --nx -x .gdbinit --batch --ex run --ex bt --ex q --args builddir/calculator t + +clean: + meson compile --clean -C builddir + +debug_test: build + gdb --nx -x .gdbinit --ex run --args builddir/runtests -e + +win_installer: + powershell 'start "C:\Program Files (x86)\solicus\InstallForge\bin\ifbuilderenvx86.exe" scripts\win_installer.ifp' + +coverage_report: + powershell 'scripts/coverage_report.ps1' diff --git a/dbc.cpp b/dbc.cpp new file mode 100644 index 0000000..6b17faf --- /dev/null +++ b/dbc.cpp @@ -0,0 +1,47 @@ +#include "dbc.hpp" +#include + +void dbc::log(const string &message, const std::source_location location) { + std::cout << '[' << location.file_name() << ':' + << location.line() << "|" + << location.function_name() << "] " + << message << std::endl; +} + +void dbc::sentinel(const string &message, const std::source_location location) { + string err = fmt::format("[SENTINEL!] {}", message); + dbc::log(err, location); + throw dbc::SentinelError{err}; +} + +void dbc::pre(const string &message, bool test, const std::source_location location) { + if(!test) { + string err = fmt::format("[PRE!] {}", message); + dbc::log(err, location); + throw dbc::PreCondError{err}; + } +} + +void dbc::pre(const string &message, std::function tester, const std::source_location location) { + dbc::pre(message, tester(), location); +} + +void dbc::post(const string &message, bool test, const std::source_location location) { + if(!test) { + string err = fmt::format("[POST!] {}", message); + dbc::log(err, location); + throw dbc::PostCondError{err}; + } +} + +void dbc::post(const string &message, std::function tester, const std::source_location location) { + dbc::post(message, tester(), location); +} + +void dbc::check(bool test, const string &message, const std::source_location location) { + if(!test) { + string err = fmt::format("[CHECK!] {}\n", message); + dbc::log(err, location); + throw dbc::CheckError{err}; + } +} diff --git a/dbc.hpp b/dbc.hpp new file mode 100644 index 0000000..87e4fb0 --- /dev/null +++ b/dbc.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include +#include + +using std::string; + +namespace dbc { + class Error { + public: + const string message; + Error(string m) : message{m} {} + Error(const char *m) : message{m} {} + }; + + class CheckError : public Error {}; + class SentinelError : public Error {}; + class PreCondError : public Error {}; + class PostCondError : public Error {}; + + void log(const string &message, + const std::source_location location = + std::source_location::current()); + + [[noreturn]] void sentinel(const string &message, + const std::source_location location = + std::source_location::current()); + + void pre(const string &message, bool test, + const std::source_location location = + std::source_location::current()); + + void pre(const string &message, std::function tester, + const std::source_location location = + std::source_location::current()); + + void post(const string &message, bool test, + const std::source_location location = + std::source_location::current()); + + void post(const string &message, std::function tester, + const std::source_location location = + std::source_location::current()); + + void check(bool test, const string &message, + const std::source_location location = + std::source_location::current()); +} diff --git a/guecs.cpp b/guecs.cpp new file mode 100644 index 0000000..22dba4b --- /dev/null +++ b/guecs.cpp @@ -0,0 +1,354 @@ +#include "guecs.hpp" +#include "shaders.hpp" +#include "sound.hpp" +#include "textures.hpp" +#include + +namespace guecs { + using std::make_shared; + + void Textual::init(lel::Cell &cell, shared_ptr font_ptr) { + dbc::check(font_ptr != nullptr, "you failed to initialize this WideText"); + if(font == nullptr) font = font_ptr; + if(text == nullptr) text = make_shared(*font, content, size); + text->setFillColor(color); + + if(centered) { + auto bounds = text->getLocalBounds(); + auto text_cell = lel::center(bounds.size.x, bounds.size.y, cell); + // this stupid / 2 is because SFML renders from baseline rather than from the claimed bounding box + text->setPosition({float(text_cell.x), float(text_cell.y) - text_cell.h / 2}); + } else { + text->setPosition({float(cell.x + padding * 2), float(cell.y + padding * 2)}); + } + + text->setCharacterSize(size); + } + + void Textual::update(const wstring& new_content) { + content = new_content; + text->setString(content); + } + + void Sprite::update(const string& new_name) { + if(new_name != name) { + name = new_name; + auto sprite_texture = textures::get(name); + sprite->setTexture(*sprite_texture.texture); + sprite->setTextureRect(sprite_texture.sprite->getTextureRect()); + } + } + + void Sprite::init(lel::Cell &cell) { + auto sprite_texture = textures::get(name); + + sprite = make_shared( + *sprite_texture.texture, + sprite_texture.sprite->getTextureRect()); + + sprite->setPosition({ + float(cell.x + padding), + float(cell.y + padding)}); + + auto bounds = sprite->getLocalBounds(); + + sprite->setScale({ + float(cell.w - padding * 2) / bounds.size.x, + float(cell.h - padding * 2) / bounds.size.y}); + } + + void Rectangle::init(lel::Cell& cell) { + sf::Vector2f size{float(cell.w) - padding * 2, float(cell.h) - padding * 2}; + if(shape == nullptr) shape = make_shared(size); + shape->setPosition({float(cell.x + padding), float(cell.y + padding)}); + shape->setFillColor(color); + shape->setOutlineColor(border_color); + shape->setOutlineThickness(border_px); + } + + + void Meter::init(lel::Cell& cell) { + bar.init(cell); + } + + void Meter::render(lel::Cell& cell) { + float level = std::clamp(percent, 0.0f, 1.0f) * float(cell.w); + // ZED: this 6 is a border width, make it a thing + bar.shape->setSize({std::max(level, 0.0f), float(cell.h - 6)}); + } + + void Sound::play(bool hover) { + if(!hover) { + sound::play(on_click); + } + } + + void Sound::stop(bool hover) { + if(!hover) { + sound::stop(on_click); + } + } + + void Background::init() { + sf::Vector2f size{float(w), float(h)}; + if(shape == nullptr) shape = make_shared(size); + shape->setPosition({float(x), float(y)}); + shape->setFillColor(color); + } + + void Effect::init(lel::Cell &cell) { + $shader_version = shaders::version(); + $shader = shaders::get(name); + $shader->setUniform("u_resolution", sf::Vector2f({float(cell.w), float(cell.h)})); + $clock = std::make_shared(); + } + + void Effect::step() { + sf::Time cur_time = $clock->getElapsedTime(); + float u_time = cur_time.asSeconds(); + + if(u_time < $u_time_end) { + $shader->setUniform("u_duration", duration); + $shader->setUniform("u_time_end", $u_time_end); + $shader->setUniform("u_time", u_time); + } else { + $active = false; + } + } + + void Effect::run() { + $active = true; + sf::Time u_time = $clock->getElapsedTime(); + $u_time_end = u_time.asSeconds() + duration; + } + + shared_ptr Effect::checkout_ptr() { + if(shaders::updated($shader_version)) { + $shader = shaders::get(name); + $shader_version = shaders::version(); + } + + return $shader; + } + + void Effect::stop() { + $active = false; + } + + UI::UI() { + $font = make_shared(FONT_FILE_NAME); + } + + void UI::position(int x, int y, int width, int height) { + $parser.position(x, y, width, height); + } + + sf::Vector2f UI::get_position() { + return {float($parser.grid_x), float($parser.grid_y)}; + } + + sf::Vector2f UI::get_size() { + return {float($parser.grid_w), float($parser.grid_h)}; + } + + void UI::layout(const string& grid) { + $grid = grid; + bool good = $parser.parse($grid); + dbc::check(good, "LEL parsing failed."); + + for(auto& [name, cell] : $parser.cells) { + auto ent = init_entity(name); + set(ent, cell); + } + } + + Entity UI::init_entity(const string& name) { + auto ent = entity(); + // this lets you look up an entity by name + $name_ents.insert_or_assign(name, ent); + // this makes it easier to get the name during querying + set(ent, {name}); + return ent; + } + + Entity UI::entity(const string& name) { + dbc::check($name_ents.contains(name), + fmt::format("GUECS entity {} does not exist. Mispelled cell name?", name)); + return $name_ents.at(name); + } + + Entity UI::entity(const string& name, int id) { + return entity(fmt::format("{}{}", name, id)); + } + + void UI::init() { + query([](auto, auto& bg) { + bg.init(); + }); + + query([](auto, auto& cell, auto& rect) { + rect.init(cell); + }); + + query([](auto, auto& cell, auto& shader) { + shader.init(cell); + }); + + query([](auto, auto& bg, auto &meter) { + bg.shape->setFillColor(meter.color); + }); + + query([](auto, auto &cell, auto& meter) { + meter.init(cell); + }); + + query([this](auto, auto& cell, auto& text) { + text.init(cell, $font); + }); + + query([this](auto, auto& cell, auto& text) { + text.init(cell, $font); + }); + + query([&](auto, auto &cell, auto &sprite) { + sprite.init(cell); + }); + } + + void UI::debug_layout(sf::RenderWindow& window) { + query([&](const auto, auto &cell) { + sf::RectangleShape rect{{float(cell.w), float(cell.h)}}; + rect.setPosition({float(cell.x), float(cell.y)}); + rect.setFillColor(sf::Color::Transparent); + rect.setOutlineColor(sf::Color::Red); + rect.setOutlineThickness(2.0f); + window.draw(rect); + }); + } + + void UI::render(sf::RenderWindow& window) { + if(auto bg = get_if(MAIN)) { + window.draw(*(bg->shape)); + } + + query([&](auto, auto& shader) { + if(shader.$active) shader.step(); + }); + + query([&](auto ent, auto& rect) { + render_helper(window, ent, true, rect.shape); + }); + + query([&](auto ent, auto& cell, auto &meter) { + meter.render(cell); + render_helper(window, ent, true, meter.bar.shape); + }); + + query([&](auto ent, auto& sprite) { + render_helper(window, ent, false, sprite.sprite); + }); + + query