Просмотр исходного кода

Fix: Schema Parsing sometimes freezes forever
also cleaned up utilities.get_all_dependencies() and added a check for cycles to prevent infinite loops in readtree.execute()
TODO: get a better cycle-check solution in there - run it when a bad link is detected by Blender.

Joseph Brandenburg 9 месяцев назад
Родитель
Сommit
b60ad05a0b
2 измененных файлов с 53 добавлено и 17 удалено
  1. 47 7
      readtree.py
  2. 6 10
      utilities.py

+ 47 - 7
readtree.py

@@ -286,6 +286,35 @@ def solve_schema_to_tree(nc, all_nc, roots=[]):
 #                                  PARSE NODE TREE                                  #
 # *** # *** # *** # *** # *** # *** # *** # *** # *** # *** # *** # *** # *** # *** #
 
+from .utilities import get_all_dependencies
+def get_schema_length_dependencies(node):
+    """ Find all of the nodes that the Schema Length input depends on. """
+    # the deps recursively from the from_nodes connected to Schema Length
+    deps = []
+    # return get_all_dependencies(node)
+    inp = node.inputs.get("Schema Length")
+    if not inp:
+        inp = node.inputs.get("Array")
+    # this way we can handle Schema and Array Get nodes with one function
+    # ... since I may add more in the future this is not a robust solution HACK
+    for l in inp.links:
+        deps.extend(get_all_dependencies(l.from_node))
+    if inp := node.inputs.get("Index"):
+        for l in inp.links:
+            deps.extend(get_all_dependencies(l.from_node))
+    # now get the auto-generated simple inputs. These should not really be there but I haven't figured out how to set things directly yet lol
+    for inp in node.inputs.values():
+        for l in inp.links:
+            if "MANTIS_AUTOGENERATED" in l.from_node.signature:
+                # l.from_node.bPrepare() # try this...
+                # l.from_node.prepared = True; l.from_node.executed = True
+                deps.extend([l.from_node]) # why we need this lol
+
+    return deps
+
+        
+
+
 def parse_tree(base_tree):
     from uuid import uuid4 # do this here?
     base_tree.execution_id = uuid4().__str__() # set this, it may be used by nodes during execution
@@ -334,7 +363,6 @@ def parse_tree(base_tree):
         if isinstance(nc, UtilityArrayGet):
             arrays.append(nc)
 
-    from .utilities import get_all_dependencies
     from collections import deque
     unsolved_schema = deque()
     solve_only_these = []; solve_only_these.extend(list(all_schema.values()))
@@ -347,10 +375,10 @@ def parse_tree(base_tree):
                 break
         else:
             init_schema_dependencies(schema, all_nc)
-            solve_only_these.extend(get_all_dependencies(schema))
+            solve_only_these.extend(get_schema_length_dependencies(schema))
             unsolved_schema.append(schema)
     for array in arrays:
-        solve_only_these.extend(get_all_dependencies(array))
+        solve_only_these.extend(get_schema_length_dependencies(array))
     solve_only_these.extend(arrays)
     schema_solve_done = set()
 
@@ -360,12 +388,12 @@ def parse_tree(base_tree):
     
     while(solve_layer):
         n = solve_layer.pop()
-        if n not in solve_only_these:
+        if n not in solve_only_these: # removes the unneeded node from the solve-layer
             continue
 
-        if n.signature in all_schema.keys() and n.signature:
+        if n.signature in all_schema.keys():
             for dep in n.hierarchy_dependencies:
-                if dep not in schema_solve_done:
+                if dep not in schema_solve_done and (dep in solve_only_these):
                     solve_layer.appendleft(n)
                     break
             else:
@@ -393,6 +421,7 @@ def parse_tree(base_tree):
                         solve_layer.appendleft(conn)
     if unsolved_schema:
         raise RuntimeError("Failed to resolve all schema declarations")
+    # I had a problem with this looping forever. I think it is resolved... but I don't know lol
 
     all_nc = list(all_nc.values()).copy()
     kept_nc = {}
@@ -574,9 +603,20 @@ def execute_tree(nodes, base_tree, context):
     # exe_order = {}; i=0
     executed = []
 
-
+    # check for cycles here by keeping track of the number of times a node has been visited.
+    visited={}
+    check_max_len=len(nodes)**2 # seems too high but safe. In a well-ordered graph, I guess this number should be less than the number of nodes.
+    
     while(xForm_pass):
         n = xForm_pass.pop()
+        if visited.get(n.signature):
+            visited[n.signature]+=1
+        else:
+            visited[n.signature]=0
+        if visited[n.signature] > check_max_len:
+            prRed("There is a cycle in the graph somewhere. Fix it!")
+            # we're trying to solve the halting problem at this point.. don't do that.
+            # TODO find a better way! there are algo's for this but they will require using a different solving algo, too
         if n.prepared:
             continue
         if n.node_type not in ['XFORM', 'UTILITY']:

+ 6 - 10
utilities.py

@@ -516,16 +516,12 @@ def get_all_dependencies(nc):
     nodes = []
     can_descend = True
     check_nodes = [nc]
-    while (len(check_nodes) > 0): # this seems innefficient, why 2 loops?
-        new_nodes = []
-        while (len(check_nodes) > 0):
-            node = check_nodes.pop()
-            connected_nodes = node.hierarchy_dependencies.copy()
-            for new_node in connected_nodes:
-                if new_node in nodes: continue 
-                new_nodes.append(new_node)
-                nodes.append(new_node)
-        check_nodes = new_nodes
+    while (len(check_nodes) > 0):
+        node = check_nodes.pop()
+        connected_nodes = node.hierarchy_dependencies.copy()
+        for new_node in connected_nodes:
+            if new_node in nodes: raise GraphError() 
+            nodes.append(new_node)
     return nodes
             
 ##################################################################################################