Depsgraph: Synchronize flushing with 2.8 branch
authorSergey Sharybin <sergey.vfx@gmail.com>
Mon, 18 Dec 2017 15:33:12 +0000 (16:33 +0100)
committerSergey Sharybin <sergey.vfx@gmail.com>
Mon, 18 Dec 2017 15:33:12 +0000 (16:33 +0100)
Not only this helps merges form master to the branch, but also:

- Allows us to production-check changes as soon as possible.
- Avoids some unnecessary editors update about ID changes.
- Adds small optimization on queue size by always keeping one of the pointers
  outside of the queue.

source/blender/depsgraph/intern/eval/deg_eval_flush.cc

index 79ef0f32dda8258f7e10d0f9327ee4f094e6db3c..219ed981870059d1ecce8b213768c10fdcc630bc 100644 (file)
@@ -54,12 +54,19 @@ extern "C" {
 
 namespace DEG {
 
+enum {
+       ID_STATE_NONE     = 0,
+       ID_STATE_MODIFIED = 1,
+};
+
 enum {
        COMPONENT_STATE_NONE      = 0,
        COMPONENT_STATE_SCHEDULED = 1,
        COMPONENT_STATE_DONE      = 2,
 };
 
+typedef std::deque<OperationDepsNode *> FlushQueue;
+
 namespace {
 
 // TODO(sergey): De-duplicate with depsgraph_tag,cc
@@ -75,176 +82,200 @@ void lib_id_recalc_data_tag(Main *bmain, ID *id)
        DEG_id_type_tag(bmain, GS(id->name));
 }
 
-}  /* namespace */
-
-typedef std::deque<OperationDepsNode *> FlushQueue;
-
-static void flush_init_func(void *data_v, int i)
+void flush_init_operation_node_func(void *data_v, int i)
 {
-       /* ID node's done flag is used to avoid multiple editors update
-        * for the same ID.
-        */
        Depsgraph *graph = (Depsgraph *)data_v;
        OperationDepsNode *node = graph->operations[i];
-       ComponentDepsNode *comp_node = node->owner;
-       IDDepsNode *id_node = comp_node->owner;
-       id_node->done = 0;
-       comp_node->done = COMPONENT_STATE_NONE;
        node->scheduled = false;
 }
 
-/* Flush updates from tagged nodes outwards until all affected nodes
- * are tagged.
- */
-void deg_graph_flush_updates(Main *bmain, Depsgraph *graph)
+void flush_init_id_node_func(void *data_v, int i)
 {
-       /* Sanity check. */
-       if (graph == NULL) {
-               return;
-       }
-
-       /* Nothing to update, early out. */
-       if (BLI_gset_size(graph->entry_tags) == 0) {
-               return;
-       }
+       Depsgraph *graph = (Depsgraph *)data_v;
+       IDDepsNode *id_node = graph->id_nodes[i];
+       id_node->done = ID_STATE_NONE;
+       GHASH_FOREACH_BEGIN(ComponentDepsNode *, comp_node, id_node->components)
+               comp_node->done = COMPONENT_STATE_NONE;
+       GHASH_FOREACH_END();
+}
 
-       /* TODO(sergey): With a bit of flag magic we can get rid of this
-        * extra loop.
-        */
+BLI_INLINE void flush_prepare(Depsgraph *graph)
+{
        const int num_operations = graph->operations.size();
-       const bool do_threads = num_operations > 256;
-       BLI_task_parallel_range(0,
-                               num_operations,
+       BLI_task_parallel_range(0, num_operations,
+                               graph,
+                               flush_init_operation_node_func,
+                               (num_operations > 256));
+       const int num_id_nodes = graph->id_nodes.size();
+       BLI_task_parallel_range(0, num_id_nodes,
                                graph,
-                               flush_init_func,
-                               do_threads);
+                               flush_init_id_node_func,
+                               (num_id_nodes > 256));
+}
 
-       FlushQueue queue;
-       /* Starting from the tagged "entry" nodes, flush outwards... */
-       /* NOTE: Also need to ensure that for each of these, there is a path back to
-        *       root, or else they won't be done.
-        * NOTE: Count how many nodes we need to handle - entry nodes may be
-        *       component nodes which don't count for this purpose!
-        */
-       GSET_FOREACH_BEGIN(OperationDepsNode *, node, graph->entry_tags)
+BLI_INLINE void flush_schedule_entrypoints(Depsgraph *graph, FlushQueue *queue)
+{
+       GSET_FOREACH_BEGIN(OperationDepsNode *, op_node, graph->entry_tags)
        {
-               queue.push_back(node);
-               node->scheduled = true;
+               queue->push_back(op_node);
+               op_node->scheduled = true;
        }
        GSET_FOREACH_END();
+}
 
-       int num_flushed_objects = 0;
-       while (!queue.empty()) {
-               OperationDepsNode *node = queue.front();
-               queue.pop_front();
-
-               for (;;) {
-                       node->flag |= DEPSOP_FLAG_NEEDS_UPDATE;
+BLI_INLINE void flush_handle_id_node(IDDepsNode *id_node)
+{
+       id_node->done = ID_STATE_MODIFIED;
+}
 
-                       ComponentDepsNode *comp_node = node->owner;
-                       IDDepsNode *id_node = comp_node->owner;
+/* TODO(sergey): We can reduce number of arguments here. */
+BLI_INLINE void flush_handle_component_node(Depsgraph * /*graph*/,
+                                            IDDepsNode *id_node,
+                                            ComponentDepsNode *comp_node,
+                                            FlushQueue *queue)
+{
+       /* We only handle component once. */
+       if (comp_node->done == COMPONENT_STATE_DONE) {
+               return;
+       }
+       comp_node->done = COMPONENT_STATE_DONE;
+       /* Tag all required operations in component for update.  */
+       foreach (OperationDepsNode *op, comp_node->operations) {
+               op->flag |= DEPSOP_FLAG_NEEDS_UPDATE;
+       }
+       if (GS(id_node->id->name) == ID_OB) {
+               Object *object = (Object *)id_node->id;
+               /* This code is used to preserve those areas which does
+                * direct object update,
+                *
+                * Plus it ensures visibility changes and relations and
+                * layers visibility update has proper flags to work with.
+                */
+               switch (comp_node->type) {
+                       case DEG_NODE_TYPE_UNDEFINED:
+                       case DEG_NODE_TYPE_OPERATION:
+                       case DEG_NODE_TYPE_TIMESOURCE:
+                       case DEG_NODE_TYPE_ID_REF:
+                       case DEG_NODE_TYPE_SEQUENCER:
+                       case NUM_DEG_NODE_TYPES:
+                               /* Ignore, does not translate to object component. */
+                               BLI_assert(!"This should never happen!");
+                               break;
+                       case DEG_NODE_TYPE_ANIMATION:
+                               object->recalc |= OB_RECALC_TIME;
+                               break;
+                       case DEG_NODE_TYPE_TRANSFORM:
+                               object->recalc |= OB_RECALC_OB;
+                               break;
+                       case DEG_NODE_TYPE_GEOMETRY:
+                       case DEG_NODE_TYPE_EVAL_POSE:
+                       case DEG_NODE_TYPE_BONE:
+                       case DEG_NODE_TYPE_EVAL_PARTICLES:
+                       case DEG_NODE_TYPE_SHADING:
+                       case DEG_NODE_TYPE_CACHE:
+                       case DEG_NODE_TYPE_PROXY:
+                               object->recalc |= OB_RECALC_DATA;
+                               break;
+                       case DEG_NODE_TYPE_PARAMETERS:
+                               break;
+               }
+       }
+       /* When some target changes bone, we might need to re-run the
+        * whole IK solver, otherwise result might be unpredictable.
+        */
+       if (comp_node->type == DEG_NODE_TYPE_BONE) {
+               ComponentDepsNode *pose_comp =
+                       id_node->find_component(DEG_NODE_TYPE_EVAL_POSE);
+               BLI_assert(pose_comp != NULL);
+               if (pose_comp->done == COMPONENT_STATE_NONE) {
+                       queue->push_front(pose_comp->get_entry_operation());
+                       pose_comp->done = COMPONENT_STATE_SCHEDULED;
+               }
+       }
+}
 
-                       ID *id = id_node->id;
-                       if (id_node->done == 0) {
-                               deg_editors_id_update(bmain, id);
-                               lib_id_recalc_tag(bmain, id);
-                               /* TODO(sergey): For until we've got proper data nodes in the graph. */
-                               lib_id_recalc_data_tag(bmain, id);
+/* Schedule children of the given operation node for traversal.
+ *
+ * One of the children will by-pass the queue and will be returned as a function
+ * return value, so it can start being handled right away, without building too
+ * much of a queue.
+ */
+BLI_INLINE OperationDepsNode *flush_schedule_children(
+        OperationDepsNode *op_node,
+        FlushQueue *queue)
+{
+       OperationDepsNode *result = NULL;
+       foreach (DepsRelation *rel, op_node->outlinks) {
+               OperationDepsNode *to_node = (OperationDepsNode *)rel->to;
+               if (to_node->scheduled == false) {
+                       if (result != NULL) {
+                               queue->push_front(to_node);
                        }
-
-                       if (comp_node->done != COMPONENT_STATE_DONE) {
-                               Object *object = NULL;
-                               if (GS(id->name) == ID_OB) {
-                                       object = (Object *)id;
-                                       if (id_node->done == 0) {
-                                               ++num_flushed_objects;
-                                       }
-                               }
-                               foreach (OperationDepsNode *op, comp_node->operations) {
-                                       op->flag |= DEPSOP_FLAG_NEEDS_UPDATE;
-                               }
-                               if (object != NULL) {
-                                       /* This code is used to preserve those areas which does
-                                        * direct object update,
-                                        *
-                                        * Plus it ensures visibility changes and relations and
-                                        * layers visibility update has proper flags to work with.
-                                        */
-                                       switch (comp_node->type) {
-                                               case DEG_NODE_TYPE_UNDEFINED:
-                                               case DEG_NODE_TYPE_OPERATION:
-                                               case DEG_NODE_TYPE_TIMESOURCE:
-                                               case DEG_NODE_TYPE_ID_REF:
-                                               case DEG_NODE_TYPE_SEQUENCER:
-                                               case NUM_DEG_NODE_TYPES:
-                                                       /* Ignore, does not translate to object component. */
-                                                       BLI_assert(!"This should never happen!");
-                                                       break;
-                                               case DEG_NODE_TYPE_ANIMATION:
-                                                       object->recalc |= OB_RECALC_TIME;
-                                                       break;
-                                               case DEG_NODE_TYPE_TRANSFORM:
-                                                       object->recalc |= OB_RECALC_OB;
-                                                       break;
-                                               case DEG_NODE_TYPE_GEOMETRY:
-                                               case DEG_NODE_TYPE_EVAL_POSE:
-                                               case DEG_NODE_TYPE_BONE:
-                                               case DEG_NODE_TYPE_EVAL_PARTICLES:
-                                               case DEG_NODE_TYPE_SHADING:
-                                               case DEG_NODE_TYPE_CACHE:
-                                               case DEG_NODE_TYPE_PROXY:
-                                                       object->recalc |= OB_RECALC_DATA;
-                                                       break;
-                                               case DEG_NODE_TYPE_PARAMETERS:
-                                                       break;
-                                       }
-                               }
-                               /* When some target changes bone, we might need to re-run the
-                                * whole IK solver, otherwise result might be unpredictable.
-                                */
-                               if (comp_node->type == DEG_NODE_TYPE_BONE) {
-                                       ComponentDepsNode *pose_comp =
-                                               id_node->find_component(DEG_NODE_TYPE_EVAL_POSE);
-                                       BLI_assert(pose_comp != NULL);
-                                       if (pose_comp->done == COMPONENT_STATE_NONE) {
-                                               queue.push_front(pose_comp->get_entry_operation());
-                                               pose_comp->done = COMPONENT_STATE_SCHEDULED;
-                                       }
-                               }
+                       else {
+                               result = to_node;
                        }
+                       to_node->scheduled = true;
+               }
+       }
+       return result;
+}
 
-                       id_node->done = 1;
-                       comp_node->done = COMPONENT_STATE_DONE;
+BLI_INLINE void flush_editors_id_update(Main *bmain,
+                                        Depsgraph *graph)
+{
+       foreach (IDDepsNode *id_node, graph->id_nodes) {
+               if (id_node->done != ID_STATE_MODIFIED) {
+                       continue;
+               }
+               /* TODO(sergey): Do we need to pass original or evaluated ID here? */
+               ID *id = id_node->id;
+               deg_editors_id_update(bmain, id);
+               lib_id_recalc_tag(bmain, id);
+               /* TODO(sergey): For until we've got proper data nodes in the graph. */
+               lib_id_recalc_data_tag(bmain, id);
+       }
+}
 
-                       /* Flush to nodes along links... */
-                       /* TODO(sergey): This is mainly giving speedup due ot less queue pushes, which
-                        * reduces number of memory allocations.
-                        *
-                        * We should try solve the allocation issue instead of doing crazy things here.
-                        */
-                       if (node->outlinks.size() == 1) {
-                               OperationDepsNode *to_node = (OperationDepsNode *)node->outlinks[0]->to;
-                               if (to_node->scheduled == false) {
-                                       to_node->scheduled = true;
-                                       node = to_node;
-                               }
-                               else {
-                                       break;
-                               }
-                       }
-                       else {
-                               foreach (DepsRelation *rel, node->outlinks) {
-                                       OperationDepsNode *to_node = (OperationDepsNode *)rel->to;
-                                       if (to_node->scheduled == false) {
-                                               queue.push_front(to_node);
-                                               to_node->scheduled = true;
-                                       }
-                               }
-                               break;
-                       }
+}  // namespace
+
+/* Flush updates from tagged nodes outwards until all affected nodes
+ * are tagged.
+ */
+void deg_graph_flush_updates(Main *bmain, Depsgraph *graph)
+{
+       /* Sanity checks. */
+       BLI_assert(bmain != NULL);
+       BLI_assert(graph != NULL);
+       /* Nothing to update, early out. */
+       if (BLI_gset_size(graph->entry_tags) == 0) {
+               return;
+       }
+       /* Reset all flags, get ready for the flush. */
+       flush_prepare(graph);
+       /* Starting from the tagged "entry" nodes, flush outwards. */
+       FlushQueue queue;
+       flush_schedule_entrypoints(graph, &queue);
+       /* Do actual flush. */
+       while (!queue.empty()) {
+               OperationDepsNode *op_node = queue.front();
+               queue.pop_front();
+               while (op_node != NULL) {
+                       /* Tag operation as required for update. */
+                       op_node->flag |= DEPSOP_FLAG_NEEDS_UPDATE;
+                       /* Inform corresponding ID and component nodes about the change. */
+                       ComponentDepsNode *comp_node = op_node->owner;
+                       IDDepsNode *id_node = comp_node->owner;
+                       flush_handle_id_node(id_node);
+                       flush_handle_component_node(graph,
+                                                   id_node,
+                                                   comp_node,
+                                                   &queue);
+                       /* Flush to nodes along links. */
+                       op_node = flush_schedule_children(op_node, &queue);
                }
        }
-       DEG_DEBUG_PRINTF("Update flushed to %d objects\n", num_flushed_objects);
+       /* Inform editors about all changes. */
+       flush_editors_id_update(bmain, graph);
 }
 
 static void graph_clear_func(void *data_v, int i)