sdl_runtime_compiler git · main
SDL3 game for running and compiling code at runtime
C++ 72.3% C 26.2%git clone https://git.christianimmanuel.de/sdl-graphics/sdl_runtime_compiler.gitwget https://git.christianimmanuel.de/sdl-graphics/sdl_runtime_compiler/archive/sdl_runtime_compiler.tar.gzCleanup
info.txt | 3 +
src/states/game_state.cpp | 141 +++++++++++----------
src/states/game_state.hpp | 2 +-
src/systems/cpp_highlight_system.hpp | 1 -
.../{word_anim.cpp => word_anim_system.cpp} | 2 +-
.../{word_anim.hpp => word_anim_system.hpp} | 0
src/text/handler.cpp | 88 ++++++++++---
src/text/handler.hpp | 53 ++++----
src/text/text_mesh.cpp | 99 ++++++++++-----
src/text/token.cpp | 1 +
src/text/token.hpp | 14 ++
11 files changed, 254 insertions(+), 150 deletions(-)
diff --git a/info.txt b/info.txt
index 9d0f111..69e2615 100644
--- a/info.txt
+++ b/info.txt
@@ -1,4 +1,7 @@
+i need to store the big info text meshes so displaying them is not that expensive.
+
+---------------------------------
Text::Handler
- handles tokens and lines as one commplete text structure so it can easily get tokenized from tree-sitter
diff --git a/src/states/game_state.cpp b/src/states/game_state.cpp
index bfcc1da..66ab04f 100644
--- a/src/states/game_state.cpp
+++ b/src/states/game_state.cpp
@@ -309,15 +309,22 @@ GameState::on_ShootAll()
all_words.push_back(std::move(node->word));
_carried = CarriedLine();
+ Aim::Result aim;
if (!all_words.empty()) {
- Aim::Result aim = Aim::crosshair_Target(*_ctx.camera, _ctx.words, _ctx.scenery,
- _text_walls, World::get_BoxScale());
- _projectiles_sys->launch(_projectiles, std::move(all_words),
- _ctx.camera->fwd, _ctx.camera->pos, aim);
+ { FP_ZONE("dispatch.ShootAll.aim");
+ aim = Aim::crosshair_Target(*_ctx.camera, _ctx.words, _ctx.scenery,
+ _text_walls, World::get_BoxScale());
+ }
+ { FP_ZONE("dispatch.ShootAll.launch");
+ _projectiles_sys->launch(_projectiles, std::move(all_words),
+ _ctx.camera->fwd, _ctx.camera->pos, aim);
+ }
}
- _level_director->show_Details(_ctx);
- _level_director->record_Shot ();
+ { FP_ZONE("dispatch.ShootAll.details");
+ _level_director->show_Details(_ctx); }
+ { FP_ZONE("dispatch.ShootAll.record");
+ _level_director->record_Shot (); }
}
@@ -472,13 +479,10 @@ GameState::projectile_LandOnFloor(ProjectileResult& result)
void
GameState::projectile_LandOnWall(ProjectileResult& result)
{
- DynArray<Text::TokenPtr> editor_block;
- std::string prog_line, comp_line, info_line;
+ DynArray<Text::TokenPtr> edit_block, prog_block, comp_block, info_block;
for (Text::TokenPtr& w : result.words) {
if (!w) continue;
- if (w->type == ObjectType::Text)
- w->set_Look(ConfigColor::keep, Text::Token::depth_wall);
w->body.mode = PhysicsMode::Static;
w->body.vel = Vec3{0, 0, 0};
@@ -486,35 +490,27 @@ GameState::projectile_LandOnWall(ProjectileResult& result)
if (!d.token) continue;
switch (d.role) {
- case Wall::Role::Editor:
- editor_block.push_back(std::move(d.token));
- break;
- case Wall::Role::Program:
- if (!prog_line.empty()) prog_line += ' ';
- prog_line += d.token->text;
- break;
- case Wall::Role::Compiler:
- if (!comp_line.empty()) comp_line += ' ';
- comp_line += d.token->text;
- break;
- case Wall::Role::Info:
- if (!info_line.empty()) info_line += ' ';
- info_line += d.token->text;
- break;
+ case Wall::Role::Editor: edit_block.push_back(std::move(d.token)); break;
+ case Wall::Role::Program: prog_block.push_back(std::move(d.token)); break;
+ case Wall::Role::Compiler: comp_block.push_back(std::move(d.token)); break;
+ case Wall::Role::Info: info_block.push_back(std::move(d.token)); break;
}
}
- if (!editor_block.empty()) {
+ if (!edit_block.empty()) {
Vec3 impact{0, 0, 0};
- for (const Text::TokenPtr& t : editor_block)
+ for (const Text::TokenPtr& t : edit_block)
{ impact.x += t->pos.x; impact.y += t->pos.y; impact.z += t->pos.z; }
- const double inv = 1.0 / static_cast<double>(editor_block.size());
+ const double inv = 1.0 / static_cast<double>(edit_block.size());
impact.x *= inv; impact.y *= inv; impact.z *= inv;
- _editor.attach(std::move(editor_block), impact);
+ _editor.attach(std::move(edit_block), impact);
+ }
+
+ { FP_ZONE("flush.append");
+ if (!prog_block.empty()) _program .append_Tokens(std::move(prog_block));
+ if (!comp_block.empty()) _compiler.append_Tokens(std::move(comp_block));
+ if (!info_block.empty()) _info .append_Tokens(std::move(info_block));
}
- if (!prog_line.empty()) _program .append_Line(prog_line);
- if (!comp_line.empty()) _compiler.append_Line(comp_line);
- if (!info_line.empty()) _info .append_Line(info_line);
}
@@ -582,50 +578,59 @@ void
GameState::handle_Input(FrameInput& input, double dt) {
for (InputCommand cmd : input.commands) {
switch (cmd) {
- case InputCommand::Jump: on_Jump(); break;
- case InputCommand::Click: on_Click(); break;
- case InputCommand::Drop: on_DropLast(); break;
- case InputCommand::ShootAll: on_ShootAll(); break;
- case InputCommand::Compile: on_Compile(); break;
- case InputCommand::Run: on_Run(); break;
- case InputCommand::DebugPrint: on_DebugPrint(); break;
- case InputCommand::FreelookStart: on_FreelookStart(); break;
- case InputCommand::FreelookEnd: on_FreelookEnd(); break;
- case InputCommand::NumberInc: on_NumberAdjust(+1); break;
- case InputCommand::NumberDec: on_NumberAdjust(-1); break;
- case InputCommand::TypingStart: on_TypingStart (input); break;
- case InputCommand::TypingCommit: on_TypingCommit(input); break;
- case InputCommand::TypingCancel: on_TypingCancel(input); break;
- case InputCommand::SandboxRestore: sandbox_Restore(); break;
- case InputCommand::ConnectPultSubmit: on_WallConnectSubmit(); break;
- case InputCommand::ConnectPultExit: on_WallConnectExit(); break;
+ case InputCommand::Jump: { FP_ZONE("on_Jump"); on_Jump();} break;
+ case InputCommand::Click: { FP_ZONE("on_Click"); on_Click();} break;
+ case InputCommand::Drop: { FP_ZONE("on_DropLast"); on_DropLast();} break;
+ case InputCommand::ShootAll: { FP_ZONE("on_ShootAll"); on_ShootAll();} break;
+ case InputCommand::Compile: { FP_ZONE("on_Compile"); on_Compile();} break;
+ case InputCommand::Run: { FP_ZONE("on_Run"); on_Run();} break;
+ case InputCommand::DebugPrint: { FP_ZONE("on_DebugPrint"); on_DebugPrint();} break;
+ case InputCommand::FreelookStart: { FP_ZONE("on_FreelookStart"); on_FreelookStart();} break;
+ case InputCommand::FreelookEnd: { FP_ZONE("on_FreelookEnd"); on_FreelookEnd();} break;
+ case InputCommand::NumberInc: { FP_ZONE("on_NumberAdjust"); on_NumberAdjust(+1);} break;
+ case InputCommand::NumberDec: { FP_ZONE("on_NumberAdjust"); on_NumberAdjust(-1);} break;
+ case InputCommand::TypingStart: { FP_ZONE("on_TypingStart "); on_TypingStart (input);} break;
+ case InputCommand::TypingCommit: { FP_ZONE("on_TypingCommit"); on_TypingCommit(input);} break;
+ case InputCommand::TypingCancel: { FP_ZONE("on_TypingCancel"); on_TypingCancel(input);} break;
+ case InputCommand::SandboxRestore: { FP_ZONE("on_TypingCancel"); sandbox_Restore();} break;
+ case InputCommand::ConnectPultSubmit: { FP_ZONE("on_WallConnectSubmit");on_WallConnectSubmit();} break;
+ case InputCommand::ConnectPultExit: { FP_ZONE("on_WallConnectExit"); on_WallConnectExit();} break;
}
}
if (_wall_connection.get_Target() == Wall::Connection::Target::Editor) {
- Text::Handler::EditReq req = _editor.feed_Keys(input.vim_keys, _ctx.camera->pos.y);
- input.vim_keys.clear();
- switch (req) {
- case Text::Handler::EditReq::Write:
- _editor.mark_Saved();
- break;
- case Text::Handler::EditReq::WriteQuit:
- case Text::Handler::EditReq::Exit:
- case Text::Handler::EditReq::Discard:
- _editor.end_Edit();
- _wall_connection.end();
- _frame_input.capture_vim = false;
- break;
- case Text::Handler::EditReq::None:
- break;
+ { FP_ZONE("handle_input.Editor");
+ Text::Handler::EditReq req = _editor.feed_Keys(input.vim_keys, _ctx.camera->pos.y);
+ input.vim_keys.clear();
+ switch (req) {
+ case Text::Handler::EditReq::Write:
+ _editor.mark_Saved();
+ break;
+ case Text::Handler::EditReq::WriteQuit:
+ case Text::Handler::EditReq::Exit:
+ case Text::Handler::EditReq::Discard:
+ _editor.end_Edit();
+ _wall_connection.end();
+ _frame_input.capture_vim = false;
+ break;
+ case Text::Handler::EditReq::None:
+ break;
+ }
}
}
else if (_wall_connection.get_Target() == Wall::Connection::Target::Program) {
- _wall_connection.set_ProgramBuffer(input.typing_buffer);
+ { FP_ZONE("handle_input.Program");
+ _wall_connection.set_ProgramBuffer(input.typing_buffer);
+ }
}
else if (input.typing_mode) {
- input.forward = input.backward = input.left = input.right = false;
- if (input.typing_buffer != _typing_last) { typing_RebuildPreview(input.typing_buffer); _typing_last = input.typing_buffer; }
+ { FP_ZONE("handle_input.typing_mode");
+ input.forward = input.backward = input.left = input.right = false;
+ if (input.typing_buffer != _typing_last) {
+ typing_RebuildPreview(input.typing_buffer);
+ _typing_last = input.typing_buffer;
+ }
+ }
}
if (_pick_cooldown > 0.0) _pick_cooldown -= dt;
@@ -634,7 +639,7 @@ GameState::handle_Input(FrameInput& input, double dt) {
if (_shoot_cooldown > 0.0) _shoot_cooldown -= dt;
if (input.shoot && _shoot_cooldown <= 0.0)
- { if (on_ShootOne()) _shoot_cooldown = 0.15; }
+ { FP_ZONE("handle_input.onShootO"); if (on_ShootOne()) _shoot_cooldown = 0.15; }
}
void
diff --git a/src/states/game_state.hpp b/src/states/game_state.hpp
index 2bdd018..9cc242f 100644
--- a/src/states/game_state.hpp
+++ b/src/states/game_state.hpp
@@ -23,7 +23,7 @@
#include "systems/pult_system.hpp"
#include "systems/wall_connection.hpp"
#include "systems/word_ai_system.hpp"
-#include "systems/word_anim.hpp"
+#include "systems/word_anim_system.hpp"
#include "text/handler.hpp" // Text::Handler (replaces Wall::Editor + Text::SrcView)
#include "text/vim_buffer.hpp"
#include "utils/globals.hpp"
diff --git a/src/systems/cpp_highlight_system.hpp b/src/systems/cpp_highlight_system.hpp
index bbfee6a..934cadf 100644
--- a/src/systems/cpp_highlight_system.hpp
+++ b/src/systems/cpp_highlight_system.hpp
@@ -16,7 +16,6 @@ public:
void request(const std::string& source, uint64_t gen)
{
- // TODO: dont send a request twice?!
if (source == _last_requested) return;
_last_requested = source;
diff --git a/src/systems/word_anim.cpp b/src/systems/word_anim_system.cpp
similarity index 98%
rename from src/systems/word_anim.cpp
rename to src/systems/word_anim_system.cpp
index 36bf2a2..248223a 100644
--- a/src/systems/word_anim.cpp
+++ b/src/systems/word_anim_system.cpp
@@ -1,7 +1,7 @@
#include <algorithm>
#include "entities/world.hpp"
-#include "word_anim.hpp"
+#include "word_anim_system.hpp"
#include "utils/config.hpp"
diff --git a/src/systems/word_anim.hpp b/src/systems/word_anim_system.hpp
similarity index 100%
rename from src/systems/word_anim.hpp
rename to src/systems/word_anim_system.hpp
diff --git a/src/text/handler.cpp b/src/text/handler.cpp
index 58f399b..821e4e5 100644
--- a/src/text/handler.cpp
+++ b/src/text/handler.cpp
@@ -68,7 +68,7 @@ set_NumberText(Token::Data* t, const std::string& s)
Handler::Handler(Wall::Role role, float world_scale, LayoutMode mode)
: _role(role), _layout(mode)
{
- _tf = Wall::get_Transform(role, world_scale);
+ _transform = Wall::get_Transform(role, world_scale);
_line_height = ConfigWall::line_height;
_word_spacing = ConfigWall::free_space_dist;
_append_color = ConfigColor::dark_gray;
@@ -127,8 +127,8 @@ Handler::set_Lines(const DynArray<std::string>& lines)
std::istringstream in(text);
std::string tk;
while (in >> tk) {
- TokenPtr t = make_Token(tk, _depth, _scale, _tf.position, ConfigColor::wall);
- t->yaw = _tf.yaw;
+ TokenPtr t = make_Token(tk, _depth, _scale, _transform.position, ConfigColor::wall);
+ t->yaw = _transform.yaw;
t->set_ColorSource(src);
ln.tokens.push_back(std::move(t));
}
@@ -143,6 +143,8 @@ Handler::set_Lines(const DynArray<std::string>& lines)
void
Handler::append_Line(const std::string& text)
{
+ SPIKE("append_Line");
+
for (Line& l : _lines) {
l.y += _line_height;
for (TokenPtr& t : l.tokens) if (t) t->pos.y += _line_height;
@@ -151,9 +153,9 @@ Handler::append_Line(const std::string& text)
Line ln;
ln.y = _append_y;
- TokenPtr t = make_Token(text, _depth, _scale, _tf.position, _append_color);
- t->yaw = _tf.yaw;
- t->pos = _tf.position;
+ TokenPtr t = make_Token(text, _depth, _scale, _transform.position, _append_color);
+ t->yaw = _transform.yaw;
+ t->pos = _transform.position;
t->pos.y = _append_y;
t->set_Color(_append_color);
ln.tokens.push_back(std::move(t));
@@ -162,9 +164,55 @@ Handler::append_Line(const std::string& text)
}
+void
+Handler::append_Tokens(DynArray<TokenPtr> block)
+{
+ SPIKE("append_Tokens");
+
+ DynArray<TokenPtr> toks;
+ toks.reserve(block.size());
+ for (TokenPtr& t : block) if (t) toks.push_back(std::move(t));
+ if (toks.empty()) return;
+
+ for (Line& l : _lines) {
+ l.y += _line_height;
+ for (TokenPtr& t : l.tokens) if (t) t->pos.y += _line_height;
+ }
+
+ double row_w = 0.0;
+ for (size_t i = 0; i < toks.size(); ++i) {
+ row_w += 2.0 * (double)toks[i]->half_extent;
+ if (i + 1 < toks.size()) row_w += _word_spacing;
+ }
+
+ Line ln;
+ ln.y = _append_y;
+ ln.line_height = _line_height;
+
+ double cursor = row_w * 0.5;
+ for (TokenPtr& t : toks) {
+ const double hw = (double)t->half_extent;
+ t->yaw = _transform.yaw;
+ t->pos = _transform.position;
+ t->pos.y = _append_y;
+ t->pos.z = cursor - hw;
+ t->body.mode = PhysicsMode::Static;
+ t->body.vel = Vec3{0, 0, 0};
+ t->set_ColorSource(ColorSource::Static);
+ t->set_Color(_append_color);
+ cursor = t->pos.z - hw - _word_spacing;
+ ln.tokens.push_back(std::move(t));
+ }
+
+ _lines.push_back(std::move(ln));
+ mark_Dirty();
+}
+
+
TokenPtr
Handler::detach(Token::Data* t)
{
+ SPIKE("detach");
if (edit_Active()) return nullptr;
for (size_t li = 0; li < _lines.size(); ++li) {
@@ -187,6 +235,7 @@ Handler::detach(Token::Data* t)
void
Handler::attach(DynArray<TokenPtr> tokens, const Vec3& impact)
{
+ SPIKE("attach");
if (tokens.empty()) return;
if (_layout == LayoutMode::Mono)
attach_Mono (tokens, impact);
@@ -317,7 +366,6 @@ Handler::apply_Target(const Target& t)
void
Handler::attach_Snapped(DynArray<TokenPtr>& block, const Vec3& impact)
{
- // Order the run visual left->right (decreasing z on the NegX wall).
std::sort(block.begin(), block.end(),
[](const TokenPtr& a, const TokenPtr& b){
if (!a) return false;
@@ -325,11 +373,11 @@ Handler::attach_Snapped(DynArray<TokenPtr>& block, const Vec3& impact)
return a->pos.z > b->pos.z;
});
- // Incoming editor tokens are lexed; face the wall.
for (TokenPtr& w : block) {
if (!w) continue;
- w->yaw = _tf.yaw;
+ w->yaw = _transform.yaw;
w->set_ColorSource(_lex_enabled ? ColorSource::Lexed : ColorSource::Static);
+ w->set_Depth(_depth);
}
double block_w = 0.0;
@@ -379,7 +427,7 @@ Handler::attach_Snapped(DynArray<TokenPtr>& block, const Vec3& impact)
const double center_z = cursor - hw;
w->pos.y = target.y;
w->pos.z = center_z;
- w->pos.x = _tf.position.x;
+ w->pos.x = _transform.position.x;
cursor = center_z - hw - ConfigWall::free_space_dist;
}
@@ -550,7 +598,7 @@ Handler::relayout()
void
Handler::relayout_Snapped()
{
- const double x_wall = _tf.position.x;
+ const double x_wall = _transform.position.x;
const double lh = _line_height;
const double gap = 0.5;
const double floor_y = World::get_FloorY<double>();
@@ -567,7 +615,7 @@ Handler::relayout_Snapped()
if (l.is_Empty()) continue;
double total_w = 0.0;
for (TokenPtr& t : l.tokens) {
- t->yaw = _tf.yaw;
+ t->yaw = _transform.yaw;
total_w += 2.0 * (double)t->half_extent;
}
total_w += gap * static_cast<double>(l.tokens.size() - 1);
@@ -640,7 +688,7 @@ Handler::relayout_Mono()
if (can_reuse && i < old.size() && old[i].num_left)
br.num = std::move(old[i].num_left);
else
- br.num = make_Number(std::to_string(i + 1), _tf.position, _tf.yaw, 0.0022f);
+ br.num = make_Number(std::to_string(i + 1), _transform.position, _transform.yaw, 0.0022f);
if (br.num) {
const std::string s_no = std::to_string(i + 1);
@@ -662,8 +710,8 @@ Handler::relayout_Mono()
if (reuse_line && ti < old[i].tokens.size())
t = std::move(old[i].tokens[ti]);
else {
- t = make_Token(tk, Token::depth_wall, 0.003f, _tf.position, ConfigColor::white);
- t->yaw = _tf.yaw;
+ t = make_Token(tk, Token::depth_wall, 0.003f, _transform.position, ConfigColor::white);
+ t->yaw = _transform.yaw;
t->set_ColorSource(ColorSource::Lexed);
}
br.toks.push_back({ std::move(t), start, tk.size() });
@@ -690,7 +738,7 @@ Handler::relayout_Mono()
if (built[i].num) {
const double he = (double)built[i].num->half_extent;
- built[i].num->pos = _tf.position;
+ built[i].num->pos = _transform.position;
built[i].num->pos.y = row_y;
built[i].num->pos.z = z_block_left - he;
ln.num_left = std::move(built[i].num);
@@ -699,7 +747,7 @@ Handler::relayout_Mono()
for (BT& bt : built[i].toks) {
const double left_z = _code_left_z - static_cast<double>(bt.col) * _char_w;
const double tok_w = _char_w * static_cast<double>(bt.len);
- bt.t->pos = _tf.position;
+ bt.t->pos = _transform.position;
bt.t->pos.y = row_y;
bt.t->pos.z = left_z - tok_w * 0.5;
ln.tokens.push_back(std::move(bt.t));
@@ -740,10 +788,10 @@ Handler::maintain_Numbers(double dt, const Camera&)
const std::string s = std::to_string(rank);
if (!l.num_left)
- l.num_left = make_Number(s, Vec3{_tf.position.x, l.y, 0.0}, _tf.yaw,
+ l.num_left = make_Number(s, Vec3{_transform.position.x, l.y, 0.0}, _transform.yaw,
ConfigWall::line_number_scale);
if (_both_columns && !l.num_right)
- l.num_right = make_Number(s, Vec3{_tf.position.x, l.y, 0.0}, _tf.yaw,
+ l.num_right = make_Number(s, Vec3{_transform.position.x, l.y, 0.0}, _transform.yaw,
ConfigWall::line_number_scale);
if (l.number != rank) {
@@ -770,7 +818,7 @@ Handler::maintain_Numbers(double dt, const Camera&)
for (size_t oi : order) {
Line& l = _lines[oi];
- double x = _tf.position.x;
+ double x = _transform.position.x;
for (TokenPtr& w : l.tokens) { if (w) { x = w->pos.x; break; } }
const double ny = l.y + l.bounce_off;
diff --git a/src/text/handler.hpp b/src/text/handler.hpp
index 555ab51..9001b48 100644
--- a/src/text/handler.hpp
+++ b/src/text/handler.hpp
@@ -45,44 +45,42 @@ public:
void set_Bordered (bool on) { _bordered = on; }
void set_Underline (bool on) { _underline = on; }
void set_DefaultStyle(float depth, float scale, double line_height, double word_spacing);
+ void set_Lines (const DynArray<std::string>& lines);
Wall::Role role () const { return _role; }
LayoutMode layout() const { return _layout; }
+ void set_AppendBaseline(double y) { _append_y = y; }
+ void set_AppendColor (SDL_FColor c) { _append_color = c; }
- // ── content ────────────────────────────────────────────────────────────
void clear();
- void set_Lines (const DynArray<std::string>& lines);
- void append_Line(const std::string& text);
- void set_AppendBaseline(double y) { _append_y = y; }
- void set_AppendColor (SDL_FColor c) { _append_color = c; }
- // ── ownership transfer ────────────────────────────────────────────────
- TokenPtr detach (Token::Data* t);
- void attach (DynArray<TokenPtr> tokens, const Vec3& impact);
- void clear_ToFloor(DynArray<TokenPtr>& out);
+ void append_Line (const std::string& text);
+ const DynArray<Line>&
+ get_Lines () const { return _lines; }
+ DynArray<std::string>
+ as_Lines () const;
+ void append_Tokens (DynArray<TokenPtr> block);
+ void apply_Highlight(const DynArray<HiSpan>& spans, uint64_t gen);
+
+ Token::Data* token_At_Byte(size_t off);
+ TokenPtr detach (Token::Data* t);
+ void attach (DynArray<TokenPtr> tokens, const Vec3& impact);
+ void clear_ToFloor(DynArray<TokenPtr>& out);
+ double scroll_DeltaToEye (size_t line_id, double eye_y) const;
+
+ void bounce (size_t line_id);
+ DynArray<NumberHit>
+ number_Hitboxes () const;
- // ── async lexing ──────────────────────────────────────────────────────
bool is_Dirty () const { return _lex_enabled && _dirty; }
uint64_t generation () const { return _gen; }
std::string render_Source();
- void apply_Highlight(const DynArray<HiSpan>& spans, uint64_t gen);
void mark_Dirty ();
- // ── queries ───────────────────────────────────────────────────────────
- Token::Data* token_At_Byte(size_t off);
- DynArray<std::string> as_Lines () const;
- const DynArray<Line>& get_Lines () const { return _lines; }
-
- // ── per-frame ──────────────────────────────────────────────────────────
void update (double dt, const Camera& cam);
void render (GlRenderer& gl, const Camera& cam, const Light& light) const;
void scroll (double delta);
- // ── line-number ───────────────────────────────────────────────────────
- double scroll_DeltaToEye (size_t line_id, double eye_y) const;
- void bounce (size_t line_id);
- DynArray<NumberHit>
- number_Hitboxes () const;
// ── vim cursor support (Mono) ──────────────────────────────────────────
MonoMetrics mono_Metrics() const;
@@ -122,12 +120,12 @@ public:
void draw (GlRenderer& gl, const Camera& cam, const Light& light) const { render(gl, cam, light); }
private:
- DynArray<Line> _lines;
+ DynArray<Line> _lines;
- Wall::Role _role { Wall::Role::Editor };
- Wall::Transform _tf {};
- LayoutMode _layout { LayoutMode::Snapped };
- FaceMode _face { FaceMode::Pitch };
+ Wall::Role _role { Wall::Role::Editor };
+ Wall::Transform _transform{};
+ LayoutMode _layout { LayoutMode::Snapped };
+ FaceMode _face { FaceMode::Pitch };
float _depth { Token::depth_wall };
float _scale { 0.003f };
@@ -197,4 +195,5 @@ private:
bool impact_To_RowCol(const Vec3& impact, int& row, int& col) const;
};
+
} // namespace Text
diff --git a/src/text/text_mesh.cpp b/src/text/text_mesh.cpp
index 364ff32..67edd81 100644
--- a/src/text/text_mesh.cpp
+++ b/src/text/text_mesh.cpp
@@ -9,16 +9,18 @@
// Dependencies: freetype2, earcut.hpp (header-only)
#include "text_mesh.hpp"
+#include "thirdparty/earcut/earcut.hpp"
+#include "token.hpp"
+#include <SDL3/SDL.h>
+#include <algorithm>
+#include <array>
#include <ft2build.h>
+#include <unordered_map>
+
#include FT_FREETYPE_H
#include FT_OUTLINE_H
-#include "thirdparty/earcut/earcut.hpp"
-
-#include <SDL3/SDL.h>
-#include <array>
-#include <algorithm>
static constexpr int BEZIER_STEPS = 8;
static constexpr float FT_SCALE = 1.0f / 64.0f;
@@ -27,6 +29,48 @@ using Pt2 = std::array<float, 2>;
using Ring = DynArray<Pt2>;
using Poly = DynArray<Ring>; // [0]=outer, [1..]=holes
+
+
+
+namespace {
+
+struct FaceKey {
+ std::string path;
+ int size;
+ bool operator==(const FaceKey& o) const { return size == o.size && path == o.path; }
+};
+struct FaceKeyHash {
+ size_t operator()(const FaceKey& k) const {
+ return std::hash<std::string>{}(k.path) ^ (std::hash<int>{}(k.size) << 1);
+ }
+};
+
+FT_Library g_ft = nullptr;
+std::unordered_map<FaceKey, FT_Face, FaceKeyHash> g_faces;
+
+// Returns a ready-to-use face at the requested pixel size, or nullptr.
+FT_Face acquire_Face(const std::string& path, int size) {
+ if (!g_ft && FT_Init_FreeType(&g_ft)) { g_ft = nullptr; return nullptr; }
+
+ FaceKey key{ path, size };
+ auto it = g_faces.find(key);
+ if (it != g_faces.end()) {
+ FT_Set_Pixel_Sizes(it->second, 0, static_cast<FT_UInt>(size)); // cheap; face stays loaded
+ return it->second;
+ }
+
+ FT_Face face;
+ if (FT_New_Face(g_ft, path.c_str(), 0, &face)) {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "FT_New_Face failed: %s", path.c_str());
+ return nullptr;
+ }
+ FT_Set_Pixel_Sizes(face, 0, static_cast<FT_UInt>(size));
+ g_faces.emplace(key, face);
+ return face;
+}
+
+} // namespace
+
// ── FreeType outline callbacks ────────────────────────────────────────────────
struct OutlineCtx {
@@ -207,25 +251,19 @@ bool
TextMesh::build(const std::string& font_path,
const std::string& text, int size, float depth, float scale)
{
+ SPIKE("mesh.build");
verts .clear();
tris .clear();
is_front.clear();
- //edges.clear();
- FT_Library ft;
- if (FT_Init_FreeType(&ft)) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "FT_Init_FreeType failed"); return false; }
- FT_Face face;
- if (FT_New_Face(ft, font_path.c_str(), 0, &face)) {
- SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "FT_New_Face failed: %s", font_path.c_str());
- FT_Done_FreeType(ft); return false;
- }
- FT_Set_Pixel_Sizes(face, 0, static_cast<FT_UInt>(size));
+ FT_Face face = acquire_Face(font_path, size); // inits g_ft once, caches the face
+ if (!face) return false;
float pen_x = 0.0f;
for (const char ch : text) {
FT_UInt gi = FT_Get_Char_Index(face, static_cast<FT_ULong>(static_cast<unsigned char>(ch)));
- if (FT_Load_Glyph(face, gi, FT_LOAD_NO_BITMAP)) continue;
+ if (FT_Load_Glyph(face, gi, FT_LOAD_NO_BITMAP)) continue;
if (face->glyph->format != FT_GLYPH_FORMAT_OUTLINE) continue;
OutlineCtx ctx;
@@ -258,16 +296,16 @@ TextMesh::build(const std::string& font_path,
// Front verts (z = -half_d, facing -Z = toward viewer)
for (auto& p : flat) {
verts.push_back({ static_cast<double>(p[0]),
- static_cast<double>(p[1]),
- static_cast<double>(-half_d) });
+ static_cast<double>(p[1]),
+ static_cast<double>(-half_d) });
is_front.push_back(1.0f);
}
int back_base = static_cast<int>(verts.size());
// Back verts (z = +half_d)
for (auto& p : flat) {
verts.push_back({ static_cast<double>(p[0]),
- static_cast<double>(p[1]),
- static_cast<double>( half_d) });
+ static_cast<double>(p[1]),
+ static_cast<double>( half_d) });
is_front.push_back(0.0f);
}
@@ -280,8 +318,8 @@ TextMesh::build(const std::string& font_path,
// Back face triangles — natural winding so normal faces -Z.
for (size_t i = 0; i+2 < indices.size(); i += 3)
tris.push_back({ back_base + static_cast<int>(indices[i] ),
- back_base + static_cast<int>(indices[i+1]),
- back_base + static_cast<int>(indices[i+2]) });
+ back_base + static_cast<int>(indices[i+1]),
+ back_base + static_cast<int>(indices[i+2]) });
// Side walls: duplicate verts so they get hard outward normals
// (NOT shared with face verts — this is the key fix for shading artifacts)
@@ -298,20 +336,20 @@ TextMesh::build(const std::string& font_path,
const Pt2& p1 = ring[static_cast<size_t>(i1)];
verts.push_back({ static_cast<double>(p0[0]),
- static_cast<double>(p0[1]),
- static_cast<double>(-half_d) }); // front-left
+ static_cast<double>(p0[1]),
+ static_cast<double>(-half_d) }); // front-left
is_front.push_back(0.0f);
verts.push_back({ static_cast<double>(p1[0]),
- static_cast<double>(p1[1]),
- static_cast<double>(-half_d) }); // front-right
+ static_cast<double>(p1[1]),
+ static_cast<double>(-half_d) }); // front-right
is_front.push_back(0.0f);
verts.push_back({ static_cast<double>(p1[0]),
- static_cast<double>(p1[1]),
- static_cast<double>( half_d) }); // back-right
+ static_cast<double>(p1[1]),
+ static_cast<double>( half_d) }); // back-right
is_front.push_back(0.0f);
verts.push_back({ static_cast<double>(p0[0]),
- static_cast<double>(p0[1]),
- static_cast<double>( half_d) }); // back-left
+ static_cast<double>(p0[1]),
+ static_cast<double>( half_d) }); // back-left
is_front.push_back(0.0f);
tris.push_back({ wb+0, wb+1, wb+2 });
@@ -324,9 +362,6 @@ TextMesh::build(const std::string& font_path,
pen_x += static_cast<float>(face->glyph->advance.x) * FT_SCALE;
}
- FT_Done_Face(face);
- FT_Done_FreeType(ft);
-
// Centre mesh on bounding box
if (!verts.empty()) {
float minX=1e9f, maxX=-1e9f;
diff --git a/src/text/token.cpp b/src/text/token.cpp
index 6cb6946..4791020 100644
--- a/src/text/token.cpp
+++ b/src/text/token.cpp
@@ -164,6 +164,7 @@ Data::get_Distance(Vec3 position) const
void
Data::rebuild_Mesh()
{
+ SPIKE("rebuild_mesh");
loaded = mesh.build(font_path, text, 64, depth, scale);
if (!loaded) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
diff --git a/src/text/token.hpp b/src/text/token.hpp
index 26088e7..cac206e 100644
--- a/src/text/token.hpp
+++ b/src/text/token.hpp
@@ -181,6 +181,20 @@ Token
};
+
+
+ struct SpikeTimer {
+ const char* name; Uint64 t0;
+ SpikeTimer(const char* n) : name(n), t0(SDL_GetPerformanceCounter()) {}
+ ~SpikeTimer() {
+ double ms = 1000.0 * double(SDL_GetPerformanceCounter() - t0)
+ / double(SDL_GetPerformanceFrequency());
+ if (ms > 1.0) SDL_Log("SPIKE %-24s %.2f ms", name, ms);
+ }
+ };
+#define SPIKE(n) Text::Token::SpikeTimer _spk_##__LINE__(n)
+
+
} // namespace Token::Data