Optimize traversal and reduce string allocations in Stage and Tydra APIs

Performance optimizations for path traversal and prim lookup:

Stage (stage.cc):
- Replace recursive GetPrimAtPathRec with iterative GetPrimAtPathIterative
- Avoid string concatenation by using compare() with indices
- Direct path component parsing without intermediate allocations

Scene Access (scene-access.cc):
- Replace recursive TraverseRec with iterative TraverseIterative
- Use explicit stack with StackVector to avoid recursion overhead
- Reuse path buffer across traversal to minimize string allocations
- Reserve capacity for path buffer (256) and stack (64)

Additional optimizations:
- layer.cc: Improved path handling
- prim-types.cc: Optimized type operations
- usd-to-json.cc: Better JSON serialization
- usdGeom.cc, usdMtlx.cc: Enhanced prim reconstruction

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Syoyo Fujita
2025-12-14 02:45:18 +09:00
parent b1aafd6436
commit 0ba4ffa43a
7 changed files with 1065 additions and 476 deletions

View File

@@ -5,6 +5,7 @@
#include "prim-types.hh" // For PrimSpec, LayerMetas, etc.
#include "path-util.hh" // For Path
#include "str-util.hh" // For split function
#include "tiny-container.hh"
#include "common-macros.inc"
#include "tiny-format.hh"
@@ -16,169 +17,163 @@ namespace tinyusdz {
namespace {
// Forward declare helper functions
bool HasReferencesRec(uint32_t depth, const PrimSpec &primspec, const uint32_t max_depth);
bool HasPayloadRec(uint32_t depth, const PrimSpec &primspec, const uint32_t max_depth);
bool HasVariantRec(uint32_t depth, const PrimSpec &primspec, const uint32_t max_depth);
bool HasInheritsRec(uint32_t depth, const PrimSpec &primspec, const uint32_t max_depth);
bool HasSpecializesRec(uint32_t depth, const PrimSpec &primspec, const uint32_t max_depth);
bool HasOverRec(uint32_t depth, const PrimSpec &primspec, const uint32_t max_depth);
nonstd::optional<const PrimSpec *> GetPrimSpecAtPathRec(
const PrimSpec *parent, const std::string &parent_path, const Path &path,
uint32_t depth);
bool HasReferencesRec(uint32_t depth, const PrimSpec &primspec,
const uint32_t max_depth = 1024 * 128) {
if (depth > max_depth) {
// too deep
return false;
}
if (primspec.metas().references) {
// Generic iterative tree search using explicit stack
// Returns true if any PrimSpec satisfies the predicate
template <typename Predicate>
bool HasPrimSpecWithCondition(const PrimSpec &root, Predicate pred) {
// Check root first
if (pred(root)) {
return true;
}
for (auto &child : primspec.children()) {
if (HasReferencesRec(depth + 1, child, max_depth)) {
// Use explicit stack for DFS traversal (StackVector for stack allocation)
StackVector<std::pair<const PrimSpec *, size_t>, 4> stack;
stack.reserve(64);
if (!root.children().empty()) {
stack.emplace_back(&root, 0);
}
while (!stack.empty()) {
auto &top = stack.back();
const PrimSpec *current = top.first;
size_t &child_idx = top.second;
if (child_idx >= current->children().size()) {
stack.pop_back();
continue;
}
const PrimSpec &child = current->children()[child_idx];
++child_idx;
if (pred(child)) {
return true;
}
if (!child.children().empty()) {
stack.emplace_back(&child, 0);
}
}
return false;
}
bool HasPayloadRec(uint32_t depth, const PrimSpec &primspec,
const uint32_t max_depth = 1024 * 128) {
if (depth > max_depth) {
// too deep
return false;
}
if (primspec.metas().payload) {
return true;
}
for (auto &child : primspec.children()) {
if (HasPayloadRec(depth + 1, child, max_depth)) {
return true;
}
}
return false;
// Iterative predicates for each Has* function
bool HasReferencesIterative(const PrimSpec &primspec) {
return HasPrimSpecWithCondition(primspec, [](const PrimSpec &ps) {
return ps.metas().references.has_value();
});
}
bool HasVariantRec(uint32_t depth, const PrimSpec &primspec,
const uint32_t max_depth = 1024 * 128) {
if (depth > max_depth) {
// too deep
return false;
}
// TODO: Also check if PrimSpec::variantSets is empty?
if (primspec.metas().variants && primspec.metas().variantSets) {
return true;
}
for (auto &child : primspec.children()) {
if (HasVariantRec(depth + 1, child, max_depth)) {
return true;
}
}
return false;
bool HasPayloadIterative(const PrimSpec &primspec) {
return HasPrimSpecWithCondition(primspec, [](const PrimSpec &ps) {
return ps.metas().payload.has_value();
});
}
bool HasInheritsRec(uint32_t depth, const PrimSpec &primspec,
const uint32_t max_depth = 1024 * 128) {
if (depth > max_depth) {
// too deep
return false;
}
if (primspec.metas().inherits) {
return true;
}
for (auto &child : primspec.children()) {
if (HasInheritsRec(depth + 1, child, max_depth)) {
return true;
}
}
return false;
bool HasVariantIterative(const PrimSpec &primspec) {
return HasPrimSpecWithCondition(primspec, [](const PrimSpec &ps) {
// TODO: Also check if PrimSpec::variantSets is empty?
return ps.metas().variants.has_value() && ps.metas().variantSets.has_value();
});
}
bool HasSpecializesRec(uint32_t depth, const PrimSpec &primspec,
const uint32_t max_depth = 1024 * 128) {
if (depth > max_depth) {
// too deep
return false;
}
if (primspec.metas().specializes) {
return true;
}
for (auto &child : primspec.children()) {
if (HasSpecializesRec(depth + 1, child, max_depth)) {
return true;
}
}
return false;
bool HasInheritsIterative(const PrimSpec &primspec) {
return HasPrimSpecWithCondition(primspec, [](const PrimSpec &ps) {
return ps.metas().inherits.has_value();
});
}
bool HasOverRec(uint32_t depth, const PrimSpec &primspec,
const uint32_t max_depth = 1024 * 128) {
if (depth > max_depth) {
// too deep
return false;
}
if (primspec.specifier() == Specifier::Over) {
return true;
}
for (auto &child : primspec.children()) {
if (HasOverRec(depth + 1, child, max_depth)) {
return true;
}
}
return false;
bool HasSpecializesIterative(const PrimSpec &primspec) {
return HasPrimSpecWithCondition(primspec, [](const PrimSpec &ps) {
return ps.metas().specializes.has_value();
});
}
nonstd::optional<const PrimSpec *> GetPrimSpecAtPathRec(
const PrimSpec *parent, const std::string &parent_path, const Path &path,
uint32_t depth) {
if (depth > (1024 * 1024 * 128)) {
// Too deep.
bool HasOverIterative(const PrimSpec &primspec) {
return HasPrimSpecWithCondition(primspec, [](const PrimSpec &ps) {
return ps.specifier() == Specifier::Over;
});
}
// Optimized iterative path lookup starting from a single root PrimSpec
// Uses direct path component matching without string allocation
nonstd::optional<const PrimSpec *> GetPrimSpecAtPathFromRoot(
const PrimSpec &root, const Path &path) {
const std::string &target_path = path.full_path_name();
// Must be absolute path starting with '/'
if (target_path.empty() || target_path[0] != '/') {
return nonstd::nullopt;
}
if (!parent) {
// Root path "/" has no primspec
if (target_path.size() == 1) {
return nonstd::nullopt;
}
std::string abs_path;
{
std::string elementName = parent->name();
// Parse first component to check if it matches root
size_t start = 1; // skip leading '/'
const size_t len = target_path.size();
abs_path = parent_path + "/" + elementName;
if (abs_path == path.full_path_name()) {
return parent;
}
size_t end = start;
while (end < len && target_path[end] != '/') {
++end;
}
for (const auto &child : parent->children()) {
if (auto pv = GetPrimSpecAtPathRec(&child, abs_path, path, depth + 1)) {
return pv.value();
}
// Check if first component matches root name
const size_t first_len = end - start;
const std::string &root_name = root.name();
if (root_name.size() != first_len ||
target_path.compare(start, first_len, root_name) != 0) {
return nonstd::nullopt;
}
// not found
return nonstd::nullopt;
// If path is just the root, return it
if (end >= len) {
return &root;
}
// Navigate down the tree
const PrimSpec *current = &root;
start = end + 1;
while (start < len) {
// Find end of current component
end = start;
while (end < len && target_path[end] != '/') {
++end;
}
if (end == start) {
// Empty component (double slash), skip
start = end + 1;
continue;
}
// Search for matching child
const PrimSpec *found = nullptr;
const size_t component_len = end - start;
for (const auto &child : current->children()) {
const std::string &name = child.name();
if (name.size() == component_len &&
target_path.compare(start, component_len, name) == 0) {
found = &child;
break;
}
}
if (!found) {
return nonstd::nullopt;
}
current = found;
start = end + 1;
}
return current;
}
// Helper function to estimate PrimSpec memory usage
@@ -468,10 +463,9 @@ bool Layer::find_primspec_at(const Path &path, const PrimSpec **ps,
}
}
// Brute-force search.
// Direct path-based lookup (iterative, no string allocation)
for (const auto &parent : _impl->_prim_specs) {
if (auto pv = GetPrimSpecAtPathRec(&parent.second, /* parent_path */ "",
path, /* depth */ 0)) {
if (auto pv = GetPrimSpecAtPathFromRoot(parent.second, path)) {
(*ps) = pv.value();
// Add to cache.
@@ -487,10 +481,11 @@ bool Layer::find_primspec_at(const Path &path, const PrimSpec **ps,
}
bool Layer::check_unresolved_references(const uint32_t max_depth) const {
(void)max_depth; // Not needed for iterative version
bool ret = false;
for (const auto &item : _impl->_prim_specs) {
if (HasReferencesRec(/* depth */ 0, item.second, max_depth)) {
if (HasReferencesIterative(item.second)) {
ret = true;
break;
}
@@ -501,10 +496,11 @@ bool Layer::check_unresolved_references(const uint32_t max_depth) const {
}
bool Layer::check_unresolved_payload(const uint32_t max_depth) const {
(void)max_depth; // Not needed for iterative version
bool ret = false;
for (const auto &item : _impl->_prim_specs) {
if (HasPayloadRec(/* depth */ 0, item.second, max_depth)) {
if (HasPayloadIterative(item.second)) {
ret = true;
break;
}
@@ -515,10 +511,11 @@ bool Layer::check_unresolved_payload(const uint32_t max_depth) const {
}
bool Layer::check_unresolved_variant(const uint32_t max_depth) const {
(void)max_depth; // Not needed for iterative version
bool ret = false;
for (const auto &item : _impl->_prim_specs) {
if (HasVariantRec(/* depth */ 0, item.second, max_depth)) {
if (HasVariantIterative(item.second)) {
ret = true;
break;
}
@@ -529,10 +526,11 @@ bool Layer::check_unresolved_variant(const uint32_t max_depth) const {
}
bool Layer::check_unresolved_inherits(const uint32_t max_depth) const {
(void)max_depth; // Not needed for iterative version
bool ret = false;
for (const auto &item : _impl->_prim_specs) {
if (HasInheritsRec(/* depth */ 0, item.second, max_depth)) {
if (HasInheritsIterative(item.second)) {
ret = true;
break;
}
@@ -543,10 +541,11 @@ bool Layer::check_unresolved_inherits(const uint32_t max_depth) const {
}
bool Layer::check_unresolved_specializes(const uint32_t max_depth) const {
(void)max_depth; // Not needed for iterative version
bool ret = false;
for (const auto &item : _impl->_prim_specs) {
if (HasSpecializesRec(/* depth */ 0, item.second, max_depth)) {
if (HasSpecializesIterative(item.second)) {
ret = true;
break;
}
@@ -557,10 +556,11 @@ bool Layer::check_unresolved_specializes(const uint32_t max_depth) const {
}
bool Layer::check_over_primspec(const uint32_t max_depth) const {
(void)max_depth; // Not needed for iterative version
bool ret = false;
for (const auto &item : _impl->_prim_specs) {
if (HasOverRec(/* depth */ 0, item.second, max_depth)) {
if (HasOverIterative(item.second)) {
ret = true;
break;
}

View File

@@ -7,6 +7,7 @@
//
#include "prim-types.hh"
#include "str-util.hh"
#include "tiny-container.hh"
#include "tiny-format.hh"
//
#include "usdGeom.hh"
@@ -1547,48 +1548,52 @@ bool GetCustomDataByKey(const CustomDataType &custom, const std::string &key,
namespace {
bool OverrideCustomDataRec(uint32_t depth, CustomDataType &dst,
const CustomDataType &src, const bool override_existing) {
if (depth > (1024 * 1024 * 128)) {
// too deep
return false;
}
// Iterative version of dictionary override using explicit stack
// Avoids recursion for deeply nested dictionary structures
void OverrideCustomDataIterative(CustomDataType &dst, const CustomDataType &src,
const bool override_existing) {
// Stack of pairs: (dst_dict pointer, src_dict pointer)
StackVector<std::pair<CustomDataType *, const CustomDataType *>, 4> stack;
stack.reserve(16);
for (const auto &item : src) {
if (dst.count(item.first)) {
if (override_existing) {
CustomDataType *dst_dict =
dst.at(item.first).get_raw_value().as<CustomDataType>();
// Start with the root dictionaries
stack.emplace_back(&dst, &src);
const value::Value &src_data = item.second.get_raw_value();
const CustomDataType *src_dict = src_data.as<CustomDataType>();
while (!stack.empty()) {
auto current = stack.back();
stack.pop_back();
//
// Recursively apply override op both types are dict.
//
if (src_dict && dst_dict) {
// recursively override dict
if (!OverrideCustomDataRec(depth + 1, (*dst_dict), (*src_dict), override_existing)) {
return false;
CustomDataType *current_dst = current.first;
const CustomDataType *current_src = current.second;
for (const auto &item : *current_src) {
if (current_dst->count(item.first)) {
if (override_existing) {
CustomDataType *dst_dict =
current_dst->at(item.first).get_raw_value().as<CustomDataType>();
const value::Value &src_data = item.second.get_raw_value();
const CustomDataType *src_dict = src_data.as<CustomDataType>();
// If both are dicts, push to stack for later processing
if (src_dict && dst_dict) {
stack.emplace_back(dst_dict, src_dict);
} else {
(*current_dst)[item.first] = item.second;
}
} else {
dst[item.first] = item.second;
}
} else {
// add dict value
current_dst->emplace(item.first, item.second);
}
} else {
// add dict value
dst.emplace(item.first, item.second);
}
}
return true;
}
} // namespace
void OverrideDictionary(CustomDataType &dst, const CustomDataType &src, const bool override_existing) {
OverrideCustomDataRec(0, dst, src, override_existing);
OverrideCustomDataIterative(dst, src, override_existing);
}
AssetInfo PrimMeta::get_assetInfo(bool *is_authored) const {

View File

@@ -27,6 +27,7 @@
#include "prim-pprint.hh"
#include "prim-pprint-parallel.hh"
#include "str-util.hh"
#include "tiny-container.hh"
#include "tiny-format.hh"
#include "tinyusdz.hh"
#include "usdLux.hh"
@@ -46,44 +47,69 @@ namespace tinyusdz {
namespace {
nonstd::optional<const Prim *> GetPrimAtPathRec(const Prim *parent,
const std::string &parent_path,
const Path &path,
const uint32_t depth) {
// Optimized version: iterative traversal using path components
// Avoids string concatenation and recursion by directly navigating to target
nonstd::optional<const Prim *> GetPrimAtPathIterative(
const std::vector<Prim> &root_nodes,
const Path &path) {
if (!parent) {
const std::string &target_path = path.full_path_name();
// Must be absolute path starting with '/'
if (target_path.empty() || target_path[0] != '/') {
return nonstd::nullopt;
}
std::string abs_path;
// if (auto pv = GetPrimElementName(parent->data())) {
{
std::string elementName = parent->element_path().prim_part();
// DCOUT(pprint::Indent(depth) << "Prim elementName = " << elementName);
// DCOUT(pprint::Indent(depth) << "Given Path = " << path);
// fully absolute path
abs_path = parent_path + "/" + elementName;
// DCOUT(pprint::Indent(depth) << "abs_path = " << abs_path);
// DCOUT(pprint::Indent(depth)
// << "queriying path = " << path.full_path_name());
if (abs_path == path.full_path_name()) {
// DCOUT(pprint::Indent(depth)
// << "Got it! Found Prim at Path = " << abs_path);
return parent;
}
// For paths like "/" only (root path), we don't have a prim
if (target_path.size() == 1) {
return nonstd::nullopt;
}
// DCOUT(pprint::Indent(depth)
// << "# of children : " << parent->children().size());
for (const auto &child : parent->children()) {
// const std::string &p = parent->elementPath.full_path_name();
// DCOUT(pprint::Indent(depth + 1) << "Parent path : " << abs_path);
if (auto pv = GetPrimAtPathRec(&child, abs_path, path, depth + 1)) {
return pv.value();
// Iteratively parse and traverse path components
// No string allocations - uses compare() with indices
const Prim *current = nullptr;
const std::vector<Prim> *current_children = &root_nodes;
size_t start = 1; // skip leading '/'
const size_t len = target_path.size();
while (start < len) {
// Find end of current component
size_t end = start;
while (end < len && target_path[end] != '/') {
++end;
}
if (end == start) {
// Empty component (double slash), skip
start = end + 1;
continue;
}
// Search for matching child using direct string comparison
// No string allocation - compare against substring
const Prim *found = nullptr;
const size_t component_len = end - start;
for (const auto &child : *current_children) {
const std::string &name = child.element_name();
if (name.size() == component_len &&
target_path.compare(start, component_len, name) == 0) {
found = &child;
break;
}
}
if (!found) {
return nonstd::nullopt;
}
current = found;
current_children = &current->children();
start = end + 1;
}
return nonstd::nullopt;
return current;
}
} // namespace
@@ -130,15 +156,12 @@ nonstd::expected<const Prim *, std::string> Stage::GetPrimAtPath(
}
// Brute-force search.
for (const auto &parent : _root_nodes) {
if (auto pv =
GetPrimAtPathRec(&parent, /* root */ "", path, /* depth */ 0)) {
// Add to cache.
// Assume pointer address does not change unless dirty state.
_prim_path_cache[path.prim_part()] = pv.value();
return pv.value();
}
// Direct path-based lookup (no brute-force search)
if (auto pv = GetPrimAtPathIterative(_root_nodes, path)) {
// Add to cache.
// Assume pointer address does not change unless dirty state.
_prim_path_cache[path.prim_part()] = pv.value();
return pv.value();
}
DCOUT("Not found.");
@@ -181,36 +204,61 @@ bool Stage::find_prim_at_path(const Path &path, int64_t *prim_id,
}
}
namespace {
bool FindPrimByPrimIdRec(uint64_t prim_id, const Prim *root,
const Prim **primFound, int level, std::string *err) {
if (level > 1024 * 1024 * 128) {
// too deep node.
return false;
}
// Optimized iterative version using explicit stack
// Avoids recursion to save stack memory for deep hierarchies
static bool FindPrimByPrimIdIterative(uint64_t prim_id,
const std::vector<Prim> &root_nodes,
const Prim **primFound) {
if (!primFound) {
return false;
}
if (root->prim_id() == int64_t(prim_id)) {
(*primFound) = root;
return true;
// Use explicit stack for DFS traversal (StackVector for stack allocation)
// Store pointer to prim and current child index
StackVector<std::pair<const Prim *, size_t>, 4> stack;
stack.reserve(64); // Pre-allocate for typical depth
// Initialize stack with root nodes
for (const auto &root : root_nodes) {
if (root.prim_id() == int64_t(prim_id)) {
(*primFound) = &root;
return true;
}
if (!root.children().empty()) {
stack.emplace_back(&root, 0);
}
}
// Brute-force search.
for (const auto &child : root->children()) {
if (FindPrimByPrimIdRec(prim_id, &child, primFound, level + 1, err)) {
// Iterative DFS
while (!stack.empty()) {
auto &top = stack.back();
const Prim *current = top.first;
size_t &child_idx = top.second;
if (child_idx >= current->children().size()) {
// All children processed, backtrack
stack.pop_back();
continue;
}
const Prim &child = current->children()[child_idx];
++child_idx; // Move to next child for when we return
// Check if this is the target
if (child.prim_id() == int64_t(prim_id)) {
(*primFound) = &child;
return true;
}
// Push child to stack if it has children
if (!child.children().empty()) {
stack.emplace_back(&child, 0);
}
}
return false;
}
} // namespace
bool Stage::find_prim_by_prim_id(const uint64_t prim_id, const Prim *&prim,
std::string *err) const {
if (prim_id < 1) {
@@ -237,12 +285,10 @@ bool Stage::find_prim_by_prim_id(const uint64_t prim_id, const Prim *&prim,
}
const Prim *p{nullptr};
for (const auto &root : root_prims()) {
if (FindPrimByPrimIdRec(prim_id, &root, &p, 0, err)) {
_prim_id_cache[prim_id] = p;
prim = p;
return true;
}
if (FindPrimByPrimIdIterative(prim_id, _root_nodes, &p)) {
_prim_id_cache[prim_id] = p;
prim = p;
return true;
}
return false;
@@ -577,49 +623,98 @@ bool Stage::has_prim_id(const uint64_t prim_id) const {
namespace {
bool ComputeAbsPathAndAssignPrimIdRec(const Stage &stage, Prim &prim,
const Path &parentPath, uint32_t depth,
bool assign_prim_id,
bool force_assign_prim_id = true,
std::string *err = nullptr) {
if (depth > 1024 * 1024 * 128) {
// too deep node.
if (err) {
(*err) += "Prim hierarchy too deep.\n";
// Iterative version of ComputeAbsPathAndAssignPrimIdRec
// Uses explicit stack to avoid recursion
bool ComputeAbsPathAndAssignPrimIdIterative(const Stage &stage,
std::vector<Prim> &root_prims,
bool assign_prim_id,
bool force_assign_prim_id,
std::string *err) {
// Stack entry: (prim pointer, parent path, child index)
struct StackEntry {
Prim *prim;
Path parent_path;
size_t child_idx;
StackEntry(Prim *p, Path pp) : prim(p), parent_path(std::move(pp)), child_idx(0) {}
};
StackVector<StackEntry, 4> stack;
stack.reserve(64);
Path root_path("/", "");
// Process each root prim
for (Prim &root : root_prims) {
if (root.element_name().empty()) {
if (err) {
(*err) += "Prim's elementName is empty. Prim's parent Path = /\n";
}
return false;
}
return false;
}
if (prim.element_name().empty()) {
// Prim's elementName must not be empty.
if (err) {
(*err) += "Prim's elementName is empty. Prim's parent Path = " +
parentPath.full_path_name() + "\n";
// Compute path and assign ID for root
Path abs_path = root_path.AppendPrim(root.element_name());
root.absolute_path() = abs_path;
if (assign_prim_id) {
if (force_assign_prim_id || (root.prim_id() < 1)) {
uint64_t prim_id{0};
if (!stage.allocate_prim_id(&prim_id)) {
if (err) {
(*err) += "Failed to assign unique Prim ID.\n";
}
return false;
}
root.prim_id() = int64_t(prim_id);
}
}
return false;
}
Path abs_path = parentPath.AppendPrim(prim.element_name());
if (!root.children().empty()) {
stack.emplace_back(&root, abs_path);
}
prim.absolute_path() = abs_path;
if (assign_prim_id) {
if (force_assign_prim_id || (prim.prim_id() < 1)) {
uint64_t prim_id{0};
if (!stage.allocate_prim_id(&prim_id)) {
// Process tree iteratively
while (!stack.empty()) {
auto &top = stack.back();
Prim *current = top.prim;
size_t &child_idx = top.child_idx;
if (child_idx >= current->children().size()) {
stack.pop_back();
continue;
}
Prim &child = current->children()[child_idx];
++child_idx;
if (child.element_name().empty()) {
if (err) {
(*err) += "Failed to assign unique Prim ID.\n";
(*err) += "Prim's elementName is empty. Prim's parent Path = " +
top.parent_path.full_path_name() + "\n";
}
return false;
}
prim.prim_id() = int64_t(prim_id);
}
}
for (Prim &child : prim.children()) {
if (!ComputeAbsPathAndAssignPrimIdRec(stage, child, abs_path, depth + 1,
assign_prim_id, force_assign_prim_id,
err)) {
return false;
Path child_abs_path = top.parent_path.AppendPrim(child.element_name());
child.absolute_path() = child_abs_path;
if (assign_prim_id) {
if (force_assign_prim_id || (child.prim_id() < 1)) {
uint64_t prim_id{0};
if (!stage.allocate_prim_id(&prim_id)) {
if (err) {
(*err) += "Failed to assign unique Prim ID.\n";
}
return false;
}
child.prim_id() = int64_t(prim_id);
}
}
if (!child.children().empty()) {
stack.emplace_back(&child, child_abs_path);
}
}
}
@@ -630,13 +725,10 @@ bool ComputeAbsPathAndAssignPrimIdRec(const Stage &stage, Prim &prim,
bool Stage::compute_absolute_prim_path_and_assign_prim_id(
bool force_assign_prim_id) {
Path rootPath("/", "");
for (Prim &root : root_prims()) {
if (!ComputeAbsPathAndAssignPrimIdRec(*this, root, rootPath, 1,
/* assign_prim_id */ true,
force_assign_prim_id, &_err)) {
return false;
}
if (!ComputeAbsPathAndAssignPrimIdIterative(*this, _root_nodes,
/* assign_prim_id */ true,
force_assign_prim_id, &_err)) {
return false;
}
// TODO: Only set dirty when prim_id changed.
@@ -646,13 +738,11 @@ bool Stage::compute_absolute_prim_path_and_assign_prim_id(
}
bool Stage::compute_absolute_prim_path() {
Path rootPath("/", "");
for (Prim &root : root_prims()) {
if (!ComputeAbsPathAndAssignPrimIdRec(
*this, root, rootPath, 1, /* assign prim_id */ false,
/* force_assign_prim_id */ true, &_err)) {
return false;
}
if (!ComputeAbsPathAndAssignPrimIdIterative(*this, _root_nodes,
/* assign_prim_id */ false,
/* force_assign_prim_id */ true,
&_err)) {
return false;
}
return true;
@@ -782,21 +872,57 @@ bool Stage::replace_root_prim(const std::string &prim_name, Prim &&prim) {
namespace {
std::string DumpPrimTreeRec(const Prim &prim, uint32_t depth) {
// Iterative version of DumpPrimTree using explicit stack
// Avoids recursion to save stack memory for deep hierarchies
std::string DumpPrimTreeIterative(const std::vector<Prim> &root_prims) {
std::stringstream ss;
if (depth > 1024 * 1024 * 128) {
// too deep node.
return ss.str();
// Stack entries: (prim pointer, depth, child index)
// child_idx == SIZE_MAX means we haven't processed this node yet
struct StackEntry {
const Prim *prim;
uint32_t depth;
size_t child_idx;
StackEntry(const Prim *p, uint32_t d) : prim(p), depth(d), child_idx(SIZE_MAX) {}
};
StackVector<StackEntry, 4> stack;
stack.reserve(64);
// Push root prims in reverse order to process in forward order
for (auto it = root_prims.rbegin(); it != root_prims.rend(); ++it) {
stack.emplace_back(&(*it), 0);
}
ss << pprint::Indent(depth) << "\"" << prim.element_name() << "\" "
<< prim.absolute_path() << "\n";
ss << pprint::Indent(depth + 1) << fmt::format("prim_id {}", prim.prim_id())
<< "\n";
constexpr uint32_t kMaxDepth = 1024 * 1024 * 128;
for (const Prim &child : prim.children()) {
ss << DumpPrimTreeRec(child, depth + 1);
while (!stack.empty()) {
StackEntry &entry = stack.back();
if (entry.depth > kMaxDepth) {
stack.pop_back();
continue;
}
if (entry.child_idx == SIZE_MAX) {
// First visit: output this node
ss << pprint::Indent(entry.depth) << "\"" << entry.prim->element_name() << "\" "
<< entry.prim->absolute_path() << "\n";
ss << pprint::Indent(entry.depth + 1) << fmt::format("prim_id {}", entry.prim->prim_id())
<< "\n";
entry.child_idx = 0;
}
// Process children
const auto &children = entry.prim->children();
if (entry.child_idx < children.size()) {
// Push next child and increment index
size_t idx = entry.child_idx++;
stack.emplace_back(&children[idx], entry.depth + 1);
} else {
// All children processed, pop this node
stack.pop_back();
}
}
return ss.str();
@@ -805,12 +931,7 @@ std::string DumpPrimTreeRec(const Prim &prim, uint32_t depth) {
} // namespace
std::string Stage::dump_prim_tree() const {
std::stringstream ss;
for (const Prim &root : root_prims()) {
ss << DumpPrimTreeRec(root, 0);
}
return ss.str();
return DumpPrimTreeIterative(_root_nodes);
}
size_t Stage::estimate_memory_usage() const {

View File

@@ -8,6 +8,7 @@
#include "prim-pprint.hh"
#include "prim-types.hh"
#include "primvar.hh"
#include "tiny-container.hh"
#include "tiny-format.hh"
#include "tydra/prim-apply.hh"
#include "usdGeom.hh"
@@ -108,57 +109,135 @@ value::TimeSamples EnumTimeSamplesToTypelessTimeSamples(
return dst;
}
// Optimized iterative traversal using explicit stack
// Avoids recursion and reuses path buffer to minimize string allocations
template <typename T>
bool TraverseRec(const std::string &path_prefix, const tinyusdz::Prim &prim,
uint32_t depth, PathPrimMap<T> &itemmap) {
if (depth > 1024 * 128) {
// Too deep
return false;
}
bool TraverseIterative(const tinyusdz::Prim &root_prim, PathPrimMap<T> &itemmap) {
// Stack stores: (prim pointer, child index, path length before this prim)
StackVector<std::tuple<const tinyusdz::Prim *, size_t, size_t>, 4> stack;
stack.reserve(64);
std::string prim_abs_path =
path_prefix + "/" + prim.local_path().full_path_name();
// Shared path buffer - reuse to avoid allocations
std::string path_buffer;
path_buffer.reserve(256);
if (prim.is<T>()) {
if (const T *pv = prim.as<T>()) {
DCOUT("Path : <" << prim_abs_path << "> is " << tinyusdz::value::TypeTraits<T>::type_name());
itemmap[prim_abs_path] = pv;
// Process root prim
path_buffer = "/" + root_prim.local_path().full_path_name();
if (root_prim.is<T>()) {
if (const T *pv = root_prim.as<T>()) {
DCOUT("Path : <" << path_buffer << "> is " << tinyusdz::value::TypeTraits<T>::type_name());
itemmap[path_buffer] = pv;
}
}
for (const auto &child : prim.children()) {
if (!TraverseRec(prim_abs_path, child, depth + 1, itemmap)) {
return false;
if (!root_prim.children().empty()) {
stack.emplace_back(&root_prim, 0, 0); // path_len=0 since "/" is implicit
}
while (!stack.empty()) {
auto &top = stack.back();
const tinyusdz::Prim *parent = std::get<0>(top);
size_t &child_idx = std::get<1>(top);
const size_t parent_path_len = std::get<2>(top);
if (child_idx >= parent->children().size()) {
// All children processed, backtrack
// Restore path to parent's length
path_buffer.resize(parent_path_len);
stack.pop_back();
continue;
}
const tinyusdz::Prim &child = parent->children()[child_idx];
++child_idx;
// Build path for this child
size_t current_path_len = path_buffer.size();
path_buffer += "/";
path_buffer += child.local_path().full_path_name();
// Check and add to map if type matches
if (child.is<T>()) {
if (const T *pv = child.as<T>()) {
DCOUT("Path : <" << path_buffer << "> is " << tinyusdz::value::TypeTraits<T>::type_name());
itemmap[path_buffer] = pv;
}
}
// Push child to stack if it has children
if (!child.children().empty()) {
stack.emplace_back(&child, 0, current_path_len);
} else {
// No children, restore path immediately
path_buffer.resize(current_path_len);
}
}
return true;
}
// Specialization for Shader type.
// Optimized iterative shader traversal using explicit stack
// Avoids recursion and reuses path buffer to minimize string allocations
template <typename ShaderTy>
bool TraverseShaderRec(const std::string &path_prefix,
const tinyusdz::Prim &prim, uint32_t depth,
PathShaderMap<ShaderTy> &itemmap) {
if (depth > 1024 * 128) {
// Too deep
return false;
}
bool TraverseShaderIterative(const tinyusdz::Prim &root_prim,
PathShaderMap<ShaderTy> &itemmap) {
// Stack stores: (prim pointer, child index, path length before this prim)
StackVector<std::tuple<const tinyusdz::Prim *, size_t, size_t>, 4> stack;
stack.reserve(64);
std::string prim_abs_path =
path_prefix + "/" + prim.local_path().full_path_name();
// Shared path buffer - reuse to avoid allocations
std::string path_buffer;
path_buffer.reserve(256);
// First check if type is Shader Prim.
if (const Shader *ps = prim.as<Shader>()) {
// Then check if wanted Shder type
// Process root prim
path_buffer = "/" + root_prim.local_path().full_path_name();
// Check if root is a Shader of the wanted type
if (const Shader *ps = root_prim.as<Shader>()) {
if (const ShaderTy *s = ps->value.as<ShaderTy>()) {
itemmap[prim_abs_path] = std::make_pair(ps, s);
itemmap[path_buffer] = std::make_pair(ps, s);
}
}
for (const auto &child : prim.children()) {
if (!TraverseShaderRec(prim_abs_path, child, depth + 1, itemmap)) {
return false;
if (!root_prim.children().empty()) {
stack.emplace_back(&root_prim, 0, 0);
}
while (!stack.empty()) {
auto &top = stack.back();
const tinyusdz::Prim *parent = std::get<0>(top);
size_t &child_idx = std::get<1>(top);
const size_t parent_path_len = std::get<2>(top);
if (child_idx >= parent->children().size()) {
// All children processed, backtrack
path_buffer.resize(parent_path_len);
stack.pop_back();
continue;
}
const tinyusdz::Prim &child = parent->children()[child_idx];
++child_idx;
// Build path for this child
size_t current_path_len = path_buffer.size();
path_buffer += "/";
path_buffer += child.local_path().full_path_name();
// Check if this is a Shader of the wanted type
if (const Shader *ps = child.as<Shader>()) {
if (const ShaderTy *s = ps->value.as<ShaderTy>()) {
itemmap[path_buffer] = std::make_pair(ps, s);
}
}
// Push child to stack if it has children
if (!child.children().empty()) {
stack.emplace_back(&child, 0, current_path_len);
} else {
// No children, restore path immediately
path_buffer.resize(current_path_len);
}
}
@@ -205,7 +284,7 @@ bool ListPrims(const tinyusdz::Stage &stage, PathPrimMap<T> &m /* output */) {
}
for (const auto &root_prim : stage.root_prims()) {
TraverseRec(/* root path is empty */ "", root_prim, /* depth */ 0, m);
TraverseIterative(root_prim, m);
}
return true;
@@ -232,7 +311,7 @@ bool ListShaders(const tinyusdz::Stage &stage,
}
for (const auto &root_prim : stage.root_prims()) {
TraverseShaderRec(/* root path is empty */ "", root_prim, /* depth */ 0, m);
TraverseShaderIterative(root_prim, m);
}
return true;
@@ -315,62 +394,144 @@ template bool ListShaders(const tinyusdz::Stage &stage,
namespace {
bool VisitPrimsRec(const tinyusdz::Path &root_abs_path,
const tinyusdz::Prim &root, int32_t level,
VisitPrimFunction visitor_fun, void *userdata,
std::string *err) {
std::string fun_error;
bool ret = visitor_fun(root_abs_path, root, level, userdata, &fun_error);
if (!ret) {
if (fun_error.empty()) {
// early termination request.
DCOUT("Early termination requested");
// Optimized iterative version of VisitPrimsRec
// Handles primChildren ordering and early termination
bool VisitPrimsIterative(const tinyusdz::Path &start_abs_path,
const tinyusdz::Prim &start_prim, int32_t start_level,
VisitPrimFunction visitor_fun, void *userdata,
std::string *err) {
// Stack entry: (prim pointer, ordered children to visit, current child index, level, parent path)
struct StackEntry {
const tinyusdz::Prim *prim;
std::vector<const tinyusdz::Prim *> ordered_children;
size_t child_idx;
int32_t level;
tinyusdz::Path abs_path;
StackEntry(const tinyusdz::Prim *p, int32_t lvl, tinyusdz::Path path)
: prim(p), child_idx(0), level(lvl), abs_path(std::move(path)) {}
};
StackVector<StackEntry, 4> stack;
stack.reserve(64);
// Helper to get ordered children list
auto get_ordered_children = [err](const tinyusdz::Prim &prim)
-> std::pair<bool, std::vector<const tinyusdz::Prim *>> {
std::vector<const tinyusdz::Prim *> result;
if (prim.children().empty()) {
return {true, result};
}
// If primChildren metadata matches children count, use it for ordering
if (prim.metas().primChildren.size() == prim.children().size()) {
std::map<std::string, const tinyusdz::Prim *> primNameTable;
for (size_t i = 0; i < prim.children().size(); i++) {
primNameTable.emplace(prim.children()[i].element_name(),
&prim.children()[i]);
}
for (size_t i = 0; i < prim.metas().primChildren.size(); i++) {
value::token nameTok = prim.metas().primChildren[i];
const auto it = primNameTable.find(nameTok.str());
if (it != primNameTable.end()) {
result.push_back(it->second);
} else {
if (err) {
(*err) += fmt::format(
"Prim name `{}` in `primChildren` metadatum not found in this "
"Prim's children",
nameTok.str());
}
return {false, {}};
}
}
} else {
if (err) {
(*err) += fmt::format(
"Visit function returned an error for Prim {} (id {}). err = {}",
root_abs_path.full_path_name(), root.prim_id(), fun_error);
// Use natural order
for (const auto &child : prim.children()) {
result.push_back(&child);
}
}
return false;
}
// if `primChildren` is available, use it
if (root.metas().primChildren.size() == root.children().size()) {
std::map<std::string, const Prim *> primNameTable;
for (size_t i = 0; i < root.children().size(); i++) {
primNameTable.emplace(root.children()[i].element_name(),
&root.children()[i]);
}
return {true, result};
};
for (size_t i = 0; i < root.metas().primChildren.size(); i++) {
value::token nameTok = root.metas().primChildren[i];
const auto it = primNameTable.find(nameTok.str());
if (it != primNameTable.end()) {
const Path child_abs_path = root_abs_path.AppendPrim(nameTok.str());
if (!VisitPrimsRec(child_abs_path, *it->second, level + 1, visitor_fun,
userdata, err)) {
return false;
}
// Visit start prim first
{
std::string fun_error;
bool ret = visitor_fun(start_abs_path, start_prim, start_level, userdata, &fun_error);
if (!ret) {
if (fun_error.empty()) {
DCOUT("Early termination requested");
} else {
if (err) {
(*err) += fmt::format(
"Prim name `{}` in `primChildren` metadatum not found in this "
"Prim's children",
nameTok.str());
"Visit function returned an error for Prim {} (id {}). err = {}",
start_abs_path.full_path_name(), start_prim.prim_id(), fun_error);
}
return false;
}
return false;
}
}
// Get ordered children for start prim
std::pair<bool, std::vector<const tinyusdz::Prim *>> start_result =
get_ordered_children(start_prim);
if (!start_result.first) {
return false;
}
if (!start_result.second.empty()) {
StackEntry entry(&start_prim, start_level, start_abs_path);
entry.ordered_children = std::move(start_result.second);
stack.push_back(std::move(entry));
}
// Iterative traversal
while (!stack.empty()) {
auto &top = stack.back();
if (top.child_idx >= top.ordered_children.size()) {
// All children processed, backtrack
stack.pop_back();
continue;
}
} else {
for (const auto &child : root.children()) {
const Path child_abs_path =
root_abs_path.AppendPrim(child.element_name());
if (!VisitPrimsRec(child_abs_path, child, level + 1, visitor_fun,
userdata, err)) {
return false;
const tinyusdz::Prim *child = top.ordered_children[top.child_idx];
++top.child_idx;
// Build path for this child
tinyusdz::Path child_abs_path = top.abs_path.AppendPrim(child->element_name());
int32_t child_level = top.level + 1;
// Call visitor
std::string fun_error;
bool ret = visitor_fun(child_abs_path, *child, child_level, userdata, &fun_error);
if (!ret) {
if (fun_error.empty()) {
DCOUT("Early termination requested");
} else {
if (err) {
(*err) += fmt::format(
"Visit function returned an error for Prim {} (id {}). err = {}",
child_abs_path.full_path_name(), child->prim_id(), fun_error);
}
}
return false;
}
// Get ordered children for this child
std::pair<bool, std::vector<const tinyusdz::Prim *>> child_result =
get_ordered_children(*child);
if (!child_result.first) {
return false;
}
if (!child_result.second.empty()) {
StackEntry entry(child, child_level, std::move(child_abs_path));
entry.ordered_children = std::move(child_result.second);
stack.push_back(std::move(entry));
}
}
@@ -1929,8 +2090,8 @@ bool VisitPrims(const tinyusdz::Stage &stage, VisitPrimFunction visitor_fun,
const auto it = primNameTable.find(nameTok.str());
if (it != primNameTable.end()) {
const Path root_abs_path("/" + nameTok.str(), "");
if (!VisitPrimsRec(root_abs_path, *it->second, 0, visitor_fun, userdata,
err)) {
if (!VisitPrimsIterative(root_abs_path, *it->second, 0, visitor_fun,
userdata, err)) {
return false;
}
} else {
@@ -1947,8 +2108,8 @@ bool VisitPrims(const tinyusdz::Stage &stage, VisitPrimFunction visitor_fun,
} else {
for (const auto &root : stage.root_prims()) {
const Path root_abs_path("/" + root.element_name(), /* prop part */ "");
if (!VisitPrimsRec(root_abs_path, root, /* root level */ 0, visitor_fun,
userdata, err)) {
if (!VisitPrimsIterative(root_abs_path, root, /* root level */ 0,
visitor_fun, userdata, err)) {
return false;
}
}
@@ -2132,20 +2293,12 @@ bool ListSceneNames(const tinyusdz::Prim &root,
namespace {
bool BuildXformNodeFromStageRec(
const tinyusdz::Stage &stage, const Path &parent_abs_path, const Prim *prim,
XformNode *nodeOut, /* out */
value::matrix4d rootMat, const double t,
const tinyusdz::value::TimeSampleInterpolationType tinterp) {
if (!nodeOut) {
return false;
}
XformNode node;
if (prim->element_name().empty()) {
// TODO: report error
}
// Helper to compute XformNode properties from a Prim
static void ComputeXformNodeProperties(
const Prim *prim, const Path &parent_abs_path,
const value::matrix4d &parent_world_mat, const double t,
const tinyusdz::value::TimeSampleInterpolationType tinterp,
XformNode &node) {
node.element_name = prim->element_name();
node.absolute_path = parent_abs_path.AppendPrim(prim->element_name());
@@ -2161,7 +2314,6 @@ bool BuildXformNodeFromStageRec(
GetLocalTransform(*prim, &resetXformStack, t, tinterp);
DCOUT("local mat = " << localMat);
value::matrix4d worldMat = rootMat;
node.has_resetXformStack() = resetXformStack;
value::matrix4d m;
@@ -2171,10 +2323,10 @@ bool BuildXformNodeFromStageRec(
m = localMat;
} else {
// matrix is row-major, so local first
m = localMat * worldMat;
m = localMat * parent_world_mat;
}
node.set_parent_world_matrix(rootMat);
node.set_parent_world_matrix(parent_world_mat);
node.set_local_matrix(localMat);
node.set_world_matrix(m);
node.has_xform() = true;
@@ -2182,46 +2334,131 @@ bool BuildXformNodeFromStageRec(
DCOUT("Not xformable");
node.has_xform() = false;
node.has_resetXformStack() = false;
node.set_parent_world_matrix(rootMat);
node.set_world_matrix(rootMat);
node.set_parent_world_matrix(parent_world_mat);
node.set_world_matrix(parent_world_mat);
node.set_local_matrix(value::matrix4d::identity());
}
}
for (const auto &childPrim : prim->children()) {
XformNode childNode;
if (!BuildXformNodeFromStageRec(stage, node.absolute_path, &childPrim,
&childNode, node.get_world_matrix(), t,
tinterp)) {
return false;
}
// Iterative version of BuildXformNodeFromStage using explicit stack
bool BuildXformNodeFromStageIterative(
const tinyusdz::Stage &stage, const Path &initial_parent_path, const Prim *root_prim,
XformNode *nodeOut, /* out */
value::matrix4d rootMat, const double t,
const tinyusdz::value::TimeSampleInterpolationType tinterp) {
childNode.parent = &node;
node.children.emplace_back(std::move(childNode));
(void)stage; // Currently unused
if (!nodeOut) {
return false;
}
(*nodeOut) = node;
// Stack entry for iterative processing
struct StackEntry {
const Prim *prim;
Path parent_path;
value::matrix4d parent_world_mat;
size_t child_idx;
XformNode node;
StackEntry(const Prim *p, Path pp, value::matrix4d pwm)
: prim(p), parent_path(std::move(pp)), parent_world_mat(pwm), child_idx(0) {}
};
StackVector<StackEntry, 4> stack;
stack.reserve(64);
// Initialize with root prim
stack.emplace_back(root_prim, initial_parent_path, rootMat);
// Compute root node properties
ComputeXformNodeProperties(root_prim, initial_parent_path, rootMat, t, tinterp,
stack.back().node);
while (!stack.empty()) {
StackEntry &curr = stack.back();
const auto &children = curr.prim->children();
if (curr.child_idx < children.size()) {
// Push next child
const Prim &child = children[curr.child_idx];
curr.child_idx++;
stack.emplace_back(&child, curr.node.absolute_path, curr.node.get_world_matrix());
// Compute new child's properties
StackEntry &new_entry = stack.back();
ComputeXformNodeProperties(new_entry.prim, new_entry.parent_path,
new_entry.parent_world_mat, t, tinterp,
new_entry.node);
} else {
// All children processed
if (stack.size() > 1) {
// Move completed node to parent's children
XformNode completed = std::move(curr.node);
stack.pop_back();
// Note: parent pointer will point to stack.back().node, which will be
// moved later. This preserves the same behavior as the recursive version.
completed.parent = &stack.back().node;
stack.back().node.children.emplace_back(std::move(completed));
} else {
// Root node - copy to output
*nodeOut = std::move(curr.node);
stack.pop_back();
}
}
}
return true;
}
std::string DumpXformNodeRec(const XformNode &node, uint32_t indent) {
// Iterative version of DumpXformNode using explicit stack
std::string DumpXformNodeIterative(const XformNode &root) {
std::stringstream ss;
ss << pprint::Indent(indent) << "Prim name: " << node.element_name
<< " PrimID: " << node.prim_id << " (Path " << node.absolute_path
<< ") Xformable? " << node.has_xform() << " resetXformStack? "
<< node.has_resetXformStack() << " {\n";
ss << pprint::Indent(indent + 1)
<< "parent_world: " << node.get_parent_world_matrix() << "\n";
ss << pprint::Indent(indent + 1) << "world: " << node.get_world_matrix()
<< "\n";
ss << pprint::Indent(indent + 1) << "local: " << node.get_local_matrix()
<< "\n";
// Stack entry: (node pointer, indent, child index, closing_brace_pending)
// child_idx == SIZE_MAX means we haven't printed this node yet
struct StackEntry {
const XformNode *node;
uint32_t indent;
size_t child_idx;
StackEntry(const XformNode *n, uint32_t i)
: node(n), indent(i), child_idx(SIZE_MAX) {}
};
for (const auto &child : node.children) {
ss << DumpXformNodeRec(child, indent + 1);
StackVector<StackEntry, 4> stack;
stack.reserve(64);
stack.emplace_back(&root, 0);
while (!stack.empty()) {
StackEntry &entry = stack.back();
if (entry.child_idx == SIZE_MAX) {
// First visit: print node info
ss << pprint::Indent(entry.indent) << "Prim name: " << entry.node->element_name
<< " PrimID: " << entry.node->prim_id << " (Path " << entry.node->absolute_path
<< ") Xformable? " << entry.node->has_xform() << " resetXformStack? "
<< entry.node->has_resetXformStack() << " {\n";
ss << pprint::Indent(entry.indent + 1)
<< "parent_world: " << entry.node->get_parent_world_matrix() << "\n";
ss << pprint::Indent(entry.indent + 1) << "world: " << entry.node->get_world_matrix()
<< "\n";
ss << pprint::Indent(entry.indent + 1) << "local: " << entry.node->get_local_matrix()
<< "\n";
entry.child_idx = 0;
}
// Process children
const auto &children = entry.node->children;
if (entry.child_idx < children.size()) {
size_t idx = entry.child_idx++;
stack.emplace_back(&children[idx], entry.indent + 1);
} else {
// All children processed, print closing brace and pop
ss << pprint::Indent(entry.indent + 1) << "}\n";
stack.pop_back();
}
}
ss << pprint::Indent(indent + 1) << "}\n";
return ss.str();
}
@@ -2253,8 +2490,8 @@ bool BuildXformNodeFromStage(
value::matrix4d rootMat{value::matrix4d::identity()};
if (!BuildXformNodeFromStageRec(stage, stage_root.absolute_path, &root,
&node, rootMat, t, tinterp)) {
if (!BuildXformNodeFromStageIterative(stage, stage_root.absolute_path, &root,
&node, rootMat, t, tinterp)) {
return false;
}
@@ -2267,7 +2504,7 @@ bool BuildXformNodeFromStage(
}
std::string DumpXformNode(const XformNode &node) {
return DumpXformNodeRec(node, 0);
return DumpXformNodeIterative(node);
}
template <typename T>
@@ -3113,16 +3350,33 @@ bool BuildSkelHierarchy(const Skeleton &skel, SkelNode &dst, std::string *err) {
namespace {
void BuildSkelNameToIndexMapRec(const SkelNode &node, std::map<std::string, int> &m) {
// Iterative version of BuildSkelNameToIndexMap using explicit stack
void BuildSkelNameToIndexMapIterative(const SkelNode &root, std::map<std::string, int> &m) {
// Stack for DFS traversal
StackVector<std::pair<const SkelNode *, size_t>, 4> stack;
stack.reserve(64);
stack.emplace_back(&root, 0);
if (node.joint_name.size() && (node.joint_id >= 0)) {
m[node.joint_name] = node.joint_id;
while (!stack.empty()) {
std::pair<const SkelNode *, size_t> &entry = stack.back();
const SkelNode *node = entry.first;
size_t &child_idx = entry.second;
// Process current node on first visit (child_idx == 0)
if (child_idx == 0) {
if (node->joint_name.size() && (node->joint_id >= 0)) {
m[node->joint_name] = node->joint_id;
}
}
// Process children
if (child_idx < node->children.size()) {
size_t idx = child_idx++;
stack.emplace_back(&node->children[idx], 0);
} else {
stack.pop_back();
}
}
for (const auto &child : node.children) {
BuildSkelNameToIndexMapRec(child, m);
}
}
} // namespace
@@ -3131,8 +3385,8 @@ std::map<std::string, int> BuildSkelNameToIndexMap(const SkelHierarchy &skel) {
std::map<std::string, int> m;
BuildSkelNameToIndexMapRec(skel.root_node, m);
BuildSkelNameToIndexMapIterative(skel.root_node, m);
return m;
}

View File

@@ -990,30 +990,69 @@ nonstd::expected<json, std::string> ToJSON(const tinyusdz::StageMetas& metas) {
return j;
}
bool PrimToJSONRec(json &root, const tinyusdz::Prim& prim, int depth) {
json j = ToJSON(prim.data());
// Iterative version of PrimToJSON using explicit stack
bool PrimToJSONIterative(json &root, const tinyusdz::Prim& root_prim) {
// Stack entry for iterative processing
struct StackEntry {
const tinyusdz::Prim *prim;
size_t child_idx;
json j;
json jchildren;
json jchildren = json::object();
// TODO: Traverse Prim according to primChildren.
for (const auto &child : prim.children()) {
json cj;
if (!PrimToJSONRec(cj, child, depth+1)) {
return false;
explicit StackEntry(const tinyusdz::Prim *p)
: prim(p), child_idx(0), jchildren(json::object()) {
// Convert prim data to JSON immediately
j = ToJSON(p->data());
}
std::string cname = child.element_name();
jchildren[cname] = cj;
}
};
if (jchildren.size()) {
j["primChildren"] = jchildren;
}
std::vector<StackEntry> stack;
stack.reserve(64);
root[prim.element_name()] = j;
// Initialize with root prim
stack.emplace_back(&root_prim);
while (!stack.empty()) {
StackEntry &curr = stack.back();
const auto &children = curr.prim->children();
if (curr.child_idx < children.size()) {
// Push next child
const tinyusdz::Prim &child = children[curr.child_idx];
curr.child_idx++;
stack.emplace_back(&child);
} else {
// All children processed
// Finalize this node's JSON
if (curr.jchildren.size()) {
curr.j["primChildren"] = curr.jchildren;
}
std::string prim_name = curr.prim->element_name();
json completed_j = std::move(curr.j);
if (stack.size() > 1) {
// Add to parent's children
stack.pop_back();
stack.back().jchildren[prim_name] = std::move(completed_j);
} else {
// Root node - add to output
root[prim_name] = std::move(completed_j);
stack.pop_back();
}
}
}
return true;
}
// Wrapper to maintain backward compatibility with PrimToJSONRec signature
bool PrimToJSONRec(json &root, const tinyusdz::Prim& prim, int depth) {
(void)depth; // Iterative version doesn't need depth
return PrimToJSONIterative(root, prim);
}
// Helper function to serialize context to JSON
json SerializeContextToJSON(const USDToJSONContext& context) {
json j;

View File

@@ -5,7 +5,9 @@
// UsdGeom API implementations
#include <cstring>
#include <sstream>
#include <type_traits>
#include "pprinter.hh"
#include "value-types.hh"
@@ -36,6 +38,32 @@ namespace {
constexpr auto kPrimvars = "primvars:";
constexpr auto kIndices = ":indices";
// Helper trait: can use data() pointer (excludes std::vector<bool>)
template <typename T>
struct can_use_data_ptr : std::integral_constant<bool,
!std::is_same<T, bool>::value> {};
// Helper trait: can use memcpy for block copy
template <typename T>
struct can_use_memcpy : std::integral_constant<bool,
std::is_trivially_copyable<T>::value && !std::is_same<T, bool>::value> {};
// Block copy with memcpy for trivially copyable types
template <typename T>
inline typename std::enable_if<can_use_memcpy<T>::value>::type
CopyBlockElements(T* dest, const T* src, size_t count) {
memcpy(dest, src, count * sizeof(T));
}
// Block copy with loop for non-trivially copyable types
template <typename T>
inline typename std::enable_if<!can_use_memcpy<T>::value>::type
CopyBlockElements(T* dest, const T* src, size_t count) {
for (size_t k = 0; k < count; k++) {
dest[k] = src[k];
}
}
///
/// Computes
///
@@ -45,8 +73,11 @@ constexpr auto kIndices = ":indices";
///
/// `dest` is set to `values` when `indices` is empty
///
/// Optimized version for types that support data() (not std::vector<bool>)
///
template <typename T>
nonstd::expected<bool, std::string> ExpandWithIndices(
typename std::enable_if<can_use_data_ptr<T>::value, nonstd::expected<bool, std::string>>::type
ExpandWithIndices(
const std::vector<T> &values, uint32_t elementSize, const std::vector<int32_t> &indices,
std::vector<T> *dest) {
if (!dest) {
@@ -69,17 +100,31 @@ nonstd::expected<bool, std::string> ExpandWithIndices(
dest->resize(indices.size() * elementSize);
std::vector<size_t> invalidIndices;
const size_t numValues = values.size();
const size_t numIndices = indices.size();
T* destData = dest->data();
const T* srcData = values.data();
bool valid = true;
for (size_t i = 0; i < indices.size(); i++) {
int32_t idx = indices[i];
if ((idx >= 0) && ((size_t(idx+1) * size_t(elementSize)) <= values.size())) {
for (size_t k = 0; k < elementSize; k++) {
(*dest)[i*elementSize + k] = values[size_t(idx)*elementSize + k];
// Fast path for elementSize == 1 (most common case)
if (elementSize == 1) {
for (size_t i = 0; i < numIndices; i++) {
int32_t idx = indices[i];
if ((idx >= 0) && (size_t(idx) < numValues)) {
destData[i] = srcData[idx];
} else {
invalidIndices.push_back(i);
}
}
}
// Optimized path for elementSize > 1
else {
for (size_t i = 0; i < numIndices; i++) {
int32_t idx = indices[i];
if ((idx >= 0) && ((size_t(idx+1) * size_t(elementSize)) <= numValues)) {
CopyBlockElements(destData + i * elementSize, srcData + size_t(idx) * elementSize, elementSize);
} else {
invalidIndices.push_back(i);
}
} else {
invalidIndices.push_back(i);
valid = false;
}
}
@@ -90,7 +135,59 @@ nonstd::expected<bool, std::string> ExpandWithIndices(
/* N to display */ 5));
}
return valid;
return true;
}
///
/// Fallback for std::vector<bool> (no data() method available)
///
template <typename T>
typename std::enable_if<!can_use_data_ptr<T>::value, nonstd::expected<bool, std::string>>::type
ExpandWithIndices(
const std::vector<T> &values, uint32_t elementSize, const std::vector<int32_t> &indices,
std::vector<T> *dest) {
if (!dest) {
return nonstd::make_unexpected("`dest` is nullptr.");
}
if (indices.empty()) {
(*dest) = values;
return true;
}
if (elementSize == 0) {
return false;
}
if ((values.size() % elementSize) != 0) {
return false;
}
dest->resize(indices.size() * elementSize);
std::vector<size_t> invalidIndices;
const size_t numValues = values.size();
const size_t numIndices = indices.size();
for (size_t i = 0; i < numIndices; i++) {
int32_t idx = indices[i];
if ((idx >= 0) && ((size_t(idx+1) * size_t(elementSize)) <= numValues)) {
for (size_t k = 0; k < elementSize; k++) {
(*dest)[i*elementSize + k] = values[size_t(idx)*elementSize + k];
}
} else {
invalidIndices.push_back(i);
}
}
if (invalidIndices.size()) {
return nonstd::make_unexpected(
"Invalid indices found: " +
value::print_array_snipped(invalidIndices,
/* N to display */ 5));
}
return true;
}
} // namespace

View File

@@ -1272,15 +1272,13 @@ static bool ConvertNoise(const tinyusdz::mtlx::pugi::xml_node &node, PrimSpec &p
return true;
}
static bool ConvertNodeGraphRec(const uint32_t depth,
const tinyusdz::mtlx::pugi::xml_node &node, PrimSpec &ps_out,
std::string *warn, std::string *err) {
if (depth > (1024 * 1024)) {
PUSH_ERROR_AND_RETURN("Network too deep.\n");
}
PrimSpec ps;
// Helper to convert a single MaterialX node to PrimSpec
// Returns true if successful (including skip case), false on error
// Sets is_skip to true if the node should be skipped (input/output/unknown)
static bool ConvertSingleNode(const tinyusdz::mtlx::pugi::xml_node &node,
PrimSpec &ps, bool &is_skip,
std::string *warn, std::string *err) {
is_skip = false;
std::string node_name = node.name();
// Convert MaterialX nodes to USD shader nodes
@@ -1330,29 +1328,104 @@ static bool ConvertNodeGraphRec(const uint32_t depth,
}
} else if (node_name == "input" || node_name == "output") {
// Skip input/output nodes - they are handled separately
is_skip = true;
return true;
} else {
PUSH_WARN(fmt::format("Unknown/unsupported Shader Node: {}. Skipping.\n", node.name()));
is_skip = true;
return true; // Don't fail, just skip unknown nodes
}
// Recursively process child nodes
for (const auto &child : node) {
PrimSpec child_ps;
if (!ConvertNodeGraphRec(depth + 1, child, child_ps, warn, err)) {
return false;
return true;
}
// Iterative version of ConvertNodeGraph using explicit stack
static bool ConvertNodeGraphIterative(const tinyusdz::mtlx::pugi::xml_node &root_node,
PrimSpec &ps_out,
std::string *warn, std::string *err) {
constexpr size_t kMaxDepth = 1024 * 1024;
// Stack entry for iterative processing
// We need to collect children into a vector since the iterator is temporary
struct StackEntry {
tinyusdz::mtlx::pugi::xml_node xml_node;
std::vector<tinyusdz::mtlx::pugi::xml_node> children;
size_t child_idx;
PrimSpec ps;
bool is_skip;
explicit StackEntry(const tinyusdz::mtlx::pugi::xml_node &n)
: xml_node(n), child_idx(0), is_skip(false) {
// Collect children into vector
for (auto it = n.begin(); it != n.end(); ++it) {
children.push_back(*it);
}
}
};
std::vector<StackEntry> stack;
stack.reserve(64);
// Initialize with root node
stack.emplace_back(root_node);
// Convert root node
if (!ConvertSingleNode(root_node, stack.back().ps, stack.back().is_skip, warn, err)) {
return false;
}
while (!stack.empty()) {
if (stack.size() > kMaxDepth) {
PUSH_ERROR_AND_RETURN("Network too deep.\n");
}
if (!child_ps.name().empty()) {
ps.children().emplace_back(std::move(child_ps));
StackEntry &curr = stack.back();
// Check if there are more children to process
if (curr.child_idx < curr.children.size()) {
// Get current child and advance index
tinyusdz::mtlx::pugi::xml_node child = curr.children[curr.child_idx];
curr.child_idx++;
// Push child onto stack
stack.emplace_back(child);
// Convert the child node
if (!ConvertSingleNode(child, stack.back().ps, stack.back().is_skip, warn, err)) {
return false;
}
} else {
// All children processed
if (stack.size() > 1) {
// Move completed node to parent's children if not skipped and has name
PrimSpec completed = std::move(curr.ps);
bool was_skip = curr.is_skip;
stack.pop_back();
if (!was_skip && !completed.name().empty()) {
stack.back().ps.children().emplace_back(std::move(completed));
}
} else {
// Root node - copy to output
if (!curr.is_skip) {
ps_out = std::move(curr.ps);
}
stack.pop_back();
}
}
}
ps_out = std::move(ps);
return true;
}
// Wrapper to maintain backward compatibility with ConvertNodeGraphRec signature
static bool ConvertNodeGraphRec(const uint32_t depth,
const tinyusdz::mtlx::pugi::xml_node &node, PrimSpec &ps_out,
std::string *warn, std::string *err) {
(void)depth; // Iterative version handles depth internally
return ConvertNodeGraphIterative(node, ps_out, warn, err);
}
#if 0 // TODO
static bool ConvertPlace2d(const tinyusdz::mtlx::pugi::xml_node &node, UsdTransform2d &tx, std::string *warn, std::string *err) {
// texcoord(vector2). default index=0 uv coordinate