FoundationIdentity¶
Typed runtime handle values, persistent UUID values and their codecs, slot
generation advancement, and the process lifetime-token source. Link
Ludus::FoundationIdentity; it depends only on FoundationBase and builds for
native and browser targets. Installed SDKs list it as the FoundationIdentity
component, so a project descriptor may request it in engine.components.
Engine code is exception-free and allocation-free.
See the architecture for the full
identity and lifetime contract.
#include <ludus/foundation/identity/handle.hpp>
#include <ludus/foundation/identity/lifetime.hpp>
#include <ludus/foundation/identity/uuid.hpp>
namespace identity = ludus::foundation::identity;
struct LightTag;
using LightHandle = identity::Handle<LightTag>;
bool ReadAssetId(const char* text, ludus::foundation::usize size, identity::AssetId& output) noexcept
{
identity::Uuid value;
if (identity::TryParseUuid(text, size, identity::UuidPolicy::RequireVersion4, value) !=
identity::UuidStatus::Success)
{
return false; // output is unchanged
}
output = identity::AssetId{value};
return true;
}
bool StartRegistry(ludus::foundation::uint64& owner) noexcept
{
return identity::TryIssueLifetimeId(owner) == identity::LifetimeIdStatus::Success;
}
Runtime handles¶
Handle<Tag> holds Owner / Slot / Generation as uint64 / uint32 / uint32,
the same layout as EntityId. Different tags are different types with no
conversion; the owner token distinguishes two registries of the same domain.
All-zero is null. Equality and IsNull() never say whether a handle is live:
only the issuing registry validates it. A handle does not own or pin anything.
Registries own their slot tables and use the shared rules: generations start at
FIRST_GENERATION, and AdvanceGeneration increments a released slot's
generation or reports Retired at TERMINAL_GENERATION, never wrapping.
Retired slots stay out of reuse for that owner and count against capacity.
TryIssueLifetimeId issues nonzero, strictly increasing uint64 owner tokens
from a relaxed atomic counter, once per registry lifetime, and reports
IdentityExhausted instead of wrapping. A rebuilt registry obtains a new token.
The counter lives in this static library: an executable or shared module that
links it privately gets its own counter. Reloadable gameplay modules must get
tokens from the host instead of calling it themselves. Tokens are process-scoped
and are never serialized.
Persistent UUIDs¶
Uuid stores 16 bytes in RFC 9562 network order. Never reinterpret it as native
integers or a platform GUID. AssetId, SourceObjectId and InstanceId wrap a
Uuid with separate domain types; PersistentObjectKey pairs an instance with
a source object (32 bytes). Construct a wrapper explicitly at a serialization
boundary and read its Value to write it back.
| Operation | Contract |
|---|---|
TryParseUuid(text, size, policy, output) |
Exactly 36 characters in 8-4-4-4-12 groups, case-insensitive hex. Rejects whitespace, braces, URN prefixes and misplaced separators. Output unchanged on failure. |
FormatUuid(value, text, capacity) |
Writes 36 lowercase characters; adds a terminator when capacity >= UUID_TEXT_BUFFER_BYTES. InsufficientSpace writes nothing. |
ValidateUuid(value, policy) |
Checks bytes read from a binary format. |
TryMakeUuidV4(bytes, size, output) |
Pure construction from exactly 16 caller-supplied random bytes. |
TryGenerateUuidV4(source, output) |
Calls the injected EntropySource once; EntropyUnavailable leaves no partial output. |
UuidPolicy::RequireVersion4 accepts only a nonnil version-4 UUID, the only form
of durable identity. AllowNilOrVersion4 also accepts nil, which marks an
absent optional reference. Other versions or variants are rejected with
UnsupportedVersion or UnsupportedVariant.
FoundationIdentity performs no entropy acquisition and has no fallback source.
Platform or tool owners supply an EntropySource over operating-system
entropy; tests inject controlled bytes. Uniqueness is probabilistic. An owner of
a project, import, save or package index must check new values against that
index and reject duplicates supplied by content. For a hash table, hash the
canonical bytes with FoundationHash; exact byte equality decides identity.
Generating UUIDs with platform entropy¶
Ludus::PlatformEntropy provides the production source in
<ludus/platform/entropy.h>. It links only FoundationBase and
FoundationIdentity (publicly), so tools can use it without Platform's window
backends; Ludus::Platform re-exports it. Link either target and pass
GetSystemEntropySource() to TryGenerateUuidV4:
#include <ludus/foundation/identity/uuid.hpp>
#include <ludus/platform/entropy.h>
namespace identity = ludus::foundation::identity;
bool MintAssetId(identity::AssetId& output) noexcept
{
identity::Uuid value;
if (identity::TryGenerateUuidV4(ludus::platform::GetSystemEntropySource(), value) !=
identity::UuidStatus::Success)
{
return false; // EntropyUnavailable; output is unchanged
}
output = identity::AssetId{value};
return true;
}
| Target | Source | Failure |
|---|---|---|
| macOS, Linux | getentropy(3), in requests of at most 256 bytes |
The call reports an error; TryGenerateUuidV4 returns EntropyUnavailable. |
| Browser (Emscripten) | Web Crypto crypto.getRandomValues, in requests of at most 256 bytes, called through a guarded bridge (Node's global Web Crypto object under Node) |
A missing or throwing Web Crypto object fails the request instead of raising a JavaScript exception through wasm. |
| Other targets | None yet | Every nonempty request fails. |
The source is stateless and thread-safe; copy it freely. No target falls back to
wall time, addresses, rand or a gameplay PRNG. TryFillSystemEntropy(bytes,
size) exposes the same bytes for other security-sensitive callers; its
destination is unspecified after a failure. Linux may block briefly during early
boot until the kernel pool is initialized, so generate identities in tools,
import and save paths rather than inside frame-critical work.
Status¶
This is the first stage of the
implementation sequence.
Existing EntityId, audio and RHI handles, and current content formats, are
unchanged. The native and browser platform entropy adapters are implemented in
Ludus::PlatformEntropy. SceneCook generates its UUIDv4 values with
TryGenerateUuidV4 from an entropy source its callers inject
(AssetOptions::EntropyOrNull; see the
architecture decision).
The Editor mints its World entity and level IDs the same way, from an injectable
source that defaults to GetSystemEntropySource().
Registry alignment and UUID authoring records are later stages.