blob: e2fbc00050c018a3afc74fd7e6948b6caadc01ee [file]
#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;
}
// 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();
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);
SemanticDirt m_dirt = SemanticDirt::All;
SemanticsDiff m_lastDiff;
std::vector<SemanticsDiffNode> m_lastFlatSnapshot;
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