HUD leaves
swofty/hud_leaves.hpp is the way to drive a HUD that has more than a handful of values. You declare each leaf once with its type, set it every frame, and call flush(). The core keeps the last value it wrote for every leaf, so only changes reach the game, a table row lands in one tick, and a rebuilt view model is refilled by the core, not by a timer in your addon.
#include <swofty/events.hpp>
#include <swofty/hud_leaves.hpp>
namespace hud = swofty::hud;
struct Leaves {
hud::Leaves L{"view_model_data.hud_my_"};
hud::BoolLeaf live = L.boolean("live");
hud::TextLeaf kmh = L.text("kmh", 4);
hud::IntLeaf lap = L.integer("lap");
hud::OneHot gear = L.one_hot("g", 9); // g0..g8, exactly one true
hud::Level throttle = L.level("th", 10); // th01..th10, cumulative
hud::OneHot rival = L.one_hot("rv", 24, 2); // rv00..rv23
};
Leaves h;
void on_frame(float)
{
h.L.set(h.live, true);
h.L.set(h.kmh, "212");
h.L.set(h.lap, 7);
h.L.set(h.gear, 3); // sets g3, clears the other eight
h.L.set(h.throttle, swofty::ease::level(0.43f, 10));
h.L.set(h.rival, -1); // none
h.L.flush(); // hands the changed leaves to the core
}[hud] 42 leaves registered by author.my-addon (42 in the table)- Header:
swofty/hud_leaves.hpp, namespaceswofty::hud. Header only overSwoftyHudversion 2; no Windows includes on the host. - Types:
bool,int32_tand text. The engine's view model has no float or colour setter, so there are no such leaves; pack a colour into an integer leaf and bind it in the template. - Handles:
Leaf<T>is a typed index. Setting a text leaf from anintdoes not compile. - Prefix: every path must begin with
ui_menu_data.hud_,view_model_data.hud.orview_model_data.hud_, as forhud::set. - Thread: from your frame callback. The core takes one lock per
flush(), not one per leaf. - Text: capped at
capbytes (default and maximum 200) on a UTF-8 boundary.
Rows and atomic groups
Everything set while an Atomic lives is one group. The core applies a group whole in a single tick, so a table row is never half old and half new, even when the tick's write cap is reached.
struct Row {
hud::TextLeaf pos, gap;
hud::BoolLeaf me;
};
hud::Leaves L{"view_model_data.hud_tt_"};
auto rows = L.rows<Row>("r", 22, 2, [](hud::Leaves &l, int) { // r00_pos, r00_gap, r00_me ... r21_*
return Row{l.text("pos", 8), l.text("gap", 15), l.boolean("me")};
});
for (int i = 0; i < 22; i++) {
hud::Leaves::Atomic row(L);
L.set(rows[i].pos, std::to_string(i + 1));
L.set(rows[i].gap, "+1.2");
L.set(rows[i].me, i == 4);
}
L.flush();The core's rules
The core side is the registered leaf table of SwoftyHud version 2:
leaves_register(leaves, n, ids) declare leaves; a path outside the hud prefixes or a second declaration
with another type is invalid
leaves_set(ids, values, n) write by handle; only values that differ from the last write are queued
leaves_begin() / leaves_end() a group, nestable, per thread
generation() bumped when the game rebuilds its view model
stats(out) applied last tick, queued, dropped, registered, cap per tick, failed- The queue is a dirty list with one entry per leaf, so it cannot overflow. The core applies up to 256 leaves a tick and defers the rest to the next tick; nothing is dropped. A group larger than the cap still goes out whole.
- A leaf whose parent node does not resolve yet (a view model not built) is retried every second. The first failures are logged.
- The core bumps
generation()onsession.start, onsession.unload, and when a registered leaf's parent node or the view-model registry changes. It then re-sends every registered leaf with a value, so an addon needs no periodic refresh. As a safety net the core also re-sends all registered leaves every 10 seconds (counted instats().resyncs). - Type check: a registered path written through
hud::setwith another type is refused (invalid) in the log. The engine does not expose a view-model node's type, so a template binding of a type different from the leaf's is still found only byswofty dev check.
if (hud::Leaves::generation() != seen) { // the game rebuilt its view model; the core already re-sent
seen = hud::Leaves::generation();
smoothers.reset();
}
hud::LeafStats s;
if (hud::Leaves::stats(s))
swofty::log("[my-hud] %u leaves, %u queued, %u applied last tick", s.registered, s.queued, s.applied_last_tick);Older cores
On a core without version 2 the same code runs over hud::set: changes are detected in the addon, at most 40 writes go out per flush(), and every leaf is rewritten every two seconds. Leaves::generation() answers 0 and stats() returns false.
Parts of this header are the generic pieces of Project Halo's HUD code, ported with permission. Uses work from Project Halo by nyannoying, https://github.com/nyannoying1337/f1-25-project-halo.