27 bool has_cycles = std::any_of(result.
issues.begin(), result.
issues.end(),
29 return issue.message.find(
"circular") != EmberCore::String::npos ||
30 issue.message.find(
"cycle") != EmberCore::String::npos;
37 std::vector<const Node *> visited;
58 result.
AddError(
"Tree has no root node");
64 std::vector<const Node *> visited;
67 result.
AddError(
"Tree contains circular references (cycles)");
69 }
catch (
const std::exception &e) {
72 result.
AddError(
"Cycle detection failed with unknown error (possible circular structure)");
79 result.
AddError(
"Null node found in tree", parent_path);
87 if (std::find(visited.begin(), visited.end(), node) != visited.end()) {
88 result.
AddError(
"Circular reference detected", node_path);
91 visited.push_back(node);
95 result.
AddError(
"Node has empty name", node_path);
113 result.
AddError(
"Node has null child at index " + std::to_string(i), node_path);
121 const auto &node_config =
config_.GetNodeConfig();
125 if (node_type_id.empty()) {
126 result.
AddWarning(
"Node missing '" + node_config.node_id_attribute +
"' attribute (required for serialization)",
132 bool type_registered =
false;
135 type_registered =
config_.IsControlType(node_type_id);
136 if (!type_registered) {
137 result.
AddError(
"Control node type '" + node_type_id +
"' not registered in profile", path);
142 type_registered =
config_.IsActionType(node_type_id);
144 if (!type_registered && !
config_.GetClassificationConfig().action_types.empty()) {
145 result.
AddError(
"Action node type '" + node_type_id +
"' not registered in profile", path);
150 type_registered =
config_.IsConditionType(node_type_id);
151 if (!type_registered && !
config_.GetClassificationConfig().condition_types.empty()) {
152 result.
AddError(
"Condition node type '" + node_type_id +
"' not registered in profile", path);
157 type_registered =
config_.IsDecoratorType(node_type_id);
158 if (!type_registered) {
159 result.
AddError(
"Decorator node type '" + node_type_id +
"' not registered in profile", path);
164 result.
AddWarning(
"Node has unknown type enum value", path);
171 const auto &node_config =
config_.GetNodeConfig();
174 if (!node_config.node_id_attribute.empty()) {
177 result.
AddWarning(
"Node missing required '" + node_config.node_id_attribute +
"' attribute", path);
183 result.
AddError(
"Node has unexpanded SubTree placeholder", path);
190 if (!subtree_ref.empty()) {
191 result.
AddWarning(
"SubTree '" + subtree_ref +
"' is referenced but not implemented", path);
193 result.
AddWarning(
"Node references an unimplemented tree", path);
199 if (!subtree_ref.empty() && !node->
HasAttribute(
"__unimplemented__")) {
212 if (child_count == 0) {
213 result.
AddError(
"Decorator node has no children (must have exactly 1)", path);
214 }
else if (child_count > 1) {
215 result.
AddWarning(
"Decorator node has " + std::to_string(child_count) +
" children (should have exactly 1)",
221 if (child_count == 0) {
222 result.
AddWarning(
"Control node has no children", path);
228 if (child_count > 0) {
229 result.
AddWarning(
"Leaf node (" + std::to_string(
static_cast<int>(node->
GetType())) +
") has " +
230 std::to_string(child_count) +
" children (leaf nodes typically have no children)",
243 for (
const auto &bb_pair : blackboards) {
244 const auto &bb_id = bb_pair.first;
245 const auto *blackboard = bb_pair.second.get();
248 result.
AddError(
"Blackboard '" + bb_id +
"' is null");
264 if (node_name.empty()) {
265 node_name =
"[unnamed]";
268 if (parent_path.empty()) {
271 return parent_path +
"/" + node_name;
279 constexpr size_t MAX_DEPTH = 500;
280 if (visited.size() >= MAX_DEPTH) {
281 LOG_ERROR(
"BehaviorTreeValidator",
"Max recursion depth exceeded - likely circular structure");
287 if (std::any_of(visited.begin(), visited.end(),
288 [node](
const Node *visited_node) { return visited_node == node; })) {
292 visited.push_back(node);
295 size_t child_count = 0;
299 LOG_ERROR(
"BehaviorTreeValidator",
"Failed to get child count - possibly corrupted node");
304 for (
size_t i = 0; i < child_count; ++i) {
305 const Node *child =
nullptr;
309 LOG_ERROR(
"BehaviorTreeValidator",
"Failed to get child - possibly corrupted tree");
314 if (child &&
HasCycles(child, visited)) {
#define LOG_ERROR(category, message)
ValidationResult Validate(const BehaviorTree *tree) const
Validate a behavior tree against the parser profile.
EmberCore::String BuildNodePath(const EmberCore::String &parent_path, const Node *node) const
void ValidateTreeStructure(const BehaviorTree *tree, ValidationResult &result) const
static ValidationResult ValidateWithConfig(const BehaviorTree *tree, const ParserConfig &config)
Validate a behavior tree with profile override.
void ValidateNodeType(const Node *node, ValidationResult &result, const EmberCore::String &path) const
bool HasCycles(const Node *node, std::vector< const Node * > &visited) const
void ValidateNodeAttributes(const Node *node, ValidationResult &result, const EmberCore::String &path) const
void ValidateNode(const Node *node, ValidationResult &result, std::vector< const Node * > &visited, const EmberCore::String &path) const
void ValidateBlackboards(const BehaviorTree *tree, ValidationResult &result) const
void ValidateChildCount(const Node *node, ValidationResult &result, const EmberCore::String &path) const
Represents a complete behavior tree data structure.
Node * GetRootNode() const
const std::map< EmberCore::String, std::unique_ptr< Blackboard > > & GetBlackboards() const
Represents a node in a behavior tree structure.
bool HasAttribute(const String &name) const
String GetAttribute(const String &name, const String &default_value="") const
Node * GetChild(size_t index) const
const String & GetName() const
size_t GetChildCount() const
Configuration for XML parser behavior and element/attribute mappings.
Main types header for EmberCore.
std::string String
Framework-agnostic string type.
void AddError(const EmberCore::String &message, const EmberCore::String &node_path="")
std::vector< ValidationIssue > issues
void AddWarning(const EmberCore::String &message, const EmberCore::String &node_path="")