| #ifndef _RIVE_ID_HPP_ | |
| #define _RIVE_ID_HPP_ | |
| #include <cstdint> | |
| namespace rive | |
| { | |
| // `Id` is the type used by `Core` / `CoreContext` for object references | |
| // (parentId, targetId, styleId, etc.). Its shape depends on how the | |
| // runtime is being compiled: | |
| // | |
| // - Without `WITH_RIVE_EDITOR`: `Id` is a flat `uint32_t` — the index | |
| // of the referenced object in the containing artboard's | |
| // `m_Objects` array (runtime `.riv` file layout). | |
| // | |
| // - With `WITH_RIVE_EDITOR` (editor build): `Id` is a `{client, | |
| // object}` struct that represents BOTH kinds of references: | |
| // a) `.rev` (coop) references: `(client, object)` pair — the | |
| // stable per-peer identity the editor uses. | |
| // b) `.riv` (runtime) references loaded at edit time: | |
| // `(0, runtimeIndex)` — synthesized by | |
| // `CoreIdType::runtimeDeserialize` so edit-time tooling can | |
| // inspect a runtime-loaded object graph through the same | |
| // resolver. | |
| // The struct is implicitly constructible from `uint32_t` with | |
| // `client == 0` so call sites that still hand back runtime-style | |
| // `uint32_t` (generated `xxxId()` getters pre-regeneration) keep | |
| // working without each being rewritten by hand — the resulting | |
| // `Id{0, value}` resolves identically via `Artboard::resolve`. The | |
| // one exception is the unset sentinel, which promotes to | |
| // `kEmptyId` so `== -1` keeps meaning "not set". | |
| #ifdef WITH_RIVE_EDITOR | |
| struct Id | |
| { | |
| // The "unset" / "invalid" sentinel is `{UINT32_MAX, UINT32_MAX}` — | |
| // not `{0, 0}`. Runtime `.riv` files use index `0` as a legitimate | |
| // first-object reference (parentId=0 means "child of the artboard | |
| // at m_Objects[0]"), and we want `Artboard::resolve(Id{0, 0})` to | |
| // keep returning that object. The JSON defs use | |
| // `initialValueRuntime: "-1"` for Id fields that really mean | |
| // "unset"; `IdFieldType::convertCpp` rewrites both `-1` and `0`- | |
| // with-`type:Id`+initial-matches-missing into `kEmptyId`. | |
| static constexpr uint32_t kUnsetField = 0xFFFFFFFFu; | |
| uint32_t client = kUnsetField; | |
| uint32_t object = kUnsetField; | |
| constexpr Id() = default; | |
| constexpr Id(uint32_t c, uint32_t o) : client(c), object(o) {} | |
| // Implicit conversion from runtime-index uint32_t. Still callable | |
| // from code that treats `Id` as an integer (e.g. `id == 0`). | |
| // `0xFFFFFFFF` is the defs' unset sentinel, so it has to land on | |
| // `kEmptyId` rather than a client-0 index that far out. | |
| constexpr Id(uint32_t runtimeIndex) : | |
| client(runtimeIndex == kUnsetField ? kUnsetField : 0), | |
| object(runtimeIndex) | |
| {} | |
| friend constexpr bool operator==(const Id& a, const Id& b) | |
| { | |
| return a.client == b.client && a.object == b.object; | |
| } | |
| friend constexpr bool operator!=(const Id& a, const Id& b) | |
| { | |
| return !(a == b); | |
| } | |
| constexpr bool empty() const | |
| { | |
| return client == kUnsetField && object == kUnsetField; | |
| } | |
| // Direct comparisons against integer literals — specifically | |
| // disambiguate `id == 0 / id != 0` against the combined presence | |
| // of the `Id(uint32_t)` promoting constructor and the implicit | |
| // `operator uint32_t()` narrowing conversion (either path is a | |
| // "valid" way to unify the operand types, and the compiler refuses | |
| // to pick). | |
| // | |
| // Semantics (match pre-refactor runtime behavior): | |
| // - `-1` (== UINT32_MAX as unsigned) is the "unset" sentinel | |
| // per the JSON defs' `initialValueRuntime: "-1"` convention, | |
| // so it compares equal to `kEmptyId` regardless of `client`. | |
| // This preserves the runtime idiom `if (id != -1)` which is | |
| // sprinkled through `animation/*.cpp` to check "is this Id | |
| // field set at all?". | |
| // - Any other integer matches an Id at `client == 0` with that | |
| // `object` index — matching how `Id(uint32_t)` projects | |
| // runtime indices into the struct. | |
| friend constexpr bool operator==(const Id& a, int b) | |
| { | |
| if (b == -1) | |
| return a.empty(); | |
| return a.client == 0 && a.object == static_cast<uint32_t>(b); | |
| } | |
| friend constexpr bool operator==(int a, const Id& b) { return b == a; } | |
| friend constexpr bool operator!=(const Id& a, int b) { return !(a == b); } | |
| friend constexpr bool operator!=(int a, const Id& b) { return !(a == b); } | |
| friend constexpr bool operator==(const Id& a, uint32_t b) | |
| { | |
| if (b == 0xFFFFFFFFu) | |
| return a.empty(); | |
| return a.client == 0 && a.object == b; | |
| } | |
| friend constexpr bool operator==(uint32_t a, const Id& b) { return b == a; } | |
| friend constexpr bool operator!=(const Id& a, uint32_t b) | |
| { | |
| return !(a == b); | |
| } | |
| friend constexpr bool operator!=(uint32_t a, const Id& b) | |
| { | |
| return !(a == b); | |
| } | |
| // Implicit narrowing to `uint32_t` so runtime-shaped code that | |
| // returns Ids from `uint32_t`-typed APIs (`animation/*.hpp` | |
| // getters, `rive_binding.cpp` callback scaffolding, `size_t` | |
| // bounds checks on vertex / object arrays, etc.) keeps compiling | |
| // against the editor-mode struct. This drops the `client` field — | |
| // which is meaningful only for coop (non-runtime) Ids. For every | |
| // synthesized-from-runtime Id (`client == 0`) the object-field | |
| // IS the runtime index, so the conversion is lossless; for true | |
| // CoopIds it is lossy, and any path that cares about the client | |
| // must take `const Id&` (or access `.client` / `.object` | |
| // explicitly) rather than accept a `uint32_t` here. The pattern | |
| // matches the Dart editor, where runtime-typed APIs use the | |
| // `Id.object` field and editor code branches on `client`. | |
| constexpr operator uint32_t() const { return object; } | |
| }; | |
| constexpr Id kEmptyId = {}; | |
| #else | |
| using Id = uint32_t; | |
| // Matches the `initialValueRuntime: "-1"` convention used across all | |
| // Id-typed fields in the JSON defs — UINT32_MAX as unsigned. | |
| constexpr Id kEmptyId = 0xFFFFFFFFu; | |
| #endif | |
| } // namespace rive | |
| #endif |