Skip to content

Data

ll/api/data/ Β· Common

Overview

The Data module provides general-purpose data structures, including observable values, a LevelDB-based key-value store, dependency graphs, semantic versions, type-erased functions, cancellable callbacks, and thread-safe containers.

Headers

Header Description
ll/api/data/Observable.h Shared observable value with explicit subscriptions
ll/api/data/KeyValueDB.h LevelDB key-value database
ll/api/data/DependencyGraph.h Dependency resolution graph
ll/api/data/Version.h Semantic version type
ll/api/data/VersionRequirement.h Semantic version range
ll/api/data/AnyFunction.h Type-erased function container
ll/api/data/CancellableCallback.h Cancellable async callback
ll/api/data/ConcurrentPriorityQueue.h Thread-safe priority queue
ll/api/data/TightPair.h Space-optimized pair
ll/api/data/IndirectValue.h Pointer wrapper with value semantics

Key Classes

Observable\<T>

Observable<T> stores a value and synchronously notifies subscribers when that value changes. It is a general data API; it is not tied to UI. The data-driven UI module derives its typed observables from this template.

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#include "ll/api/data/Observable.h"

namespace ll::data {
template <class T>
class Observable {
public:
    using Callback       = brstd::move_only_function<void(T const&)>;
    using SubscriptionId = std::uint32_t;

    explicit Observable(T initial);

    T getData() const;
    void setData(T value);

    SubscriptionId subscribe(Callback callback);
    bool unsubscribe(SubscriptionId id);
};
}

Copies share the same value, subscribers, and subscription ID sequence. Constructing a separate Observable creates independent state.

setData() compares the new value with the current value. An equal value is ignored; otherwise, the value is replaced and callbacks run synchronously before setData() returns. Callback exceptions are not swallowed: an exception propagates to the caller and stops the current notification pass.

subscribe() accepts move-only callbacks and returns an ID. The ID is not an RAII handle: discarding it does not unsubscribe the callback. A subscription remains active until unsubscribe(id) succeeds or the shared observable state is destroyed. An empty callback returns ID 0, and unsubscribe(0) returns false.

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
ll::data::Observable<int> count{0};
auto copy = count; // Shares state with count.

auto const subscription = count.subscribe([](int const& value) {
    // Called synchronously when the value changes.
});

copy.setData(1); // count.getData() is now 1; the callback runs once.
copy.setData(1); // Equal value: no callback.

bool removed = count.unsubscribe(subscription); // true

Warning

Observable<T> does not provide internal locking. Synchronize concurrent access externally. Callbacks run directly while the subscription container is being traversed, so do not add or remove subscriptions on the same observable from one of its callbacks. Recursive setData() calls also execute immediately; avoid them unless that behavior is deliberately controlled.

T must be copyable for getData() and equality-comparable for setData(). See Data-driven UI for ObservableBoolean, ObservableNumber, ObservableString, and ObservableUIRawMessage.

KeyValueDB

A persistent key-value store backed by LevelDB.

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
namespace ll::data {
class KeyValueDB {
public:
    class WriteBatch {
    public:
        WriteBatch& set(std::string_view key, std::string_view val);
        WriteBatch& del(std::string_view key);
    };

    explicit KeyValueDB(std::filesystem::path const& path);
    KeyValueDB(std::filesystem::path const& path, bool createIfMiss, bool fixIfError, int bloomFilterBit);

    std::optional<std::string> get(std::string_view key) const;
    bool has(std::string_view key) const;
    bool empty() const;
    bool set(std::string_view key, std::string_view val);
    bool del(std::string_view key);
    bool write(WriteBatch const& batch);

    coro::Generator<std::pair<std::string_view, std::string_view>> iter() const;
};
}

Usage

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include "ll/api/data/KeyValueDB.h"
#include "ll/api/mod/Mod.h"

void useDatabase(ll::mod::Mod& mod) {
    ll::data::KeyValueDB db(mod.getDataDir() / "mydata");

    db.set("player_score", "100");

    auto score = db.get("player_score");
    if (score) {
        // *score == "100"
    }

    if (db.has("player_score")) {
        db.del("player_score");
    }

    // Apply several updates atomically
    ll::data::KeyValueDB::WriteBatch batch;
    batch.set("player_score", "125").set("last_reward", "daily").del("pending_reward");
    db.write(batch);

    // Iterate all entries
    for (auto [key, value] : db.iter()) {
        // Process each key-value pair
    }
}

Version

Version parses strict major.minor.patch semantic versions. Prerelease identifiers affect precedence, while build metadata does not. Use isIdenticalTo() when build metadata is part of the identity you need to compare.

C++
1
2
3
4
5
6
7
8
#include "ll/api/data/Version.h"

ll::data::Version current{"1.3.0-beta.1+windows.5"};
ll::data::Version release{"1.3.0"};

bool newer = release > current; // true
bool samePrecedence = ll::data::Version{"1.3.0+first"} == ll::data::Version{"1.3.0+second"}; // true
bool sameIdentity = ll::data::Version{"1.3.0+first"}.isIdenticalTo(ll::data::Version{"1.3.0+second"}); // false

VersionRequirement

VersionRequirement is a normalized semantic version range. Comparators separated by whitespace form an AND group; || separates OR groups. Comparison operators are =, >, >=, <, and <=.

Syntax Meaning
=1.2.3 Exactly version 1.2.3
>=1.2.0 <2.0.0 At least 1.2.0 and below 2.0.0
^1.2.3 Compatible updates below 2.0.0
~1.2.3 Patch updates below 1.3.0
1, 1.2, 1.2.x Partial or wildcard ranges
*, x, X Any non-prerelease version
1.2.3 - 2.0.0 Inclusive hyphen range
^1.2.3 || =2.0.0 Either alternative
C++
1
2
3
4
#include "ll/api/data/VersionRequirement.h"

ll::data::VersionRequirement supported{"^1.2.3 || =2.0.0"};
bool matches = supported.matches(ll::data::Version{"1.8.0"}); // true

A normal range does not match a prerelease unexpectedly. A comparator in the same AND group must explicitly mention a prerelease with the candidate's major.minor.patch core.

For compatibility, a bare full version such as 1.2.3 currently means >=1.2.3 <2.0.0. This form emits a migration warning when read from a mod manifest. Use =1.2.3 for an exact requirement or write the intended range explicitly.

  • Config β€” Uses reflection for serialization, can store config data
  • Mod β€” Mod::getDataDir() for database storage location