| #ifndef _RIVE_SEMANTIC_MANAGER_HPP_ | |
| #define _RIVE_SEMANTIC_MANAGER_HPP_ | |
| #include "rive/refcnt.hpp" | |
| #include "rive/semantic/semantic_dirt.hpp" | |
| #include "rive/semantic/semantic_node.hpp" | |
| #include "rive/semantic/semantic_snapshot.hpp" | |
| #include <cstdint> | |
| #include <unordered_map> | |
| #include <unordered_set> | |
| #include <vector> | |
| namespace rive | |
| { | |
| class Artboard; | |
| // Owns the semantic tree, tracks dirty state, and produces incremental | |
| // SemanticsDiff payloads. Created by StateMachineInstance, populated by | |
| // Artboard::buildSemanticTree(), consumed via SemanticManager::drainDiff(). | |
| class SemanticManager | |
| { | |
| public: | |
| explicit SemanticManager() {} | |
| void addChild(rcp<SemanticNode> parentNode, rcp<SemanticNode> child); | |
| void removeChild(rcp<SemanticNode> node); | |
| void markDirty(SemanticDirt dirt = SemanticDirt::All) | |
| { | |
| m_dirt = m_dirt | dirt; | |
| } | |
| void markNodeDirty(uint32_t id, SemanticDirt dirt = SemanticDirt::All); | |
| /// Mark a boundary node as having a dirty host transform. During the | |
| /// next drainDiff(), bounds will be re-read for all semantic nodes under | |
| /// this boundary instead of polling all nodes globally. | |
| void markBoundaryDirty(uint32_t boundaryNodeId); | |
| bool isDirty() const { return m_dirt != SemanticDirt::None; } | |
| uint64_t version() const { return m_version; } | |
| // Look up a SemanticNode by its unique ID. Returns nullptr if not found. | |
| SemanticNode* nodeById(uint32_t id) const | |
| { | |
| auto it = m_nodesById.find(id); | |
| return it != m_nodesById.end() ? it->second.get() : nullptr; | |
| } | |
| /// True when the node is in the tree the platform was last given: the | |
| /// snapshot of the most recent flatten. Hidden nodes stay registered, so | |
| /// anything acting on a node id from the platform checks this first. | |
| bool isInTree(const SemanticNode* node) const; | |
| // Request focus on the FocusData sibling of the SemanticData that owns | |
| // the given semantic node ID. Returns true if focus was set. | |
| bool requestFocus(uint32_t semanticNodeId); | |
| // Rebuild the tree if dirty and return the diff since the last call. | |
| // Returns an empty diff when nothing changed. Clears internal state so | |
| // the next call starts fresh. | |
| SemanticsDiff drainDiff(); | |
| #ifdef TESTING | |
| size_t nodeCount() const { return m_nodesById.size(); } | |
| #endif | |
| /// Returns the current semantic root when the tree the platform sees has | |
| /// exactly one root. | |
| /// | |
| /// @return The unique root node ID, or `0` when the tree has zero or | |
| /// multiple roots. | |
| uint32_t rootId() const; | |
| /// Returns artboard-space bounds for every node in the current flat | |
| /// snapshot. | |
| /// | |
| /// Command adapters use this to republish platform-space geometry when only | |
| /// their external viewport transform changes. | |
| std::vector<SemanticsBoundsUpdate> boundsSnapshot() const; | |
| private: | |
| void refresh(); | |
| SemanticsDiff consumeDiff(); | |
| void patchBoundsNode(SemanticsDiffNode& entry, SemanticsDiff& diff); | |
| void patchContentNode(SemanticsDiffNode& entry, SemanticsDiff& diff); | |
| void reconcileBoundsForSubtree(SemanticNode* node); | |
| // Sorts children of every node in the given forest by visual position | |
| // (top-to-bottom, left-to-right), recursively. Defined here (as a | |
| // private static member rather than a free helper) so it can use | |
| // SemanticNode::mutableChildren() through the friend relationship. | |
| static void sortChildrenByVisualPosition( | |
| std::vector<rcp<SemanticNode>>& nodes); | |
| // Assign a manager-local id to a freshly registered node (id == 0), | |
| // or reconcile the watermark for an explicitly-provided id. Called from | |
| // addChild() before the node is inserted in m_nodesById. | |
| void ensureNodeId(SemanticNode& node); | |
| void indexSnapshot(); | |
| SemanticDirt m_dirt = SemanticDirt::All; | |
| SemanticsDiff m_lastDiff; | |
| std::vector<SemanticsDiffNode> m_lastFlatSnapshot; | |
| // Ids in m_lastFlatSnapshot, and its single root (0 when it has none or | |
| // several). Kept by indexSnapshot(). | |
| std::unordered_set<uint32_t> m_snapshotIds; | |
| uint32_t m_rootId = 0; | |
| uint64_t m_version = 0; | |
| // Next id for auto-assignment; starts at 1 so 0 can remain an | |
| // "unassigned" sentinel. Scoped per-manager so two unrelated managers | |
| // in the same process can both start fresh without colliding. | |
| uint32_t m_nextLocalId = 1; | |
| std::unordered_map<uint32_t, rcp<SemanticNode>> m_nodesById; | |
| std::vector<rcp<SemanticNode>> m_roots; | |
| std::unordered_set<uint32_t> m_dirtyContentNodes; | |
| std::unordered_set<uint32_t> m_dirtyBoundsNodes; | |
| std::unordered_set<uint32_t> m_dirtyBoundaryIds; | |
| // Label derivation state from the last full flatten. Interactive nodes | |
| // that derived their label from children have entries in m_derivedLabels. | |
| // Children that were absorbed (flattened) are tracked in m_excludedIds | |
| // so the incremental path can detect when re-derivation is needed. | |
| std::unordered_map<uint32_t, std::string> m_derivedLabels; | |
| std::unordered_set<uint32_t> m_excludedIds; | |
| }; | |
| } // namespace rive | |
| #endif |