diff options
author | Kévin Dietrich <kevin.dietrich@mailoo.org> | 2021-12-22 15:07:46 +0300 |
---|---|---|
committer | Kévin Dietrich <kevin.dietrich@mailoo.org> | 2021-12-22 15:07:46 +0300 |
commit | 74138fb2d4ac9f0504c8f9f575bf392b49fc7488 (patch) | |
tree | c383efca92710d0a4b3ffce5ad74ac7758f64ce8 /source/blender/blenkernel | |
parent | 2529300f691109b9575ea75ae8367209751c3738 (diff) | |
parent | d2bf60cc17a961789d7c415fc3d2af14afa50f62 (diff) |
Merge branch 'master' into temp-abc-features
Diffstat (limited to 'source/blender/blenkernel')
23 files changed, 2044 insertions, 950 deletions
diff --git a/source/blender/blenkernel/BKE_action.h b/source/blender/blenkernel/BKE_action.h index ea8ee3f93b1..5dd98dbb9a3 100644 --- a/source/blender/blenkernel/BKE_action.h +++ b/source/blender/blenkernel/BKE_action.h @@ -253,12 +253,28 @@ void BKE_pose_channel_session_uuid_generate(struct bPoseChannel *pchan); */ struct bPoseChannel *BKE_pose_channel_find_name(const struct bPose *pose, const char *name); /** + * Checks if the bone is on a visible armature layer + * + * \return true if on a visible layer, false otherwise. + */ +bool BKE_pose_is_layer_visible(const struct bArmature *arm, const struct bPoseChannel *pchan); +/** * Find the active pose-channel for an object - * (we can't just use pose, as layer info is in armature) * - * \note #Object, not #bPose is used here, as we need layer info from Armature. + * \param check_arm_layer: checks if the bone is on a visible armature layer (this might be skipped + * (e.g. for "Show Active" from the Outliner). + * \return #bPoseChannel if found or NULL. + * \note #Object, not #bPose is used here, as we need info (layer/active bone) from Armature. + */ +struct bPoseChannel *BKE_pose_channel_active(struct Object *ob, const bool check_arm_layer); +/** + * Find the active pose-channel for an object if it is on a visible armature layer + * (calls #BKE_pose_channel_active with check_arm_layer set to true) + * + * \return #bPoseChannel if found or NULL. + * \note #Object, not #bPose is used here, as we need info (layer/active bone) from Armature. */ -struct bPoseChannel *BKE_pose_channel_active(struct Object *ob); +struct bPoseChannel *BKE_pose_channel_active_if_layer_visible(struct Object *ob); /** * Use this when detecting the "other selected bone", * when we have multiple armatures in pose mode. diff --git a/source/blender/blenkernel/BKE_fcurve_driver.h b/source/blender/blenkernel/BKE_fcurve_driver.h index 18676dfcb38..7e4e0ad3c2a 100644 --- a/source/blender/blenkernel/BKE_fcurve_driver.h +++ b/source/blender/blenkernel/BKE_fcurve_driver.h @@ -105,6 +105,13 @@ void driver_change_variable_type(struct DriverVar *dvar, int type); */ void driver_variable_name_validate(struct DriverVar *dvar); /** + * Ensure the driver variable's name is unique. + * + * Assumes the driver variable has already been assigned to the driver, so that + * the prev/next pointers can be used to find the other variables. + */ +void driver_variable_unique_name(struct DriverVar *dvar); +/** * Add a new driver variable. */ struct DriverVar *driver_add_new_variable(struct ChannelDriver *driver); diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index a2959556810..500d8e2e0ac 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -501,16 +501,13 @@ void ntreeFreeLocalTree(struct bNodeTree *ntree); struct bNode *ntreeFindType(const struct bNodeTree *ntree, int type); bool ntreeHasType(const struct bNodeTree *ntree, int type); bool ntreeHasTree(const struct bNodeTree *ntree, const struct bNodeTree *lookup); -void ntreeUpdateTree(struct Main *main, struct bNodeTree *ntree); void ntreeUpdateAllNew(struct Main *main); -/** - * \param tree_update_flag: #eNodeTreeUpdate enum. - */ -void ntreeUpdateAllUsers(struct Main *main, struct ID *id, int tree_update_flag); +void ntreeUpdateAllUsers(struct Main *main, struct ID *id); void ntreeGetDependencyList(struct bNodeTree *ntree, struct bNode ***r_deplist, int *r_deplist_len); +void ntreeUpdateNodeLevels(struct bNodeTree *ntree); /** * XXX: old trees handle output flags automatically based on special output @@ -833,10 +830,6 @@ void nodeClearActive(struct bNodeTree *ntree); void nodeClearActiveID(struct bNodeTree *ntree, short idtype); struct bNode *nodeGetActiveTexture(struct bNodeTree *ntree); -void nodeUpdate(struct bNodeTree *ntree, struct bNode *node); -bool nodeUpdateID(struct bNodeTree *ntree, struct ID *id); -void nodeUpdateInternalLinks(struct bNodeTree *ntree, struct bNode *node); - int nodeSocketIsHidden(const struct bNodeSocket *sock); void ntreeTagUsedSockets(struct bNodeTree *ntree); void nodeSetSocketAvailability(struct bNodeTree *ntree, @@ -960,10 +953,7 @@ bNodePreview *BKE_node_preview_verify( struct bNodeInstanceHash *previews, bNodeInstanceKey key, int xsize, int ysize, bool create); bNodePreview *BKE_node_preview_copy(struct bNodePreview *preview); void BKE_node_preview_free(struct bNodePreview *preview); -void BKE_node_preview_init_tree(struct bNodeTree *ntree, - int xsize, - int ysize, - bool create_previews); +void BKE_node_preview_init_tree(struct bNodeTree *ntree, int xsize, int ysize); void BKE_node_preview_free_tree(struct bNodeTree *ntree); void BKE_node_preview_remove_unused(struct bNodeTree *ntree); void BKE_node_preview_clear(struct bNodePreview *preview); @@ -974,14 +964,6 @@ void BKE_node_preview_merge_tree(struct bNodeTree *to_ntree, struct bNodeTree *from_ntree, bool remove_old); -/** - * Hack warning! this function is only used for shader previews, - * and since it gets called multiple times per pixel for Z-transparency we only add the color once. - * Preview gets cleared before it starts render though. - */ -void BKE_node_preview_set_pixel( - struct bNodePreview *preview, const float col[4], int x, int y, bool do_manage); - /** \} */ /* -------------------------------------------------------------------- */ @@ -1170,7 +1152,6 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, #define SH_NODE_SEPRGB 120 #define SH_NODE_COMBRGB 121 #define SH_NODE_HUE_SAT 122 -#define NODE_DYNAMIC 123 #define SH_NODE_OUTPUT_MATERIAL 124 #define SH_NODE_OUTPUT_WORLD 125 diff --git a/source/blender/blenkernel/BKE_node_tree_update.h b/source/blender/blenkernel/BKE_node_tree_update.h new file mode 100644 index 00000000000..ebaa56c89c9 --- /dev/null +++ b/source/blender/blenkernel/BKE_node_tree_update.h @@ -0,0 +1,109 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#pragma once + +/** \file + * \ingroup bke + */ + +struct bNode; +struct bNodeSocket; +struct bNodeTree; +struct bNodeLink; +struct Main; +struct ID; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Tag tree as changed without providing any more information about what has changed exactly. + * The update process has to assume that everything may have changed. + * + * Using one of the methods below to tag the tree after changes is preffered when possible. + */ +void BKE_ntree_update_tag_all(struct bNodeTree *ntree); + +/** + * More specialized tag functions that may result in a more efficient update. + */ + +void BKE_ntree_update_tag_node_property(struct bNodeTree *ntree, struct bNode *node); +void BKE_ntree_update_tag_node_new(struct bNodeTree *ntree, struct bNode *node); +void BKE_ntree_update_tag_node_removed(struct bNodeTree *ntree); +void BKE_ntree_update_tag_node_internal_link(struct bNodeTree *ntree, struct bNode *node); + +void BKE_ntree_update_tag_socket_property(struct bNodeTree *ntree, struct bNodeSocket *socket); +void BKE_ntree_update_tag_socket_new(struct bNodeTree *ntree, struct bNodeSocket *socket); +void BKE_ntree_update_tag_socket_type(struct bNodeTree *ntree, struct bNodeSocket *socket); +void BKE_ntree_update_tag_socket_availability(struct bNodeTree *ntree, struct bNodeSocket *socket); +void BKE_ntree_update_tag_socket_removed(struct bNodeTree *ntree); + +void BKE_ntree_update_tag_link_changed(struct bNodeTree *ntree); +void BKE_ntree_update_tag_link_removed(struct bNodeTree *ntree); +void BKE_ntree_update_tag_link_added(struct bNodeTree *ntree, struct bNodeLink *link); +void BKE_ntree_update_tag_link_mute(struct bNodeTree *ntree, struct bNodeLink *link); + +/** Used after file loading when run-time data on the tree has not been initialized yet. */ +void BKE_ntree_update_tag_missing_runtime_data(struct bNodeTree *ntree); +/** Used when the interface sockets/values have changed. */ +void BKE_ntree_update_tag_interface(struct bNodeTree *ntree); +/** Used when an id data block changed that might be used by nodes that need to be updated. */ +void BKE_ntree_update_tag_id_changed(struct Main *bmain, struct ID *id); + +typedef struct NodeTreeUpdateExtraParams { + /** + * Data passed into the callbacks. + */ + void *user_data; + + /** + * Called for every tree that has been changed during the update. This can be used to send + * notifiers to trigger redraws or depsgraph updates. + */ + void (*tree_changed_fn)(struct ID *, struct bNodeTree *, void *user_data); + + /** + * Called for every tree whose output value may have changed based on the provided update tags. + * This can be used to tag the depsgraph if necessary. + */ + void (*tree_output_changed_fn)(struct ID *, struct bNodeTree *, void *user_data); +} NodeTreeUpdateExtraParams; + +/** + * Updates #bmain based on changes to node trees. + */ +void BKE_ntree_update_main(struct Main *bmain, struct NodeTreeUpdateExtraParams *params); + +/** + * Same as #BKE_ntree_update_main, but will first only look at the provided tree and only looks + * at #bmain when something relevant for other data-blocks changed. This avoids scanning #bmain in + * many cases. + * + * If #bmain is null, only the provided tree is updated. This should only be used in very rare + * cases because it may result it incorrectly synced data in DNA. + * + * If #tree is null, this is the same as calling #BKE_ntree_update_main. + */ +void BKE_ntree_update_main_tree(struct Main *bmain, + struct bNodeTree *ntree, + struct NodeTreeUpdateExtraParams *params); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/blenkernel/BKE_undo_system.h b/source/blender/blenkernel/BKE_undo_system.h index 6e1f9468ce4..44893a6f030 100644 --- a/source/blender/blenkernel/BKE_undo_system.h +++ b/source/blender/blenkernel/BKE_undo_system.h @@ -91,7 +91,7 @@ typedef struct UndoStep { /** When this is true, undo/memfile read code is allowed to re-use old data-blocks for unchanged * IDs, and existing depsgraphes. This has to be forbidden in some cases (like renamed IDs). */ bool use_old_bmain_data; - /** For use by undo systems that accumulate changes (text editor, painting). */ + /** For use by undo systems that accumulate changes (mesh-sculpt & image-painting). */ bool is_applied; /* Over alloc 'type->struct_size'. */ } UndoStep; diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index f6e7f1c2473..02aef4ef79e 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -226,6 +226,7 @@ set(SRC intern/multires_versioning.c intern/nla.c intern/node.cc + intern/node_tree_update.cc intern/type_conversions.cc intern/object.cc intern/object_deform.c @@ -420,6 +421,7 @@ set(SRC BKE_multires.h BKE_nla.h BKE_node.h + BKE_node_tree_update.h BKE_object.h BKE_object_deform.h BKE_object_facemap.h diff --git a/source/blender/blenkernel/intern/action.c b/source/blender/blenkernel/intern/action.c index ddba726ba83..764c043f5ed 100644 --- a/source/blender/blenkernel/intern/action.c +++ b/source/blender/blenkernel/intern/action.c @@ -705,7 +705,12 @@ bool BKE_pose_channels_is_valid(const bPose *pose) #endif -bPoseChannel *BKE_pose_channel_active(Object *ob) +bool BKE_pose_is_layer_visible(const bArmature *arm, const bPoseChannel *pchan) +{ + return (pchan->bone->layer & arm->layer); +} + +bPoseChannel *BKE_pose_channel_active(Object *ob, const bool check_arm_layer) { bArmature *arm = (ob) ? ob->data : NULL; bPoseChannel *pchan; @@ -716,14 +721,21 @@ bPoseChannel *BKE_pose_channel_active(Object *ob) /* find active */ for (pchan = ob->pose->chanbase.first; pchan; pchan = pchan->next) { - if ((pchan->bone) && (pchan->bone == arm->act_bone) && (pchan->bone->layer & arm->layer)) { - return pchan; + if ((pchan->bone) && (pchan->bone == arm->act_bone)) { + if (!check_arm_layer || BKE_pose_is_layer_visible(arm, pchan)) { + return pchan; + } } } return NULL; } +bPoseChannel *BKE_pose_channel_active_if_layer_visible(struct Object *ob) +{ + return BKE_pose_channel_active(ob, true); +} + bPoseChannel *BKE_pose_channel_active_or_first_selected(struct Object *ob) { bArmature *arm = (ob) ? ob->data : NULL; @@ -732,7 +744,7 @@ bPoseChannel *BKE_pose_channel_active_or_first_selected(struct Object *ob) return NULL; } - bPoseChannel *pchan = BKE_pose_channel_active(ob); + bPoseChannel *pchan = BKE_pose_channel_active_if_layer_visible(ob); if (pchan && (pchan->bone->flag & BONE_SELECTED) && PBONE_VISIBLE(arm, pchan->bone)) { return pchan; } diff --git a/source/blender/blenkernel/intern/asset_catalog.cc b/source/blender/blenkernel/intern/asset_catalog.cc index aec622bb71f..eee1f6287c3 100644 --- a/source/blender/blenkernel/intern/asset_catalog.cc +++ b/source/blender/blenkernel/intern/asset_catalog.cc @@ -24,7 +24,7 @@ #include "BKE_asset_catalog.hh" #include "BKE_asset_library.h" -#include "BLI_fileops.h" +#include "BLI_fileops.hh" #include "BLI_path_util.h" /* For S_ISREG() and S_ISDIR() on Windows. */ @@ -32,6 +32,10 @@ # include "BLI_winstuff.h" #endif +#include "CLG_log.h" + +static CLG_LogRef LOG = {"bke.asset_service"}; + namespace blender::bke { const CatalogFilePath AssetCatalogService::DEFAULT_CATALOG_FILENAME = "blender_assets.cats.txt"; @@ -311,6 +315,7 @@ void AssetCatalogService::load_from_disk(const CatalogFilePath &file_or_director BLI_stat_t status; if (BLI_stat(file_or_directory_path.data(), &status) == -1) { /* TODO(@sybren): throw an appropriate exception. */ + CLOG_WARN(&LOG, "path not found: %s", file_or_directory_path.data()); return; } @@ -337,6 +342,7 @@ void AssetCatalogService::load_directory_recursive(const CatalogFilePath &direct if (!BLI_exists(file_path.data())) { /* No file to be loaded is perfectly fine. */ + CLOG_INFO(&LOG, 2, "path not found: %s", file_path.data()); return; } @@ -824,8 +830,12 @@ void AssetCatalogDefinitionFile::parse_catalog_file( const CatalogFilePath &catalog_definition_file_path, AssetCatalogParsedFn catalog_loaded_callback) { - std::fstream infile(catalog_definition_file_path); + fstream infile(catalog_definition_file_path, std::ios::in); + if (!infile.is_open()) { + CLOG_ERROR(&LOG, "%s: unable to open file", catalog_definition_file_path.c_str()); + return; + } bool seen_version_number = false; std::string line; while (std::getline(infile, line)) { @@ -956,7 +966,7 @@ bool AssetCatalogDefinitionFile::write_to_disk_unsafe(const CatalogFilePath &des return false; } - std::ofstream output(dest_file_path); + fstream output(dest_file_path, std::ios::out); /* TODO(@sybren): remember the line ending style that was originally read, then use that to write * the file again. */ diff --git a/source/blender/blenkernel/intern/asset_catalog_test.cc b/source/blender/blenkernel/intern/asset_catalog_test.cc index ba8f8716823..3ff7831b19a 100644 --- a/source/blender/blenkernel/intern/asset_catalog_test.cc +++ b/source/blender/blenkernel/intern/asset_catalog_test.cc @@ -27,6 +27,8 @@ #include "DNA_asset_types.h" #include "DNA_userdef_types.h" +#include "CLG_log.h" + #include "testing/testing.h" namespace blender::bke::tests { @@ -93,6 +95,18 @@ class AssetCatalogTest : public testing::Test { CatalogFilePath asset_library_root_; CatalogFilePath temp_library_path_; + static void SetUpTestSuite() + { + testing::Test::SetUpTestSuite(); + CLG_init(); + } + + static void TearDownTestSuite() + { + CLG_exit(); + testing::Test::TearDownTestSuite(); + } + void SetUp() override { const std::string test_files_dir = blender::tests::flags_test_asset_dir(); @@ -549,6 +563,30 @@ TEST_F(AssetCatalogTest, write_single_file) /* TODO(@sybren): test ordering of catalogs in the file. */ } +TEST_F(AssetCatalogTest, read_write_unicode_filepath) +{ + TestableAssetCatalogService service(asset_library_root_); + const CatalogFilePath load_from_path = asset_library_root_ + "/новый/" + + AssetCatalogService::DEFAULT_CATALOG_FILENAME; + service.load_from_disk(load_from_path); + + const CatalogFilePath save_to_path = use_temp_path() + "новый.cats.txt"; + AssetCatalogDefinitionFile *cdf = service.get_catalog_definition_file(); + ASSERT_NE(nullptr, cdf) << "unable to load " << load_from_path; + EXPECT_TRUE(cdf->write_to_disk(save_to_path)); + + AssetCatalogService loaded_service(save_to_path); + loaded_service.load_from_disk(); + + /* Test that the file was loaded correctly. */ + const bUUID materials_uuid("a2151dff-dead-4f29-b6bc-b2c7d6cccdb4"); + const AssetCatalog *cat = loaded_service.find_catalog(materials_uuid); + ASSERT_NE(nullptr, cat); + EXPECT_EQ(materials_uuid, cat->catalog_id); + EXPECT_EQ(AssetCatalogPath("Материалы"), cat->path); + EXPECT_EQ("Russian Materials", cat->simple_name); +} + TEST_F(AssetCatalogTest, no_writing_empty_files) { const CatalogFilePath temp_lib_root = create_temp_path(); diff --git a/source/blender/blenkernel/intern/blendfile_link_append.c b/source/blender/blenkernel/intern/blendfile_link_append.c index c265a6e2b7d..23e9e6bfbbb 100644 --- a/source/blender/blenkernel/intern/blendfile_link_append.c +++ b/source/blender/blenkernel/intern/blendfile_link_append.c @@ -440,6 +440,16 @@ static bool object_in_any_collection(Main *bmain, Object *ob) return false; } +static bool collection_instantiated_by_any_object(Main *bmain, Collection *collection) +{ + LISTBASE_FOREACH (Object *, ob, &bmain->objects) { + if (ob->type == OB_EMPTY && ob->instance_collection == collection) { + return true; + } + } + return false; +} + static ID *loose_data_instantiate_process_check(LooseDataInstantiateContext *instantiate_context, BlendfileLinkAppendContextItem *item) { @@ -633,12 +643,19 @@ static void loose_data_instantiate_collection_process( * children. */ Collection *collection = (Collection *)id; + /* The collection could be linked/appended together with an Empty object instantiating it, + * better not instantiate the collection in the viewlayer in that case. + * + * Can easily happen when copy/pasting such instantiating empty, see T93839. */ + const bool collection_is_instantiated = collection_instantiated_by_any_object(bmain, + collection); /* Always consider adding collections directly selected by the user. */ - bool do_add_collection = (item->tag & LINK_APPEND_TAG_INDIRECT) == 0; + bool do_add_collection = (item->tag & LINK_APPEND_TAG_INDIRECT) == 0 && + !collection_is_instantiated; /* In linking case, do not enforce instantiating non-directly linked collections/objects. * This avoids cluttering the ViewLayers, user can instantiate themselves specific collections * or objects easily from the Outliner if needed. */ - if (!do_add_collection && do_append) { + if (!do_add_collection && do_append && !collection_is_instantiated) { LISTBASE_FOREACH (CollectionObject *, coll_ob, &collection->gobject) { Object *ob = coll_ob->ob; if (!object_in_any_scene(bmain, ob)) { @@ -726,6 +743,8 @@ static void loose_data_instantiate_object_process(LooseDataInstantiateContext *i * if you want it do it at the editor level. */ const bool object_set_active = false; + const bool is_linking = (lapp_context->params->flag & FILE_LINK) != 0; + /* NOTE: For objects we only view_layer-instantiate duplicated objects that are not yet used * anywhere. */ LinkNode *itemlink; @@ -736,6 +755,17 @@ static void loose_data_instantiate_object_process(LooseDataInstantiateContext *i continue; } + /* In linking case, never instantiate stray objects that are not directly linked. + * + * While this is not ideal (in theory no object should remain un-owned), in case of indirectly + * linked objects, the other solution would be to add them to a local collection, which would + * make them directly linked. Think for now keeping them indirectly linked is more important. + * Ref. T93757. + */ + if (is_linking && (item->tag & LINK_APPEND_TAG_INDIRECT) != 0) { + continue; + } + Object *ob = (Object *)id; if (object_in_any_collection(bmain, ob)) { diff --git a/source/blender/blenkernel/intern/curve.cc b/source/blender/blenkernel/intern/curve.cc index 50b7c15774d..dc2527f9b62 100644 --- a/source/blender/blenkernel/intern/curve.cc +++ b/source/blender/blenkernel/intern/curve.cc @@ -43,7 +43,7 @@ #include "DNA_defaults.h" #include "DNA_material_types.h" -/* for dereferencing pointers */ +/* For dereferencing pointers. */ #include "DNA_key_types.h" #include "DNA_object_types.h" #include "DNA_vfont_types.h" @@ -1438,7 +1438,7 @@ void BKE_nurb_makeFaces(const Nurb *nu, float *coord_array, int rowstride, int r jstart = (int *)MEM_malloc_arrayN(totv, sizeof(float), "makeNurbfaces4"); jend = (int *)MEM_malloc_arrayN(totv, sizeof(float), "makeNurbfaces5"); - /* precalculation of basisv and jstart, jend */ + /* Pre-calculation of `basisv` and `jstart`, `jend`. */ if (nu->flagv & CU_NURB_CYCLIC) { cycl = nu->orderv - 1; } @@ -2104,10 +2104,10 @@ static void tilt_bezpart(const BezTriple *prevbezt, } } -/* make_bevel_list_3D_* funcs, at a minimum these must - * fill in the bezp->quat and bezp->dir values */ +/* `make_bevel_list_3D_*` functions, at a minimum these must + * fill in the #BevPoint.quat and #BevPoint.dir values. */ -/* utility for make_bevel_list_3D_* funcs */ +/** Utility for `make_bevel_list_3D_*` functions. */ static void bevel_list_calc_bisect(BevList *bl) { BevPoint *bevp2, *bevp1, *bevp0; @@ -2329,14 +2329,14 @@ static void make_bevel_list_3D_minimum_twist(BevList *bl) /* Need to correct for the start/end points not matching * do this by calculating the tilt angle difference, then apply - * the rotation gradually over the entire curve + * the rotation gradually over the entire curve. * - * note that the split is between last and second last, rather than first/last as youd expect. + * Note that the split is between last and second last, rather than first/last as you'd expect. * * real order is like this * 0,1,2,3,4 --> 1,2,3,4,0 * - * this is why we compare last with second last + * This is why we compare last with second last. */ float vec_1[3] = {0, 1, 0}, vec_2[3] = {0, 1, 0}, angle, ang_fac, cross_tmp[3]; @@ -2614,14 +2614,13 @@ void BKE_curve_bevelList_free(ListBase *bev) void BKE_curve_bevelList_make(Object *ob, const ListBase *nurbs, const bool for_render) { - /* - * - convert all curves to polys, with indication of resol and flags for double-vertices - * - possibly; do a smart vertice removal (in case Nurb) - * - separate in individual blocks with BoundBox - * - AutoHole detection + /* - Convert all curves to polys, with indication of resolution and flags for double-vertices. + * - Possibly; do a smart vertex removal (in case #Nurb). + * - Separate in individual blocks with #BoundBox. + * - Auto-hole detection. */ - /* this function needs an object, because of tflag and upflag */ + /* This function needs an object, because of `tflag` and `upflag`. */ Curve *cu = (Curve *)ob->data; BezTriple *bezt, *prevbezt; BPoint *bp; @@ -2637,7 +2636,7 @@ void BKE_curve_bevelList_make(Object *ob, const ListBase *nurbs, const bool for_ bool is_editmode = false; ListBase *bev; - /* segbevcount alsp requires seglen. */ + /* segbevcount also requires seglen. */ const bool need_seglen = ELEM( cu->bevfac1_mapping, CU_BEVFAC_MAP_SEGMENT, CU_BEVFAC_MAP_SPLINE) || ELEM(cu->bevfac2_mapping, CU_BEVFAC_MAP_SEGMENT, CU_BEVFAC_MAP_SPLINE); @@ -2805,10 +2804,10 @@ void BKE_curve_bevelList_make(Object *ob, const ListBase *nurbs, const bool for_ } } else { - /* always do all three, to prevent data hanging around */ + /* Always do all three, to prevent data hanging around. */ int j; - /* BevPoint must stay aligned to 4 so sizeof(BevPoint)/sizeof(float) works */ + /* #BevPoint must stay aligned to 4 so `sizeof(BevPoint) / sizeof(float)` works. */ for (j = 0; j < 3; j++) { BKE_curve_forward_diff_bezier(prevbezt->vec[1][j], prevbezt->vec[2][j], @@ -2819,7 +2818,7 @@ void BKE_curve_bevelList_make(Object *ob, const ListBase *nurbs, const bool for_ sizeof(BevPoint)); } - /* if both arrays are nullptr do nothiong */ + /* If both arrays are `nullptr` do nothing. */ tilt_bezpart(prevbezt, bezt, nu, @@ -2839,7 +2838,7 @@ void BKE_curve_bevelList_make(Object *ob, const ListBase *nurbs, const bool for_ sizeof(BevPoint)); } - /* seglen */ + /* `seglen`. */ if (seglen != nullptr) { *seglen = 0; *segbevcount = 0; @@ -2847,7 +2846,7 @@ void BKE_curve_bevelList_make(Object *ob, const ListBase *nurbs, const bool for_ bevp0 = bevp; bevp++; bevp->offset = len_v3v3(bevp0->vec, bevp->vec); - /* match seglen and segbevcount to the cleaned up bevel lists (see STEP 2) */ + /* Match `seglen` and `segbevcount` to the cleaned up bevel lists (see STEP 2). */ if (bevp->offset > threshold) { *seglen += bevp->offset; *segbevcount += 1; @@ -2980,7 +2979,7 @@ void BKE_curve_bevelList_make(Object *ob, const ListBase *nurbs, const bool for_ continue; } - nr = bl->nr - bl->dupe_nr + 1; /* +1 because vectorbezier sets flag too */ + nr = bl->nr - bl->dupe_nr + 1; /* +1 because vector-bezier sets flag too. */ blnew = (BevList *)MEM_mallocN(sizeof(BevList), "makeBevelList4"); memcpy(blnew, bl, sizeof(BevList)); blnew->bevpoints = (BevPoint *)MEM_calloc_arrayN(nr, sizeof(BevPoint), "makeBevelPoints4"); @@ -2992,7 +2991,7 @@ void BKE_curve_bevelList_make(Object *ob, const ListBase *nurbs, const bool for_ blnew->seglen = bl->seglen; blnew->nr = 0; BLI_remlink(bev, bl); - BLI_insertlinkbefore(bev, bl->next, blnew); /* to make sure bevlist is tuned with nurblist */ + BLI_insertlinkbefore(bev, bl->next, blnew); /* Ensure `bevlist` is tuned with `nurblist`. */ bevp0 = bl->bevpoints; bevp1 = blnew->bevpoints; nr = bl->nr; @@ -3114,7 +3113,7 @@ void BKE_curve_bevelList_make(Object *ob, const ListBase *nurbs, const bool for_ BevPoint *bevp = bl->bevpoints; unit_qt(bevp->quat); } - else if (bl->nr == 2) { /* 2 pnt, treat separate */ + else if (bl->nr == 2) { /* 2 points, treat separately. */ make_bevel_list_segment_2D(bl); } else { @@ -3129,7 +3128,7 @@ void BKE_curve_bevelList_make(Object *ob, const ListBase *nurbs, const bool for_ BevPoint *bevp = bl->bevpoints; unit_qt(bevp->quat); } - else if (bl->nr == 2) { /* 2 pnt, treat separate */ + else if (bl->nr == 2) { /* 2 points, treat separately. */ make_bevel_list_segment_3D(bl); } else { @@ -3321,13 +3320,13 @@ static void calchandleNurb_intern(BezTriple *bezt, } if (skip_align || - /* when one handle is free, alignming makes no sense, see: T35952 */ + /* When one handle is free, aligning makes no sense, see: T35952 */ ELEM(HD_FREE, bezt->h1, bezt->h2) || - /* also when no handles are aligned, skip this step */ + /* Also when no handles are aligned, skip this step. */ (!ELEM(HD_ALIGN, bezt->h1, bezt->h2) && !ELEM(HD_ALIGN_DOUBLESIDE, bezt->h1, bezt->h2))) { - /* handles need to be updated during animation and applying stuff like hooks, + /* Handles need to be updated during animation and applying stuff like hooks, * but in such situations it's quite difficult to distinguish in which order - * align handles should be aligned so skip them for now */ + * align handles should be aligned so skip them for now. */ return; } @@ -3996,8 +3995,8 @@ void BKE_nurb_handle_smooth_fcurve(BezTriple *bezt, int total, bool cyclic) } } - /* Find continuous subsequences of free auto handles and smooth them, starting at - * search_base. In cyclic mode these subsequences can span the cycle boundary. */ + /* Find continuous sub-sequences of free auto handles and smooth them, starting at search_base. + * In cyclic mode these sub-sequences can span the cycle boundary. */ int start = search_base, count = 1; for (int i = 1, j = start + 1; i < total; i++, j++) { @@ -4189,13 +4188,13 @@ void BKE_nurb_handles_autocalc(Nurb *nu, uint8_t flag) /* left handle: */ if (flag == 0 || (bezt1->f1 & flag)) { bezt1->h1 = HD_FREE; - /* distance too short: vectorhandle */ + /* Distance too short: vector-handle. */ if (len_squared_v3v3(bezt1->vec[1], bezt0->vec[1]) < eps_sq) { bezt1->h1 = HD_VECT; leftsmall = true; } else { - /* aligned handle? */ + /* Aligned handle? */ if (dist_squared_to_line_v3(bezt1->vec[1], bezt1->vec[0], bezt1->vec[2]) < eps_sq) { align = true; bezt1->h1 = HD_ALIGN; @@ -4209,13 +4208,13 @@ void BKE_nurb_handles_autocalc(Nurb *nu, uint8_t flag) /* right handle: */ if (flag == 0 || (bezt1->f3 & flag)) { bezt1->h2 = HD_FREE; - /* distance too short: vectorhandle */ + /* Distance too short: vector-handle. */ if (len_squared_v3v3(bezt1->vec[1], bezt2->vec[1]) < eps_sq) { bezt1->h2 = HD_VECT; rightsmall = true; } else { - /* aligned handle? */ + /* Aligned handle? */ if (align) { bezt1->h2 = HD_ALIGN; } @@ -4860,7 +4859,7 @@ bool BKE_nurb_type_convert(Nurb *nu, int a, c, nr; if (nu->type == CU_POLY) { - if (type == CU_BEZIER) { /* To Bezier with vecthandles. */ + if (type == CU_BEZIER) { /* To Bezier with vector-handles. */ nr = nu->pntsu; bezt = (BezTriple *)MEM_calloc_arrayN(nr, sizeof(BezTriple), "setsplinetype2"); nu->bezt = bezt; diff --git a/source/blender/blenkernel/intern/curveprofile.cc b/source/blender/blenkernel/intern/curveprofile.cc index 387709fca29..8f387be41d3 100644 --- a/source/blender/blenkernel/intern/curveprofile.cc +++ b/source/blender/blenkernel/intern/curveprofile.cc @@ -46,7 +46,7 @@ struct CurveProfile *BKE_curveprofile_add(eCurveProfilePresets preset) { - CurveProfile *profile = (CurveProfile *)MEM_callocN(sizeof(CurveProfile), __func__); + CurveProfile *profile = MEM_cnew<CurveProfile>(__func__); BKE_curveprofile_set_defaults(profile); profile->preset = preset; diff --git a/source/blender/blenkernel/intern/fcurve_driver.c b/source/blender/blenkernel/intern/fcurve_driver.c index 5496519e53b..ce30f80ba65 100644 --- a/source/blender/blenkernel/intern/fcurve_driver.c +++ b/source/blender/blenkernel/intern/fcurve_driver.c @@ -29,6 +29,7 @@ #include "BLI_alloca.h" #include "BLI_expr_pylike_eval.h" +#include "BLI_listbase.h" #include "BLI_math.h" #include "BLI_string_utils.h" #include "BLI_threads.h" @@ -864,6 +865,12 @@ void driver_variable_name_validate(DriverVar *dvar) } } +void driver_variable_unique_name(DriverVar *dvar) +{ + ListBase variables = BLI_listbase_from_link((Link *)dvar); + BLI_uniquename(&variables, dvar, dvar->name, '_', offsetof(DriverVar, name), sizeof(dvar->name)); +} + DriverVar *driver_add_new_variable(ChannelDriver *driver) { DriverVar *dvar; diff --git a/source/blender/blenkernel/intern/image.c b/source/blender/blenkernel/intern/image.c index f43cf00a310..edd5073da79 100644 --- a/source/blender/blenkernel/intern/image.c +++ b/source/blender/blenkernel/intern/image.c @@ -84,6 +84,7 @@ #include "BKE_lib_id.h" #include "BKE_main.h" #include "BKE_node.h" +#include "BKE_node_tree_update.h" #include "BKE_packedFile.h" #include "BKE_report.h" #include "BKE_scene.h" @@ -3699,16 +3700,8 @@ void BKE_image_signal(Main *bmain, Image *ima, ImageUser *iuser, int signal) BLI_mutex_unlock(ima->runtime.cache_mutex); - /* don't use notifiers because they are not 100% sure to succeeded - * this also makes sure all scenes are accounted for. */ - { - Scene *scene; - for (scene = bmain->scenes.first; scene; scene = scene->id.next) { - if (scene->nodetree) { - nodeUpdateID(scene->nodetree, &ima->id); - } - } - } + BKE_ntree_update_tag_id_changed(bmain, &ima->id); + BKE_ntree_update_main(bmain, NULL); } /* return renderpass for a given pass index and active view */ diff --git a/source/blender/blenkernel/intern/lib_override.c b/source/blender/blenkernel/intern/lib_override.c index 83ed0ee4c08..6e1bac71d3f 100644 --- a/source/blender/blenkernel/intern/lib_override.c +++ b/source/blender/blenkernel/intern/lib_override.c @@ -470,8 +470,9 @@ static void lib_override_group_tag_data_object_to_collection_init_collection_pro } LinkNodePair **collections_linkedlist_p; - if (!BLI_ghash_ensure_p( - data->linked_object_to_instantiating_collections, ob, &collections_linkedlist_p)) { + if (!BLI_ghash_ensure_p(data->linked_object_to_instantiating_collections, + ob, + (void ***)&collections_linkedlist_p)) { *collections_linkedlist_p = BLI_memarena_calloc(data->mem_arena, sizeof(**collections_linkedlist_p)); } diff --git a/source/blender/blenkernel/intern/lib_remap.c b/source/blender/blenkernel/intern/lib_remap.c index 3cea0de32ee..9ea85714b4a 100644 --- a/source/blender/blenkernel/intern/lib_remap.c +++ b/source/blender/blenkernel/intern/lib_remap.c @@ -346,7 +346,7 @@ static void libblock_remap_data_postprocess_obdata_relink(Main *bmain, Object *o static void libblock_remap_data_postprocess_nodetree_update(Main *bmain, ID *new_id) { /* Update all group nodes using a node group. */ - ntreeUpdateAllUsers(bmain, new_id, 0); + ntreeUpdateAllUsers(bmain, new_id); } /** diff --git a/source/blender/blenkernel/intern/linestyle.c b/source/blender/blenkernel/intern/linestyle.c index ac0dbcb715d..95f41ab4b39 100644 --- a/source/blender/blenkernel/intern/linestyle.c +++ b/source/blender/blenkernel/intern/linestyle.c @@ -50,6 +50,7 @@ #include "BKE_linestyle.h" #include "BKE_main.h" #include "BKE_node.h" +#include "BKE_node_tree_update.h" #include "BKE_texture.h" #include "BLO_read_write.h" @@ -2085,5 +2086,5 @@ void BKE_linestyle_default_shader(const bContext *C, FreestyleLineStyle *linesty tosock = BLI_findlink(&output_linestyle->inputs, 0); /* Color */ nodeAddLink(ntree, input_texure, fromsock, output_linestyle, tosock); - ntreeUpdateTree(CTX_data_main(C), ntree); + BKE_ntree_update_main_tree(CTX_data_main(C), ntree, NULL); } diff --git a/source/blender/blenkernel/intern/mesh_convert.cc b/source/blender/blenkernel/intern/mesh_convert.cc index e8054884f26..07c126861f0 100644 --- a/source/blender/blenkernel/intern/mesh_convert.cc +++ b/source/blender/blenkernel/intern/mesh_convert.cc @@ -917,7 +917,7 @@ static void curve_to_mesh_eval_ensure(Object &object) * will have no modifiers. */ Object bevel_object = {{nullptr}}; if (curve.bevobj != nullptr) { - bevel_object = *curve.bevobj; + memcpy(&bevel_object, curve.bevobj, sizeof(bevel_object)); BLI_listbase_clear(&bevel_object.modifiers); BKE_object_runtime_reset(&bevel_object); curve.bevobj = &bevel_object; @@ -926,7 +926,7 @@ static void curve_to_mesh_eval_ensure(Object &object) /* Same thing for taper. */ Object taper_object = {{nullptr}}; if (curve.taperobj != nullptr) { - taper_object = *curve.taperobj; + memcpy(&taper_object, curve.taperobj, sizeof(taper_object)); BLI_listbase_clear(&taper_object.modifiers); BKE_object_runtime_reset(&taper_object); curve.taperobj = &taper_object; @@ -1065,7 +1065,8 @@ static Mesh *mesh_new_from_mesh_object_with_layers(Depsgraph *depsgraph, return nullptr; } - Object object_for_eval = *object; + Object object_for_eval; + memcpy(&object_for_eval, object, sizeof(object_for_eval)); if (object_for_eval.runtime.data_orig != nullptr) { object_for_eval.data = object_for_eval.runtime.data_orig; } @@ -1440,7 +1441,8 @@ void BKE_mesh_nomain_to_mesh(Mesh *mesh_src, /* mesh_src might depend on mesh_dst, so we need to do everything with a local copy */ /* TODO(Sybren): the above claim came from 2.7x derived-mesh code (DM_to_mesh); * check whether it is still true with Mesh */ - Mesh tmp = *mesh_dst; + Mesh tmp; + memcpy(&tmp, mesh_dst, sizeof(tmp)); int totvert, totedge /*, totface */ /* UNUSED */, totloop, totpoly; bool did_shapekeys = false; eCDAllocType alloctype = CD_DUPLICATE; diff --git a/source/blender/blenkernel/intern/movieclip.c b/source/blender/blenkernel/intern/movieclip.c index fc2e7d0a6a3..b0c93a5614d 100644 --- a/source/blender/blenkernel/intern/movieclip.c +++ b/source/blender/blenkernel/intern/movieclip.c @@ -70,6 +70,7 @@ #include "BKE_main.h" #include "BKE_movieclip.h" #include "BKE_node.h" +#include "BKE_node_tree_update.h" #include "BKE_tracking.h" #include "IMB_imbuf.h" @@ -1695,17 +1696,7 @@ void BKE_movieclip_reload(Main *bmain, MovieClip *clip) movieclip_calc_length(clip); - /* same as for image update -- don't use notifiers because they are not 100% sure to succeeded - * (node trees which are not currently visible wouldn't be refreshed) - */ - { - Scene *scene; - for (scene = bmain->scenes.first; scene; scene = scene->id.next) { - if (scene->nodetree) { - nodeUpdateID(scene->nodetree, &clip->id); - } - } - } + BKE_ntree_update_tag_id_changed(bmain, &clip->id); } void BKE_movieclip_update_scopes(MovieClip *clip, MovieClipUser *user, MovieClipScopes *scopes) diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index be458ed036e..7121ef20207 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -74,6 +74,7 @@ #include "BKE_lib_query.h" #include "BKE_main.h" #include "BKE_node.h" +#include "BKE_node_tree_update.h" #include "RNA_access.h" #include "RNA_define.h" @@ -261,8 +262,7 @@ static void ntree_free_data(ID *id) /* XXX hack! node trees should not store execution graphs at all. * This should be removed when old tree types no longer require it. * Currently the execution data for texture nodes remains in the tree - * after execution, until the node tree is updated or freed. - */ + * after execution, until the node tree is updated or freed. */ if (ntree->execdata) { switch (ntree->type) { case NTREE_SHADER: @@ -278,10 +278,10 @@ static void ntree_free_data(ID *id) /* XXX not nice, but needed to free localized node groups properly */ free_localized_node_groups(ntree); - /* unregister associated RNA types */ + /* Unregister associated RNA types. */ ntreeInterfaceTypeFree(ntree); - BLI_freelistN(&ntree->links); /* do first, then unlink_node goes fast */ + BLI_freelistN(&ntree->links); LISTBASE_FOREACH_MUTABLE (bNode *, node, &ntree->nodes) { node_free_node(ntree, node); @@ -522,7 +522,6 @@ static void write_node_socket_default_value(BlendWriter *writer, bNodeSocket *so static void write_node_socket(BlendWriter *writer, bNodeSocket *sock) { - /* actual socket writing */ BLO_write_struct(writer, bNodeSocket, sock); if (sock->prop) { @@ -533,7 +532,6 @@ static void write_node_socket(BlendWriter *writer, bNodeSocket *sock) } static void write_node_socket_interface(BlendWriter *writer, bNodeSocket *sock) { - /* actual socket writing */ BLO_write_struct(writer, bNodeSocket, sock); if (sock->prop) { @@ -547,8 +545,6 @@ void ntreeBlendWrite(BlendWriter *writer, bNodeTree *ntree) { BKE_id_blend_write(writer, &ntree->id); - /* for link_list() speed, we write per list */ - if (ntree->adt) { BKE_animdata_blend_write(writer, ntree->adt); } @@ -572,7 +568,6 @@ void ntreeBlendWrite(BlendWriter *writer, bNodeTree *ntree) } if (node->storage) { - /* could be handlerized at some point, now only 1 exception still */ if (ELEM(ntree->type, NTREE_SHADER, NTREE_GEOMETRY) && ELEM(node->type, SH_NODE_CURVE_VEC, SH_NODE_CURVE_RGB, SH_NODE_CURVE_FLOAT)) { BKE_curvemapping_blend_write(writer, (const CurveMapping *)node->storage); @@ -646,13 +641,13 @@ void ntreeBlendWrite(BlendWriter *writer, bNodeTree *ntree) } if (node->type == CMP_NODE_OUTPUT_FILE) { - /* inputs have own storage data */ + /* Inputs have their own storage data. */ LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) { BLO_write_struct(writer, NodeImageMultiFileSocket, sock->storage); } } if (ELEM(node->type, CMP_NODE_IMAGE, CMP_NODE_R_LAYERS)) { - /* write extra socket info */ + /* Write extra socket info. */ LISTBASE_FOREACH (bNodeSocket *, sock, &node->outputs) { BLO_write_struct(writer, NodeImageLayer, sock->storage); } @@ -716,6 +711,7 @@ void ntreeBlendReadData(BlendDataReader *reader, bNodeTree *ntree) ntree->execdata = nullptr; ntree->field_inferencing_interface = nullptr; + BKE_ntree_update_tag_missing_runtime_data(ntree); BLO_read_data_address(reader, &ntree->adt); BKE_animdata_blend_read_data(reader, ntree->adt); @@ -748,7 +744,6 @@ void ntreeBlendReadData(BlendDataReader *reader, bNodeTree *ntree) } if (node->storage) { - /* could be handlerized at some point */ switch (node->type) { case SH_NODE_CURVE_VEC: case SH_NODE_CURVE_RGB: @@ -859,11 +854,6 @@ void ntreeBlendReadData(BlendDataReader *reader, bNodeTree *ntree) /* TODO: should be dealt by new generic cache handling of IDs... */ ntree->previews = nullptr; - if (ntree->type == NTREE_GEOMETRY) { - /* Update field referencing for the geometry nodes modifier. */ - ntree->update |= NTREE_UPDATE_FIELD_INFERENCING; - } - BLO_read_data_address(reader, &ntree->preview); BKE_previewimg_blend_read(reader, ntree->preview); @@ -1096,21 +1086,18 @@ static void node_add_sockets_from_type(bNodeTree *ntree, bNode *node, bNodeType return; } bNodeSocketTemplate *sockdef; - /* bNodeSocket *sock; */ /* UNUSED */ if (ntype->inputs) { sockdef = ntype->inputs; while (sockdef->type != -1) { - /* sock = */ node_add_socket_from_template(ntree, node, sockdef, SOCK_IN); - + node_add_socket_from_template(ntree, node, sockdef, SOCK_IN); sockdef++; } } if (ntype->outputs) { sockdef = ntype->outputs; while (sockdef->type != -1) { - /* sock = */ node_add_socket_from_template(ntree, node, sockdef, SOCK_OUT); - + node_add_socket_from_template(ntree, node, sockdef, SOCK_OUT); sockdef++; } } @@ -1168,8 +1155,7 @@ static void node_init(const struct bContext *C, bNodeTree *ntree, bNode *node) RNA_pointer_create((ID *)ntree, &RNA_Node, node, &ptr); /* XXX Warning: context can be nullptr in case nodes are added in do_versions. - * Delayed init is not supported for nodes with context-based `initfunc_api` at the moment. - */ + * Delayed init is not supported for nodes with context-based `initfunc_api` at the moment. */ BLI_assert(C != nullptr); ntype->initfunc_api(C, &ptr); } @@ -1190,6 +1176,7 @@ static void ntree_set_typeinfo(bNodeTree *ntree, bNodeTreeType *typeinfo) /* Deprecated integer type. */ ntree->type = ntree->typeinfo->type; + BKE_ntree_update_tag_all(ntree); } static void node_set_typeinfo(const struct bContext *C, @@ -1240,6 +1227,7 @@ static void node_socket_set_typeinfo(bNodeTree *ntree, ntree->init &= ~NTREE_TYPE_INIT; } + BKE_ntree_update_tag_socket_type(ntree, sock); } /* Set specific typeinfo pointers in all node trees on register/unregister */ @@ -1383,18 +1371,6 @@ bNodeType *nodeTypeFind(const char *idname) return nullptr; } -static void free_dynamic_typeinfo(bNodeType *ntype) -{ - if (ntype->type == NODE_DYNAMIC) { - if (ntype->inputs) { - MEM_freeN(ntype->inputs); - } - if (ntype->outputs) { - MEM_freeN(ntype->outputs); - } - } -} - /* callback for hash value free function */ static void node_free_type(void *nodetype_v) { @@ -1404,11 +1380,6 @@ static void node_free_type(void *nodetype_v) * or we'd want to update *all* active Mains, which we cannot do anyway currently. */ update_typeinfo(G_MAIN, nullptr, nullptr, nodetype, nullptr, true); - /* XXX deprecated */ - if (nodetype->type == NODE_DYNAMIC) { - free_dynamic_typeinfo(nodetype); - } - delete nodetype->fixed_declaration; nodetype->fixed_declaration = nullptr; @@ -1580,7 +1551,7 @@ static bNodeSocket *make_socket(bNodeTree *ntree, /* if no explicit identifier is given, assign a unique identifier based on the name */ BLI_strncpy(auto_identifier, name, sizeof(auto_identifier)); } - /* make the identifier unique */ + /* Make the identifier unique. */ BLI_uniquename_cb( unique_identifier_check, lb, "socket", '_', auto_identifier, sizeof(auto_identifier)); @@ -1749,7 +1720,7 @@ bNodeSocket *nodeAddSocket(bNodeTree *ntree, BLI_remlink(lb, sock); /* does nothing for new socket */ BLI_addtail(lb, sock); - node->update |= NODE_UPDATE; + BKE_ntree_update_tag_socket_new(ntree, sock); return sock; } @@ -1768,8 +1739,6 @@ bNodeSocket *nodeInsertSocket(bNodeTree *ntree, BLI_remlink(lb, sock); /* does nothing for new socket */ BLI_insertlinkbefore(lb, next_sock, sock); - node->update |= NODE_UPDATE; - return sock; } @@ -2010,10 +1979,7 @@ bNodeSocket *nodeInsertStaticSocket(bNodeTree *ntree, return sock; } -static void node_socket_free(bNodeTree *UNUSED(ntree), - bNodeSocket *sock, - bNode *UNUSED(node), - const bool do_id_user) +static void node_socket_free(bNodeSocket *sock, const bool do_id_user) { if (sock->prop) { IDP_FreePropertyContent_ex(sock->prop, do_id_user); @@ -2048,10 +2014,10 @@ void nodeRemoveSocketEx(struct bNodeTree *ntree, BLI_remlink(&node->inputs, sock); BLI_remlink(&node->outputs, sock); - node_socket_free(ntree, sock, node, do_id_user); + node_socket_free(sock, do_id_user); MEM_freeN(sock); - node->update |= NODE_UPDATE; + BKE_ntree_update_tag_socket_removed(ntree); } void nodeRemoveAllSockets(bNodeTree *ntree, bNode *node) @@ -2063,18 +2029,18 @@ void nodeRemoveAllSockets(bNodeTree *ntree, bNode *node) } LISTBASE_FOREACH_MUTABLE (bNodeSocket *, sock, &node->inputs) { - node_socket_free(ntree, sock, node, true); + node_socket_free(sock, true); MEM_freeN(sock); } BLI_listbase_clear(&node->inputs); LISTBASE_FOREACH_MUTABLE (bNodeSocket *, sock, &node->outputs) { - node_socket_free(ntree, sock, node, true); + node_socket_free(sock, true); MEM_freeN(sock); } BLI_listbase_clear(&node->outputs); - node->update |= NODE_UPDATE; + BKE_ntree_update_tag_socket_removed(ntree); } bNode *nodeFindNodebyName(bNodeTree *ntree, const char *name) @@ -2226,7 +2192,7 @@ bNode *nodeAddNode(const struct bContext *C, bNodeTree *ntree, const char *idnam BLI_strncpy(node->idname, idname, sizeof(node->idname)); node_set_typeinfo(C, ntree, node, nodeTypeFind(idname)); - ntree->update |= NTREE_UPDATE_NODES; + BKE_ntree_update_tag_node_new(ntree, node); return node; } @@ -2236,9 +2202,8 @@ bNode *nodeAddStaticNode(const struct bContext *C, bNodeTree *ntree, int type) const char *idname = nullptr; NODE_TYPES_BEGIN (ntype) { - /* do an extra poll here, because some int types are used - * for multiple node types, this helps find the desired type - */ + /* Do an extra poll here, because some int types are used + * for multiple node types, this helps find the desired type. */ const char *disabled_hint; if (ntype->type == type && (!ntype->poll || ntype->poll(ntype, ntree, &disabled_hint))) { idname = ntype->idname; @@ -2268,9 +2233,8 @@ static void node_socket_copy(bNodeSocket *sock_dst, const bNodeSocket *sock_src, } sock_dst->stack_index = 0; - /* XXX some compositor node (e.g. image, render layers) still store - * some persistent buffer data here, need to clear this to avoid dangling pointers. - */ + /* XXX some compositor nodes (e.g. image, render layers) still store + * some persistent buffer data here, need to clear this to avoid dangling pointers. */ sock_dst->cache = nullptr; } @@ -2285,7 +2249,7 @@ bNode *BKE_node_copy_ex(bNodeTree *ntree, *node_dst = *node_src; - /* can be called for nodes outside a node tree (e.g. clipboard) */ + /* Can be called for nodes outside a node tree (e.g. clipboard). */ if (ntree) { if (unique_name) { nodeUniqueName(ntree, node_dst); @@ -2352,7 +2316,7 @@ bNode *BKE_node_copy_ex(bNodeTree *ntree, } if (ntree) { - ntree->update |= NTREE_UPDATE_NODES; + BKE_ntree_update_tag_node_new(ntree, node_dst); } /* Reset the declaration of the new node. */ @@ -2422,7 +2386,7 @@ bNodeLink *nodeAddLink( { bNodeLink *link = nullptr; - /* test valid input */ + /* Test valid input. */ BLI_assert(fromnode); BLI_assert(tonode); @@ -2449,7 +2413,7 @@ bNodeLink *nodeAddLink( } if (ntree) { - ntree->update |= NTREE_UPDATE_LINKS; + BKE_ntree_update_tag_link_added(ntree, link); } if (link != nullptr && link->tosock->flag & SOCK_MULTI_INPUT) { @@ -2461,7 +2425,7 @@ bNodeLink *nodeAddLink( void nodeRemLink(bNodeTree *ntree, bNodeLink *link) { - /* can be called for links outside a node tree (e.g. clipboard) */ + /* Can be called for links outside a node tree (e.g. clipboard). */ if (ntree) { BLI_remlink(&ntree->links, link); } @@ -2472,7 +2436,7 @@ void nodeRemLink(bNodeTree *ntree, bNodeLink *link) MEM_freeN(link); if (ntree) { - ntree->update |= NTREE_UPDATE_LINKS; + BKE_ntree_update_tag_link_removed(ntree); } } @@ -2572,7 +2536,7 @@ void nodeMuteLinkToggle(bNodeTree *ntree, bNodeLink *link) } if (ntree) { - ntree->update |= NTREE_UPDATE_LINKS; + BKE_ntree_update_tag_link_mute(ntree, link); } } @@ -2583,8 +2547,6 @@ void nodeRemSocketLinks(bNodeTree *ntree, bNodeSocket *sock) nodeRemLink(ntree, link); } } - - ntree->update |= NTREE_UPDATE_LINKS; } bool nodeLinkIsHidden(const bNodeLink *link) @@ -2649,7 +2611,7 @@ void nodeInternalRelink(bNodeTree *ntree, bNode *node) link->flag |= NODE_LINK_MUTED; } - ntree->update |= NTREE_UPDATE_LINKS; + BKE_ntree_update_tag_link_changed(ntree); } else { if (link->tosock->flag & SOCK_MULTI_INPUT) { @@ -2843,8 +2805,11 @@ bool BKE_node_preview_used(const bNode *node) return (node->typeinfo->flag & NODE_PREVIEW) != 0; } -bNodePreview *BKE_node_preview_verify( - bNodeInstanceHash *previews, bNodeInstanceKey key, int xsize, int ysize, bool create) +bNodePreview *BKE_node_preview_verify(bNodeInstanceHash *previews, + bNodeInstanceKey key, + const int xsize, + const int ysize, + const bool create) { bNodePreview *preview = (bNodePreview *)BKE_node_instance_hash_lookup(previews, key); if (!preview) { @@ -2901,9 +2866,8 @@ void BKE_node_preview_free(bNodePreview *preview) static void node_preview_init_tree_recursive(bNodeInstanceHash *previews, bNodeTree *ntree, bNodeInstanceKey parent_key, - int xsize, - int ysize, - bool create_previews) + const int xsize, + const int ysize) { LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { bNodeInstanceKey key = BKE_node_instance_key(parent_key, ntree, node); @@ -2912,17 +2876,16 @@ static void node_preview_init_tree_recursive(bNodeInstanceHash *previews, node->preview_xsize = xsize; node->preview_ysize = ysize; - BKE_node_preview_verify(previews, key, xsize, ysize, create_previews); + BKE_node_preview_verify(previews, key, xsize, ysize, false); } if (node->type == NODE_GROUP && node->id) { - node_preview_init_tree_recursive( - previews, (bNodeTree *)node->id, key, xsize, ysize, create_previews); + node_preview_init_tree_recursive(previews, (bNodeTree *)node->id, key, xsize, ysize); } } } -void BKE_node_preview_init_tree(bNodeTree *ntree, int xsize, int ysize, bool create_previews) +void BKE_node_preview_init_tree(bNodeTree *ntree, int xsize, int ysize) { if (!ntree) { return; @@ -2932,8 +2895,7 @@ void BKE_node_preview_init_tree(bNodeTree *ntree, int xsize, int ysize, bool cre ntree->previews = BKE_node_instance_hash_new("node previews"); } - node_preview_init_tree_recursive( - ntree->previews, ntree, NODE_INSTANCE_KEY_BASE, xsize, ysize, create_previews); + node_preview_init_tree_recursive(ntree->previews, ntree, NODE_INSTANCE_KEY_BASE, xsize, ysize); } static void node_preview_tag_used_recursive(bNodeInstanceHash *previews, @@ -3069,27 +3031,6 @@ void BKE_node_preview_merge_tree(bNodeTree *to_ntree, bNodeTree *from_ntree, boo } } -void BKE_node_preview_set_pixel( - bNodePreview *preview, const float col[4], int x, int y, bool do_manage) -{ - if (preview) { - if (x >= 0 && y >= 0) { - if (x < preview->xsize && y < preview->ysize) { - unsigned char *tar = preview->rect + 4 * ((preview->xsize * y) + x); - - if (do_manage) { - linearrgb_to_srgb_uchar4(tar, col); - } - else { - rgba_float_to_uchar(tar, col); - } - } - // else printf("prv out bound x y %d %d\n", x, y); - } - // else printf("prv out bound x y %d %d\n", x, y); - } -} - /* ************** Free stuff ********** */ void nodeUnlinkNode(bNodeTree *ntree, bNode *node) @@ -3098,9 +3039,6 @@ void nodeUnlinkNode(bNodeTree *ntree, bNode *node) ListBase *lb; if (link->fromnode == node) { lb = &node->outputs; - if (link->tonode) { - link->tonode->update |= NODE_UPDATE; - } } else if (link->tonode == node) { lb = &node->inputs; @@ -3142,10 +3080,6 @@ static void node_free_node(bNodeTree *ntree, bNode *node) /* can be called for nodes outside a node tree (e.g. clipboard) */ if (ntree) { - /* remove all references to this node */ - nodeUnlinkNode(ntree, node); - node_unlink_attached(ntree, node); - BLI_remlink(&ntree->nodes, node); if (ntree->typeinfo->free_node_cache) { @@ -3165,12 +3099,12 @@ static void node_free_node(bNodeTree *ntree, bNode *node) LISTBASE_FOREACH_MUTABLE (bNodeSocket *, sock, &node->inputs) { /* Remember, no ID user refcount management here! */ - node_socket_free(ntree, sock, node, false); + node_socket_free(sock, false); MEM_freeN(sock); } LISTBASE_FOREACH_MUTABLE (bNodeSocket *, sock, &node->outputs) { /* Remember, no ID user refcount management here! */ - node_socket_free(ntree, sock, node, false); + node_socket_free(sock, false); MEM_freeN(sock); } @@ -3189,7 +3123,7 @@ static void node_free_node(bNodeTree *ntree, bNode *node) MEM_freeN(node); if (ntree) { - ntree->update |= NTREE_UPDATE_NODES; + BKE_ntree_update_tag_node_removed(ntree); } } @@ -3197,6 +3131,12 @@ void ntreeFreeLocalNode(bNodeTree *ntree, bNode *node) { /* For removing nodes while editing localized node trees. */ BLI_assert((ntree->id.tag & LIB_TAG_LOCALIZED) != 0); + + /* These two lines assume the caller might want to free a single node and maintain + * a valid state in the node tree. */ + nodeUnlinkNode(ntree, node); + node_unlink_attached(ntree, node); + node_free_node(ntree, node); } @@ -3241,6 +3181,9 @@ void nodeRemoveNode(Main *bmain, bNodeTree *ntree, bNode *node, bool do_id_user) } } + nodeUnlinkNode(ntree, node); + node_unlink_attached(ntree, node); + /* Free node itself. */ node_free_node(ntree, node); } @@ -3266,8 +3209,7 @@ static void free_localized_node_groups(bNodeTree *ntree) /* Only localized node trees store a copy for each node group tree. * Each node group tree in a localized node tree can be freed, * since it is a localized copy itself (no risk of accessing free'd - * data in main, see T37939). - */ + * data in main, see T37939). */ if (!(ntree->id.tag & LIB_TAG_LOCALIZED)) { return; } @@ -3463,7 +3405,7 @@ bNodeTree *ntreeLocalize(bNodeTree *ntree) } } - /* ensures only a single output node is enabled */ + /* Ensures only a single output node is enabled. */ ntreeSetOutput(ntree); bNode *node_src = (bNode *)ntree->nodes.first; @@ -3560,12 +3502,11 @@ bNodeSocket *ntreeAddSocketInterface(bNodeTree *ntree, bNodeSocket *iosock = make_socket_interface(ntree, in_out, idname, name); if (in_out == SOCK_IN) { BLI_addtail(&ntree->inputs, iosock); - ntree->update |= NTREE_UPDATE_GROUP_IN; } else if (in_out == SOCK_OUT) { BLI_addtail(&ntree->outputs, iosock); - ntree->update |= NTREE_UPDATE_GROUP_OUT; } + BKE_ntree_update_tag_interface(ntree); return iosock; } @@ -3578,12 +3519,11 @@ bNodeSocket *ntreeInsertSocketInterface(bNodeTree *ntree, bNodeSocket *iosock = make_socket_interface(ntree, in_out, idname, name); if (in_out == SOCK_IN) { BLI_insertlinkbefore(&ntree->inputs, next_sock, iosock); - ntree->update |= NTREE_UPDATE_GROUP_IN; } else if (in_out == SOCK_OUT) { BLI_insertlinkbefore(&ntree->outputs, next_sock, iosock); - ntree->update |= NTREE_UPDATE_GROUP_OUT; } + BKE_ntree_update_tag_interface(ntree); return iosock; } @@ -3629,7 +3569,7 @@ void ntreeRemoveSocketInterface(bNodeTree *ntree, bNodeSocket *sock) node_socket_interface_free(ntree, sock, true); MEM_freeN(sock); - ntree->update |= NTREE_UPDATE_GROUP; + BKE_ntree_update_tag_interface(ntree); } /* generates a valid RNA identifier from the node tree name */ @@ -3973,10 +3913,13 @@ int nodeSocketIsHidden(const bNodeSocket *sock) return ((sock->flag & (SOCK_HIDDEN | SOCK_UNAVAIL)) != 0); } -void nodeSetSocketAvailability(bNodeTree *UNUSED(ntree), bNodeSocket *sock, bool is_available) +void nodeSetSocketAvailability(bNodeTree *ntree, bNodeSocket *sock, bool is_available) { - /* #ntree is not needed right now, but it's generally necessary when changing the tree because we - * want to tag it as changed in the future. */ + const bool was_available = (sock->flag & SOCK_UNAVAIL) == 0; + if (is_available != was_available) { + BKE_ntree_update_tag_socket_availability(ntree, sock); + } + if (is_available) { sock->flag &= ~SOCK_UNAVAIL; } @@ -4428,7 +4371,7 @@ void ntreeGetDependencyList(struct bNodeTree *ntree, struct bNode ***r_deplist, } /* only updates node->level for detecting cycles links */ -static void ntree_update_node_level(bNodeTree *ntree) +void ntreeUpdateNodeLevels(bNodeTree *ntree) { /* first clear tag */ LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { @@ -4463,749 +4406,42 @@ void ntreeTagUsedSockets(bNodeTree *ntree) } } -static void ntree_update_link_pointers(bNodeTree *ntree) -{ - /* first clear data */ - LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) { - sock->link = nullptr; - } - } - - LISTBASE_FOREACH (bNodeLink *, link, &ntree->links) { - link->tosock->link = link; - } - - ntreeTagUsedSockets(ntree); -} - -static void ntree_validate_links(bNodeTree *ntree) -{ - LISTBASE_FOREACH (bNodeLink *, link, &ntree->links) { - link->flag |= NODE_LINK_VALID; - if (link->fromnode && link->tonode && link->fromnode->level <= link->tonode->level) { - link->flag &= ~NODE_LINK_VALID; - } - else if (ntree->typeinfo->validate_link) { - if (!ntree->typeinfo->validate_link((eNodeSocketDatatype)link->fromsock->type, - (eNodeSocketDatatype)link->tosock->type)) { - link->flag &= ~NODE_LINK_VALID; - } - } - } -} - void ntreeUpdateAllNew(Main *main) { + Vector<bNodeTree *> new_ntrees; + /* Update all new node trees on file read or append, to add/remove sockets * in groups nodes if the group changed, and handle any update flags that * might have been set in file reading or versioning. */ FOREACH_NODETREE_BEGIN (main, ntree, owner_id) { if (owner_id->tag & LIB_TAG_NEW) { - LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - if (node->typeinfo->group_update_func) { - node->typeinfo->group_update_func(ntree, node); - } - } - - ntreeUpdateTree(nullptr, ntree); + BKE_ntree_update_tag_all(ntree); } } FOREACH_NODETREE_END; + BKE_ntree_update_main(main, nullptr); } -namespace blender::bke::node_field_inferencing { - -static bool is_field_socket_type(eNodeSocketDatatype type) -{ - return ELEM(type, SOCK_FLOAT, SOCK_INT, SOCK_BOOLEAN, SOCK_VECTOR, SOCK_RGBA); -} - -static bool is_field_socket_type(const SocketRef &socket) -{ - return is_field_socket_type((eNodeSocketDatatype)socket.typeinfo()->type); -} - -static bool update_field_inferencing(bNodeTree &btree); - -static InputSocketFieldType get_interface_input_field_type(const NodeRef &node, - const InputSocketRef &socket) -{ - if (!is_field_socket_type(socket)) { - return InputSocketFieldType::None; - } - if (node.is_reroute_node()) { - return InputSocketFieldType::IsSupported; - } - if (node.is_group_output_node()) { - /* Outputs always support fields when the data type is correct. */ - return InputSocketFieldType::IsSupported; - } - if (node.is_undefined()) { - return InputSocketFieldType::None; - } - - const NodeDeclaration *node_decl = node.declaration(); - - /* Node declarations should be implemented for nodes involved here. */ - BLI_assert(node_decl != nullptr); - - /* Get the field type from the declaration. */ - const SocketDeclaration &socket_decl = *node_decl->inputs()[socket.index()]; - const InputSocketFieldType field_type = socket_decl.input_field_type(); - if (field_type == InputSocketFieldType::Implicit) { - return field_type; - } - if (node_decl->is_function_node()) { - /* In a function node, every socket supports fields. */ - return InputSocketFieldType::IsSupported; - } - return field_type; -} - -static OutputFieldDependency get_interface_output_field_dependency(const NodeRef &node, - const OutputSocketRef &socket) -{ - if (!is_field_socket_type(socket)) { - /* Non-field sockets always output data. */ - return OutputFieldDependency::ForDataSource(); - } - if (node.is_reroute_node()) { - /* The reroute just forwards what is passed in. */ - return OutputFieldDependency::ForDependentField(); - } - if (node.is_group_input_node()) { - /* Input nodes get special treatment in #determine_group_input_states. */ - return OutputFieldDependency::ForDependentField(); - } - if (node.is_undefined()) { - return OutputFieldDependency::ForDataSource(); - } - - const NodeDeclaration *node_decl = node.declaration(); - - /* Node declarations should be implemented for nodes involved here. */ - BLI_assert(node_decl != nullptr); - - if (node_decl->is_function_node()) { - /* In a generic function node, all outputs depend on all inputs. */ - return OutputFieldDependency::ForDependentField(); - } - - /* Use the socket declaration. */ - const SocketDeclaration &socket_decl = *node_decl->outputs()[socket.index()]; - return socket_decl.output_field_dependency(); -} - -static FieldInferencingInterface get_dummy_field_inferencing_interface(const NodeRef &node) -{ - FieldInferencingInterface inferencing_interface; - inferencing_interface.inputs.append_n_times(InputSocketFieldType::None, node.inputs().size()); - inferencing_interface.outputs.append_n_times(OutputFieldDependency::ForDataSource(), - node.outputs().size()); - return inferencing_interface; -} - -/** - * Retrieves information about how the node interacts with fields. - * In the future, this information can be stored in the node declaration. This would allow this - * function to return a reference, making it more efficient. - */ -static FieldInferencingInterface get_node_field_inferencing_interface(const NodeRef &node) -{ - /* Node groups already reference all required information, so just return that. */ - if (node.is_group_node()) { - bNodeTree *group = (bNodeTree *)node.bnode()->id; - if (group == nullptr) { - return FieldInferencingInterface(); - } - if (!ntreeIsRegistered(group)) { - /* This can happen when there is a linked node group that was not found (see T92799). */ - return get_dummy_field_inferencing_interface(node); - } - if (group->field_inferencing_interface == nullptr) { - /* Update group recursively. */ - update_field_inferencing(*group); - } - return *group->field_inferencing_interface; - } - - FieldInferencingInterface inferencing_interface; - for (const InputSocketRef *input_socket : node.inputs()) { - inferencing_interface.inputs.append(get_interface_input_field_type(node, *input_socket)); - } - - for (const OutputSocketRef *output_socket : node.outputs()) { - inferencing_interface.outputs.append( - get_interface_output_field_dependency(node, *output_socket)); - } - return inferencing_interface; -} - -/** - * This struct contains information for every socket. The values are propagated through the - * network. - */ -struct SocketFieldState { - /* This socket starts a new field. */ - bool is_field_source = false; - /* This socket can never become a field, because the node itself does not support it. */ - bool is_always_single = false; - /* This socket is currently a single value. It could become a field though. */ - bool is_single = true; - /* This socket is required to be a single value. This can be because the node itself only - * supports this socket to be a single value, or because a node afterwards requires this to be a - * single value. */ - bool requires_single = false; -}; - -static Vector<const InputSocketRef *> gather_input_socket_dependencies( - const OutputFieldDependency &field_dependency, const NodeRef &node) -{ - const OutputSocketFieldType type = field_dependency.field_type(); - Vector<const InputSocketRef *> input_sockets; - switch (type) { - case OutputSocketFieldType::FieldSource: - case OutputSocketFieldType::None: { - break; - } - case OutputSocketFieldType::DependentField: { - /* This output depends on all inputs. */ - input_sockets.extend(node.inputs()); - break; - } - case OutputSocketFieldType::PartiallyDependent: { - /* This output depends only on a few inputs. */ - for (const int i : field_dependency.linked_input_indices()) { - input_sockets.append(&node.input(i)); - } - break; - } - } - return input_sockets; -} - -/** - * Check what the group output socket depends on. Potentially traverses the node tree - * to figure out if it is always a field or if it depends on any group inputs. - */ -static OutputFieldDependency find_group_output_dependencies( - const InputSocketRef &group_output_socket, - const Span<SocketFieldState> field_state_by_socket_id) -{ - if (!is_field_socket_type(group_output_socket)) { - return OutputFieldDependency::ForDataSource(); - } - - /* Use a Set here instead of an array indexed by socket id, because we my only need to look at - * very few sockets. */ - Set<const InputSocketRef *> handled_sockets; - Stack<const InputSocketRef *> sockets_to_check; - - handled_sockets.add(&group_output_socket); - sockets_to_check.push(&group_output_socket); - - /* Keeps track of group input indices that are (indirectly) connected to the output. */ - Vector<int> linked_input_indices; - - while (!sockets_to_check.is_empty()) { - const InputSocketRef *input_socket = sockets_to_check.pop(); - - for (const OutputSocketRef *origin_socket : input_socket->directly_linked_sockets()) { - const NodeRef &origin_node = origin_socket->node(); - const SocketFieldState &origin_state = field_state_by_socket_id[origin_socket->id()]; - - if (origin_state.is_field_source) { - if (origin_node.is_group_input_node()) { - /* Found a group input that the group output depends on. */ - linked_input_indices.append_non_duplicates(origin_socket->index()); - } - else { - /* Found a field source that is not the group input. So the output is always a field. */ - return OutputFieldDependency::ForFieldSource(); - } - } - else if (!origin_state.is_single) { - const FieldInferencingInterface inferencing_interface = - get_node_field_inferencing_interface(origin_node); - const OutputFieldDependency &field_dependency = - inferencing_interface.outputs[origin_socket->index()]; - - /* Propagate search further to the left. */ - for (const InputSocketRef *origin_input_socket : - gather_input_socket_dependencies(field_dependency, origin_node)) { - if (!origin_input_socket->is_available()) { - continue; - } - if (!field_state_by_socket_id[origin_input_socket->id()].is_single) { - if (handled_sockets.add(origin_input_socket)) { - sockets_to_check.push(origin_input_socket); - } - } - } - } - } - } - return OutputFieldDependency::ForPartiallyDependentField(std::move(linked_input_indices)); -} - -static void propagate_data_requirements_from_right_to_left( - const NodeTreeRef &tree, const MutableSpan<SocketFieldState> field_state_by_socket_id) -{ - const NodeTreeRef::ToposortResult toposort_result = tree.toposort( - NodeTreeRef::ToposortDirection::RightToLeft); - - for (const NodeRef *node : toposort_result.sorted_nodes) { - const FieldInferencingInterface inferencing_interface = get_node_field_inferencing_interface( - *node); - - for (const OutputSocketRef *output_socket : node->outputs()) { - SocketFieldState &state = field_state_by_socket_id[output_socket->id()]; - - const OutputFieldDependency &field_dependency = - inferencing_interface.outputs[output_socket->index()]; - - if (field_dependency.field_type() == OutputSocketFieldType::FieldSource) { - continue; - } - if (field_dependency.field_type() == OutputSocketFieldType::None) { - state.requires_single = true; - state.is_always_single = true; - continue; - } - - /* The output is required to be a single value when it is connected to any input that does - * not support fields. */ - for (const InputSocketRef *target_socket : output_socket->directly_linked_sockets()) { - if (target_socket->is_available()) { - state.requires_single |= field_state_by_socket_id[target_socket->id()].requires_single; - } - } - - if (state.requires_single) { - bool any_input_is_field_implicitly = false; - const Vector<const InputSocketRef *> connected_inputs = gather_input_socket_dependencies( - field_dependency, *node); - for (const InputSocketRef *input_socket : connected_inputs) { - if (!input_socket->is_available()) { - continue; - } - if (inferencing_interface.inputs[input_socket->index()] == - InputSocketFieldType::Implicit) { - if (!input_socket->is_logically_linked()) { - any_input_is_field_implicitly = true; - break; - } - } - } - if (any_input_is_field_implicitly) { - /* This output isn't a single value actually. */ - state.requires_single = false; - } - else { - /* If the output is required to be a single value, the connected inputs in the same node - * must not be fields as well. */ - for (const InputSocketRef *input_socket : connected_inputs) { - field_state_by_socket_id[input_socket->id()].requires_single = true; - } - } - } - } - - /* Some inputs do not require fields independent of what the outputs are connected to. */ - for (const InputSocketRef *input_socket : node->inputs()) { - SocketFieldState &state = field_state_by_socket_id[input_socket->id()]; - if (inferencing_interface.inputs[input_socket->index()] == InputSocketFieldType::None) { - state.requires_single = true; - state.is_always_single = true; - } - } - } -} - -static void determine_group_input_states( - const NodeTreeRef &tree, - FieldInferencingInterface &new_inferencing_interface, - const MutableSpan<SocketFieldState> field_state_by_socket_id) -{ - { - /* Non-field inputs never support fields. */ - int index; - LISTBASE_FOREACH_INDEX (bNodeSocket *, group_input, &tree.btree()->inputs, index) { - if (!is_field_socket_type((eNodeSocketDatatype)group_input->type)) { - new_inferencing_interface.inputs[index] = InputSocketFieldType::None; - } - } - } - /* Check if group inputs are required to be single values, because they are (indirectly) - * connected to some socket that does not support fields. */ - for (const NodeRef *node : tree.nodes_by_type("NodeGroupInput")) { - for (const OutputSocketRef *output_socket : node->outputs().drop_back(1)) { - SocketFieldState &state = field_state_by_socket_id[output_socket->id()]; - if (state.requires_single) { - new_inferencing_interface.inputs[output_socket->index()] = InputSocketFieldType::None; - } - } - } - /* If an input does not support fields, this should be reflected in all Group Input nodes. */ - for (const NodeRef *node : tree.nodes_by_type("NodeGroupInput")) { - for (const OutputSocketRef *output_socket : node->outputs().drop_back(1)) { - SocketFieldState &state = field_state_by_socket_id[output_socket->id()]; - const bool supports_field = new_inferencing_interface.inputs[output_socket->index()] != - InputSocketFieldType::None; - if (supports_field) { - state.is_single = false; - state.is_field_source = true; - } - else { - state.requires_single = true; - } - } - SocketFieldState &dummy_socket_state = field_state_by_socket_id[node->outputs().last()->id()]; - dummy_socket_state.requires_single = true; - } -} - -static void propagate_field_status_from_left_to_right( - const NodeTreeRef &tree, const MutableSpan<SocketFieldState> field_state_by_socket_id) -{ - const NodeTreeRef::ToposortResult toposort_result = tree.toposort( - NodeTreeRef::ToposortDirection::LeftToRight); - - for (const NodeRef *node : toposort_result.sorted_nodes) { - if (node->is_group_input_node()) { - continue; - } - - const FieldInferencingInterface inferencing_interface = get_node_field_inferencing_interface( - *node); - - /* Update field state of input sockets, also taking into account linked origin sockets. */ - for (const InputSocketRef *input_socket : node->inputs()) { - SocketFieldState &state = field_state_by_socket_id[input_socket->id()]; - if (state.is_always_single) { - state.is_single = true; - continue; - } - state.is_single = true; - if (input_socket->directly_linked_sockets().is_empty()) { - if (inferencing_interface.inputs[input_socket->index()] == - InputSocketFieldType::Implicit) { - state.is_single = false; - } - } - else { - for (const OutputSocketRef *origin_socket : input_socket->directly_linked_sockets()) { - if (!field_state_by_socket_id[origin_socket->id()].is_single) { - state.is_single = false; - break; - } - } - } - } - - /* Update field state of output sockets, also taking into account input sockets. */ - for (const OutputSocketRef *output_socket : node->outputs()) { - SocketFieldState &state = field_state_by_socket_id[output_socket->id()]; - const OutputFieldDependency &field_dependency = - inferencing_interface.outputs[output_socket->index()]; - - switch (field_dependency.field_type()) { - case OutputSocketFieldType::None: { - state.is_single = true; - break; - } - case OutputSocketFieldType::FieldSource: { - state.is_single = false; - state.is_field_source = true; - break; - } - case OutputSocketFieldType::PartiallyDependent: - case OutputSocketFieldType::DependentField: { - for (const InputSocketRef *input_socket : - gather_input_socket_dependencies(field_dependency, *node)) { - if (!input_socket->is_available()) { - continue; - } - if (!field_state_by_socket_id[input_socket->id()].is_single) { - state.is_single = false; - break; - } - } - break; - } - } - } - } -} - -static void determine_group_output_states(const NodeTreeRef &tree, - FieldInferencingInterface &new_inferencing_interface, - const Span<SocketFieldState> field_state_by_socket_id) -{ - for (const NodeRef *group_output_node : tree.nodes_by_type("NodeGroupOutput")) { - /* Ignore inactive group output nodes. */ - if (!(group_output_node->bnode()->flag & NODE_DO_OUTPUT)) { - continue; - } - /* Determine dependencies of all group outputs. */ - for (const InputSocketRef *group_output_socket : group_output_node->inputs().drop_back(1)) { - OutputFieldDependency field_dependency = find_group_output_dependencies( - *group_output_socket, field_state_by_socket_id); - new_inferencing_interface.outputs[group_output_socket->index()] = std::move( - field_dependency); - } - break; - } -} - -static void update_socket_shapes(const NodeTreeRef &tree, - const Span<SocketFieldState> field_state_by_socket_id) -{ - const eNodeSocketDisplayShape requires_data_shape = SOCK_DISPLAY_SHAPE_CIRCLE; - const eNodeSocketDisplayShape data_but_can_be_field_shape = SOCK_DISPLAY_SHAPE_DIAMOND_DOT; - const eNodeSocketDisplayShape is_field_shape = SOCK_DISPLAY_SHAPE_DIAMOND; - - auto get_shape_for_state = [&](const SocketFieldState &state) { - if (state.is_always_single) { - return requires_data_shape; - } - if (!state.is_single) { - return is_field_shape; - } - if (state.requires_single) { - return requires_data_shape; - } - return data_but_can_be_field_shape; - }; - - for (const InputSocketRef *socket : tree.input_sockets()) { - bNodeSocket *bsocket = socket->bsocket(); - const SocketFieldState &state = field_state_by_socket_id[socket->id()]; - bsocket->display_shape = get_shape_for_state(state); - } - for (const OutputSocketRef *socket : tree.output_sockets()) { - bNodeSocket *bsocket = socket->bsocket(); - const SocketFieldState &state = field_state_by_socket_id[socket->id()]; - bsocket->display_shape = get_shape_for_state(state); - } -} - -static bool update_field_inferencing(bNodeTree &btree) -{ - using namespace blender::nodes; - if (btree.type != NTREE_GEOMETRY) { - return false; - } - - /* Create new inferencing interface for this node group. */ - FieldInferencingInterface *new_inferencing_interface = new FieldInferencingInterface(); - new_inferencing_interface->inputs.resize(BLI_listbase_count(&btree.inputs), - InputSocketFieldType::IsSupported); - new_inferencing_interface->outputs.resize(BLI_listbase_count(&btree.outputs), - OutputFieldDependency::ForDataSource()); - - /* Create #NodeTreeRef to accelerate various queries on the node tree (e.g. linked sockets). */ - const NodeTreeRef tree{&btree}; - - /* Keep track of the state of all sockets. The index into this array is #SocketRef::id(). */ - Array<SocketFieldState> field_state_by_socket_id(tree.sockets().size()); - - propagate_data_requirements_from_right_to_left(tree, field_state_by_socket_id); - determine_group_input_states(tree, *new_inferencing_interface, field_state_by_socket_id); - propagate_field_status_from_left_to_right(tree, field_state_by_socket_id); - determine_group_output_states(tree, *new_inferencing_interface, field_state_by_socket_id); - update_socket_shapes(tree, field_state_by_socket_id); - - /* Update the previous group interface. */ - const bool group_interface_changed = btree.field_inferencing_interface == nullptr || - *btree.field_inferencing_interface != - *new_inferencing_interface; - delete btree.field_inferencing_interface; - btree.field_inferencing_interface = new_inferencing_interface; - - return group_interface_changed; -} - -} // namespace blender::bke::node_field_inferencing - -void ntreeUpdateAllUsers(Main *main, ID *id, const int tree_update_flag) +void ntreeUpdateAllUsers(Main *main, ID *id) { if (id == nullptr) { return; } + bool need_update = false; + /* Update all users of ngroup, to add/remove sockets as needed. */ FOREACH_NODETREE_BEGIN (main, ntree, owner_id) { - bool need_update = false; - LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { if (node->id == id) { - if (node->typeinfo->group_update_func) { - node->typeinfo->group_update_func(ntree, node); - } - + BKE_ntree_update_tag_node_property(ntree, node); need_update = true; } } - - if (need_update) { - ntree->update |= tree_update_flag; - ntreeUpdateTree(tree_update_flag ? main : nullptr, ntree); - } } FOREACH_NODETREE_END; - - if (GS(id->name) == ID_NT) { - bNodeTree *ngroup = (bNodeTree *)id; - if (ngroup->type == NTREE_GEOMETRY && (ngroup->update & NTREE_UPDATE_GROUP)) { - LISTBASE_FOREACH (Object *, object, &main->objects) { - LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) { - if (md->type == eModifierType_Nodes) { - NodesModifierData *nmd = (NodesModifierData *)md; - if (nmd->node_group == ngroup) { - MOD_nodes_update_interface(object, nmd); - } - } - } - } - } - } -} - -void ntreeUpdateTree(Main *bmain, bNodeTree *ntree) -{ - if (!ntree) { - return; - } - - /* Avoid re-entrant updates, can be caused by RNA update callbacks. */ - if (ntree->is_updating) { - return; - } - ntree->is_updating = true; - - if (ntree->update & (NTREE_UPDATE_LINKS | NTREE_UPDATE_NODES)) { - /* set the bNodeSocket->link pointers */ - ntree_update_link_pointers(ntree); - } - - /* update individual nodes */ - LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - /* node tree update tags override individual node update flags */ - if ((node->update & NODE_UPDATE) || (ntree->update & NTREE_UPDATE)) { - if (node->typeinfo->updatefunc) { - node->typeinfo->updatefunc(ntree, node); - } - - nodeUpdateInternalLinks(ntree, node); - } - } - - /* generic tree update callback */ - if (ntree->typeinfo->update) { - ntree->typeinfo->update(ntree); - } - /* XXX this should be moved into the tree type update callback for tree supporting node groups. - * Currently the node tree interface is still a generic feature of the base NodeTree type. - */ - if (ntree->update & NTREE_UPDATE_GROUP) { - ntreeInterfaceTypeUpdate(ntree); - } - - int tree_user_update_flag = 0; - - if (ntree->update & NTREE_UPDATE) { - /* If the field interface of this node tree has changed, all node trees using - * this group will need to recalculate their interface as well. */ - if (blender::bke::node_field_inferencing::update_field_inferencing(*ntree)) { - tree_user_update_flag |= NTREE_UPDATE_FIELD_INFERENCING; - } - } - - if (bmain) { - ntreeUpdateAllUsers(bmain, &ntree->id, tree_user_update_flag); - } - - if (ntree->update & (NTREE_UPDATE_LINKS | NTREE_UPDATE_NODES)) { - /* node updates can change sockets or links, repeat link pointer update afterward */ - ntree_update_link_pointers(ntree); - - /* update the node level from link dependencies */ - ntree_update_node_level(ntree); - - /* check link validity */ - ntree_validate_links(ntree); - } - - /* clear update flags */ - LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - node->update = 0; - } - ntree->update = 0; - - ntree->is_updating = false; -} - -void nodeUpdate(bNodeTree *ntree, bNode *node) -{ - /* Avoid re-entrant updates, can be caused by RNA update callbacks. */ - if (ntree->is_updating) { - return; - } - ntree->is_updating = true; - - if (node->typeinfo->updatefunc) { - node->typeinfo->updatefunc(ntree, node); - } - - nodeUpdateInternalLinks(ntree, node); - - /* clear update flag */ - node->update = 0; - - ntree->is_updating = false; -} - -bool nodeUpdateID(bNodeTree *ntree, ID *id) -{ - bool changed = false; - - if (ELEM(nullptr, id, ntree)) { - return changed; - } - - /* Avoid re-entrant updates, can be caused by RNA update callbacks. */ - if (ntree->is_updating) { - return changed; - } - ntree->is_updating = true; - - LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - if (node->id == id) { - changed = true; - node->update |= NODE_UPDATE_ID; - if (node->typeinfo->updatefunc) { - node->typeinfo->updatefunc(ntree, node); - } - /* clear update flag */ - node->update = 0; - } - } - - LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - nodeUpdateInternalLinks(ntree, node); - } - - ntree->is_updating = false; - return changed; -} - -void nodeUpdateInternalLinks(bNodeTree *ntree, bNode *node) -{ - BLI_freelistN(&node->internal_links); - if (!node->typeinfo->no_muting) { - node_internal_links_create(ntree, node); + if (need_update) { + BKE_ntree_update_main(main, nullptr); } } diff --git a/source/blender/blenkernel/intern/node_tree_update.cc b/source/blender/blenkernel/intern/node_tree_update.cc new file mode 100644 index 00000000000..427fac747dc --- /dev/null +++ b/source/blender/blenkernel/intern/node_tree_update.cc @@ -0,0 +1,1658 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BLI_map.hh" +#include "BLI_multi_value_map.hh" +#include "BLI_noise.hh" +#include "BLI_set.hh" +#include "BLI_stack.hh" +#include "BLI_vector_set.hh" + +#include "DNA_anim_types.h" +#include "DNA_modifier_types.h" +#include "DNA_node_types.h" + +#include "BKE_anim_data.h" +#include "BKE_main.h" +#include "BKE_node.h" +#include "BKE_node_tree_update.h" + +#include "MOD_nodes.h" + +#include "NOD_node_declaration.hh" +#include "NOD_node_tree_ref.hh" + +#include "DEG_depsgraph_query.h" + +using namespace blender::nodes; + +/** + * These flags are used by the `changed_flag` field in #bNodeTree, #bNode and #bNodeSocket. + * This enum is not part of the public api. It should be used through the `BKE_ntree_update_tag_*` + * api. + */ +enum eNodeTreeChangedFlag { + NTREE_CHANGED_NOTHING = 0, + NTREE_CHANGED_ANY = (1 << 1), + NTREE_CHANGED_NODE_PROPERTY = (1 << 2), + NTREE_CHANGED_NODE_OUTPUT = (1 << 3), + NTREE_CHANGED_INTERFACE = (1 << 4), + NTREE_CHANGED_LINK = (1 << 5), + NTREE_CHANGED_REMOVED_NODE = (1 << 6), + NTREE_CHANGED_REMOVED_SOCKET = (1 << 7), + NTREE_CHANGED_SOCKET_PROPERTY = (1 << 8), + NTREE_CHANGED_INTERNAL_LINK = (1 << 9), + NTREE_CHANGED_ALL = -1, +}; + +static void add_tree_tag(bNodeTree *ntree, const eNodeTreeChangedFlag flag) +{ + ntree->changed_flag |= flag; +} + +static void add_node_tag(bNodeTree *ntree, bNode *node, const eNodeTreeChangedFlag flag) +{ + add_tree_tag(ntree, flag); + node->changed_flag |= flag; +} + +static void add_socket_tag(bNodeTree *ntree, bNodeSocket *socket, const eNodeTreeChangedFlag flag) +{ + add_tree_tag(ntree, flag); + socket->changed_flag |= flag; +} + +namespace blender::bke { + +namespace node_field_inferencing { + +static bool is_field_socket_type(eNodeSocketDatatype type) +{ + return ELEM(type, SOCK_FLOAT, SOCK_INT, SOCK_BOOLEAN, SOCK_VECTOR, SOCK_RGBA); +} + +static bool is_field_socket_type(const SocketRef &socket) +{ + return is_field_socket_type((eNodeSocketDatatype)socket.typeinfo()->type); +} + +static InputSocketFieldType get_interface_input_field_type(const NodeRef &node, + const InputSocketRef &socket) +{ + if (!is_field_socket_type(socket)) { + return InputSocketFieldType::None; + } + if (node.is_reroute_node()) { + return InputSocketFieldType::IsSupported; + } + if (node.is_group_output_node()) { + /* Outputs always support fields when the data type is correct. */ + return InputSocketFieldType::IsSupported; + } + if (node.is_undefined()) { + return InputSocketFieldType::None; + } + + const NodeDeclaration *node_decl = node.declaration(); + + /* Node declarations should be implemented for nodes involved here. */ + BLI_assert(node_decl != nullptr); + + /* Get the field type from the declaration. */ + const SocketDeclaration &socket_decl = *node_decl->inputs()[socket.index()]; + const InputSocketFieldType field_type = socket_decl.input_field_type(); + if (field_type == InputSocketFieldType::Implicit) { + return field_type; + } + if (node_decl->is_function_node()) { + /* In a function node, every socket supports fields. */ + return InputSocketFieldType::IsSupported; + } + return field_type; +} + +static OutputFieldDependency get_interface_output_field_dependency(const NodeRef &node, + const OutputSocketRef &socket) +{ + if (!is_field_socket_type(socket)) { + /* Non-field sockets always output data. */ + return OutputFieldDependency::ForDataSource(); + } + if (node.is_reroute_node()) { + /* The reroute just forwards what is passed in. */ + return OutputFieldDependency::ForDependentField(); + } + if (node.is_group_input_node()) { + /* Input nodes get special treatment in #determine_group_input_states. */ + return OutputFieldDependency::ForDependentField(); + } + if (node.is_undefined()) { + return OutputFieldDependency::ForDataSource(); + } + + const NodeDeclaration *node_decl = node.declaration(); + + /* Node declarations should be implemented for nodes involved here. */ + BLI_assert(node_decl != nullptr); + + if (node_decl->is_function_node()) { + /* In a generic function node, all outputs depend on all inputs. */ + return OutputFieldDependency::ForDependentField(); + } + + /* Use the socket declaration. */ + const SocketDeclaration &socket_decl = *node_decl->outputs()[socket.index()]; + return socket_decl.output_field_dependency(); +} + +static FieldInferencingInterface get_dummy_field_inferencing_interface(const NodeRef &node) +{ + FieldInferencingInterface inferencing_interface; + inferencing_interface.inputs.append_n_times(InputSocketFieldType::None, node.inputs().size()); + inferencing_interface.outputs.append_n_times(OutputFieldDependency::ForDataSource(), + node.outputs().size()); + return inferencing_interface; +} + +/** + * Retrieves information about how the node interacts with fields. + * In the future, this information can be stored in the node declaration. This would allow this + * function to return a reference, making it more efficient. + */ +static FieldInferencingInterface get_node_field_inferencing_interface(const NodeRef &node) +{ + /* Node groups already reference all required information, so just return that. */ + if (node.is_group_node()) { + bNodeTree *group = (bNodeTree *)node.bnode()->id; + if (group == nullptr) { + return FieldInferencingInterface(); + } + if (!ntreeIsRegistered(group)) { + /* This can happen when there is a linked node group that was not found (see T92799). */ + return get_dummy_field_inferencing_interface(node); + } + if (group->field_inferencing_interface == nullptr) { + /* This shouldn't happen because referenced node groups should always be updated first. */ + BLI_assert_unreachable(); + } + return *group->field_inferencing_interface; + } + + FieldInferencingInterface inferencing_interface; + for (const InputSocketRef *input_socket : node.inputs()) { + inferencing_interface.inputs.append(get_interface_input_field_type(node, *input_socket)); + } + + for (const OutputSocketRef *output_socket : node.outputs()) { + inferencing_interface.outputs.append( + get_interface_output_field_dependency(node, *output_socket)); + } + return inferencing_interface; +} + +/** + * This struct contains information for every socket. The values are propagated through the + * network. + */ +struct SocketFieldState { + /* This socket starts a new field. */ + bool is_field_source = false; + /* This socket can never become a field, because the node itself does not support it. */ + bool is_always_single = false; + /* This socket is currently a single value. It could become a field though. */ + bool is_single = true; + /* This socket is required to be a single value. This can be because the node itself only + * supports this socket to be a single value, or because a node afterwards requires this to be a + * single value. */ + bool requires_single = false; +}; + +static Vector<const InputSocketRef *> gather_input_socket_dependencies( + const OutputFieldDependency &field_dependency, const NodeRef &node) +{ + const OutputSocketFieldType type = field_dependency.field_type(); + Vector<const InputSocketRef *> input_sockets; + switch (type) { + case OutputSocketFieldType::FieldSource: + case OutputSocketFieldType::None: { + break; + } + case OutputSocketFieldType::DependentField: { + /* This output depends on all inputs. */ + input_sockets.extend(node.inputs()); + break; + } + case OutputSocketFieldType::PartiallyDependent: { + /* This output depends only on a few inputs. */ + for (const int i : field_dependency.linked_input_indices()) { + input_sockets.append(&node.input(i)); + } + break; + } + } + return input_sockets; +} + +/** + * Check what the group output socket depends on. Potentially traverses the node tree + * to figure out if it is always a field or if it depends on any group inputs. + */ +static OutputFieldDependency find_group_output_dependencies( + const InputSocketRef &group_output_socket, + const Span<SocketFieldState> field_state_by_socket_id) +{ + if (!is_field_socket_type(group_output_socket)) { + return OutputFieldDependency::ForDataSource(); + } + + /* Use a Set here instead of an array indexed by socket id, because we my only need to look at + * very few sockets. */ + Set<const InputSocketRef *> handled_sockets; + Stack<const InputSocketRef *> sockets_to_check; + + handled_sockets.add(&group_output_socket); + sockets_to_check.push(&group_output_socket); + + /* Keeps track of group input indices that are (indirectly) connected to the output. */ + Vector<int> linked_input_indices; + + while (!sockets_to_check.is_empty()) { + const InputSocketRef *input_socket = sockets_to_check.pop(); + + for (const OutputSocketRef *origin_socket : input_socket->directly_linked_sockets()) { + const NodeRef &origin_node = origin_socket->node(); + const SocketFieldState &origin_state = field_state_by_socket_id[origin_socket->id()]; + + if (origin_state.is_field_source) { + if (origin_node.is_group_input_node()) { + /* Found a group input that the group output depends on. */ + linked_input_indices.append_non_duplicates(origin_socket->index()); + } + else { + /* Found a field source that is not the group input. So the output is always a field. */ + return OutputFieldDependency::ForFieldSource(); + } + } + else if (!origin_state.is_single) { + const FieldInferencingInterface inferencing_interface = + get_node_field_inferencing_interface(origin_node); + const OutputFieldDependency &field_dependency = + inferencing_interface.outputs[origin_socket->index()]; + + /* Propagate search further to the left. */ + for (const InputSocketRef *origin_input_socket : + gather_input_socket_dependencies(field_dependency, origin_node)) { + if (!origin_input_socket->is_available()) { + continue; + } + if (!field_state_by_socket_id[origin_input_socket->id()].is_single) { + if (handled_sockets.add(origin_input_socket)) { + sockets_to_check.push(origin_input_socket); + } + } + } + } + } + } + return OutputFieldDependency::ForPartiallyDependentField(std::move(linked_input_indices)); +} + +static void propagate_data_requirements_from_right_to_left( + const NodeTreeRef &tree, const MutableSpan<SocketFieldState> field_state_by_socket_id) +{ + const NodeTreeRef::ToposortResult toposort_result = tree.toposort( + NodeTreeRef::ToposortDirection::RightToLeft); + + for (const NodeRef *node : toposort_result.sorted_nodes) { + const FieldInferencingInterface inferencing_interface = get_node_field_inferencing_interface( + *node); + + for (const OutputSocketRef *output_socket : node->outputs()) { + SocketFieldState &state = field_state_by_socket_id[output_socket->id()]; + + const OutputFieldDependency &field_dependency = + inferencing_interface.outputs[output_socket->index()]; + + if (field_dependency.field_type() == OutputSocketFieldType::FieldSource) { + continue; + } + if (field_dependency.field_type() == OutputSocketFieldType::None) { + state.requires_single = true; + state.is_always_single = true; + continue; + } + + /* The output is required to be a single value when it is connected to any input that does + * not support fields. */ + for (const InputSocketRef *target_socket : output_socket->directly_linked_sockets()) { + if (target_socket->is_available()) { + state.requires_single |= field_state_by_socket_id[target_socket->id()].requires_single; + } + } + + if (state.requires_single) { + bool any_input_is_field_implicitly = false; + const Vector<const InputSocketRef *> connected_inputs = gather_input_socket_dependencies( + field_dependency, *node); + for (const InputSocketRef *input_socket : connected_inputs) { + if (!input_socket->is_available()) { + continue; + } + if (inferencing_interface.inputs[input_socket->index()] == + InputSocketFieldType::Implicit) { + if (!input_socket->is_logically_linked()) { + any_input_is_field_implicitly = true; + break; + } + } + } + if (any_input_is_field_implicitly) { + /* This output isn't a single value actually. */ + state.requires_single = false; + } + else { + /* If the output is required to be a single value, the connected inputs in the same node + * must not be fields as well. */ + for (const InputSocketRef *input_socket : connected_inputs) { + field_state_by_socket_id[input_socket->id()].requires_single = true; + } + } + } + } + + /* Some inputs do not require fields independent of what the outputs are connected to. */ + for (const InputSocketRef *input_socket : node->inputs()) { + SocketFieldState &state = field_state_by_socket_id[input_socket->id()]; + if (inferencing_interface.inputs[input_socket->index()] == InputSocketFieldType::None) { + state.requires_single = true; + state.is_always_single = true; + } + } + } +} + +static void determine_group_input_states( + const NodeTreeRef &tree, + FieldInferencingInterface &new_inferencing_interface, + const MutableSpan<SocketFieldState> field_state_by_socket_id) +{ + { + /* Non-field inputs never support fields. */ + int index; + LISTBASE_FOREACH_INDEX (bNodeSocket *, group_input, &tree.btree()->inputs, index) { + if (!is_field_socket_type((eNodeSocketDatatype)group_input->type)) { + new_inferencing_interface.inputs[index] = InputSocketFieldType::None; + } + } + } + /* Check if group inputs are required to be single values, because they are (indirectly) + * connected to some socket that does not support fields. */ + for (const NodeRef *node : tree.nodes_by_type("NodeGroupInput")) { + for (const OutputSocketRef *output_socket : node->outputs().drop_back(1)) { + SocketFieldState &state = field_state_by_socket_id[output_socket->id()]; + if (state.requires_single) { + new_inferencing_interface.inputs[output_socket->index()] = InputSocketFieldType::None; + } + } + } + /* If an input does not support fields, this should be reflected in all Group Input nodes. */ + for (const NodeRef *node : tree.nodes_by_type("NodeGroupInput")) { + for (const OutputSocketRef *output_socket : node->outputs().drop_back(1)) { + SocketFieldState &state = field_state_by_socket_id[output_socket->id()]; + const bool supports_field = new_inferencing_interface.inputs[output_socket->index()] != + InputSocketFieldType::None; + if (supports_field) { + state.is_single = false; + state.is_field_source = true; + } + else { + state.requires_single = true; + } + } + SocketFieldState &dummy_socket_state = field_state_by_socket_id[node->outputs().last()->id()]; + dummy_socket_state.requires_single = true; + } +} + +static void propagate_field_status_from_left_to_right( + const NodeTreeRef &tree, const MutableSpan<SocketFieldState> field_state_by_socket_id) +{ + const NodeTreeRef::ToposortResult toposort_result = tree.toposort( + NodeTreeRef::ToposortDirection::LeftToRight); + + for (const NodeRef *node : toposort_result.sorted_nodes) { + if (node->is_group_input_node()) { + continue; + } + + const FieldInferencingInterface inferencing_interface = get_node_field_inferencing_interface( + *node); + + /* Update field state of input sockets, also taking into account linked origin sockets. */ + for (const InputSocketRef *input_socket : node->inputs()) { + SocketFieldState &state = field_state_by_socket_id[input_socket->id()]; + if (state.is_always_single) { + state.is_single = true; + continue; + } + state.is_single = true; + if (input_socket->directly_linked_sockets().is_empty()) { + if (inferencing_interface.inputs[input_socket->index()] == + InputSocketFieldType::Implicit) { + state.is_single = false; + } + } + else { + for (const OutputSocketRef *origin_socket : input_socket->directly_linked_sockets()) { + if (!field_state_by_socket_id[origin_socket->id()].is_single) { + state.is_single = false; + break; + } + } + } + } + + /* Update field state of output sockets, also taking into account input sockets. */ + for (const OutputSocketRef *output_socket : node->outputs()) { + SocketFieldState &state = field_state_by_socket_id[output_socket->id()]; + const OutputFieldDependency &field_dependency = + inferencing_interface.outputs[output_socket->index()]; + + switch (field_dependency.field_type()) { + case OutputSocketFieldType::None: { + state.is_single = true; + break; + } + case OutputSocketFieldType::FieldSource: { + state.is_single = false; + state.is_field_source = true; + break; + } + case OutputSocketFieldType::PartiallyDependent: + case OutputSocketFieldType::DependentField: { + for (const InputSocketRef *input_socket : + gather_input_socket_dependencies(field_dependency, *node)) { + if (!input_socket->is_available()) { + continue; + } + if (!field_state_by_socket_id[input_socket->id()].is_single) { + state.is_single = false; + break; + } + } + break; + } + } + } + } +} + +static void determine_group_output_states(const NodeTreeRef &tree, + FieldInferencingInterface &new_inferencing_interface, + const Span<SocketFieldState> field_state_by_socket_id) +{ + for (const NodeRef *group_output_node : tree.nodes_by_type("NodeGroupOutput")) { + /* Ignore inactive group output nodes. */ + if (!(group_output_node->bnode()->flag & NODE_DO_OUTPUT)) { + continue; + } + /* Determine dependencies of all group outputs. */ + for (const InputSocketRef *group_output_socket : group_output_node->inputs().drop_back(1)) { + OutputFieldDependency field_dependency = find_group_output_dependencies( + *group_output_socket, field_state_by_socket_id); + new_inferencing_interface.outputs[group_output_socket->index()] = std::move( + field_dependency); + } + break; + } +} + +static void update_socket_shapes(const NodeTreeRef &tree, + const Span<SocketFieldState> field_state_by_socket_id) +{ + const eNodeSocketDisplayShape requires_data_shape = SOCK_DISPLAY_SHAPE_CIRCLE; + const eNodeSocketDisplayShape data_but_can_be_field_shape = SOCK_DISPLAY_SHAPE_DIAMOND_DOT; + const eNodeSocketDisplayShape is_field_shape = SOCK_DISPLAY_SHAPE_DIAMOND; + + auto get_shape_for_state = [&](const SocketFieldState &state) { + if (state.is_always_single) { + return requires_data_shape; + } + if (!state.is_single) { + return is_field_shape; + } + if (state.requires_single) { + return requires_data_shape; + } + return data_but_can_be_field_shape; + }; + + for (const InputSocketRef *socket : tree.input_sockets()) { + bNodeSocket *bsocket = socket->bsocket(); + const SocketFieldState &state = field_state_by_socket_id[socket->id()]; + bsocket->display_shape = get_shape_for_state(state); + } + for (const OutputSocketRef *socket : tree.output_sockets()) { + bNodeSocket *bsocket = socket->bsocket(); + const SocketFieldState &state = field_state_by_socket_id[socket->id()]; + bsocket->display_shape = get_shape_for_state(state); + } +} + +static bool update_field_inferencing(const NodeTreeRef &tree) +{ + bNodeTree &btree = *tree.btree(); + + /* Create new inferencing interface for this node group. */ + FieldInferencingInterface *new_inferencing_interface = new FieldInferencingInterface(); + new_inferencing_interface->inputs.resize(BLI_listbase_count(&btree.inputs), + InputSocketFieldType::IsSupported); + new_inferencing_interface->outputs.resize(BLI_listbase_count(&btree.outputs), + OutputFieldDependency::ForDataSource()); + + /* Keep track of the state of all sockets. The index into this array is #SocketRef::id(). */ + Array<SocketFieldState> field_state_by_socket_id(tree.sockets().size()); + + propagate_data_requirements_from_right_to_left(tree, field_state_by_socket_id); + determine_group_input_states(tree, *new_inferencing_interface, field_state_by_socket_id); + propagate_field_status_from_left_to_right(tree, field_state_by_socket_id); + determine_group_output_states(tree, *new_inferencing_interface, field_state_by_socket_id); + update_socket_shapes(tree, field_state_by_socket_id); + + /* Update the previous group interface. */ + const bool group_interface_changed = btree.field_inferencing_interface == nullptr || + *btree.field_inferencing_interface != + *new_inferencing_interface; + delete btree.field_inferencing_interface; + btree.field_inferencing_interface = new_inferencing_interface; + + return group_interface_changed; +} + +} // namespace node_field_inferencing + +/** + * Common datatype priorities, works for compositor, shader and texture nodes alike + * defines priority of datatype connection based on output type (to): + * `< 0`: never connect these types. + * `>= 0`: priority of connection (higher values chosen first). + */ +static int get_internal_link_type_priority(const bNodeSocketType *from, const bNodeSocketType *to) +{ + switch (to->type) { + case SOCK_RGBA: + switch (from->type) { + case SOCK_RGBA: + return 4; + case SOCK_FLOAT: + return 3; + case SOCK_INT: + return 2; + case SOCK_BOOLEAN: + return 1; + } + return -1; + case SOCK_VECTOR: + switch (from->type) { + case SOCK_VECTOR: + return 4; + case SOCK_FLOAT: + return 3; + case SOCK_INT: + return 2; + case SOCK_BOOLEAN: + return 1; + } + return -1; + case SOCK_FLOAT: + switch (from->type) { + case SOCK_FLOAT: + return 5; + case SOCK_INT: + return 4; + case SOCK_BOOLEAN: + return 3; + case SOCK_RGBA: + return 2; + case SOCK_VECTOR: + return 1; + } + return -1; + case SOCK_INT: + switch (from->type) { + case SOCK_INT: + return 5; + case SOCK_FLOAT: + return 4; + case SOCK_BOOLEAN: + return 3; + case SOCK_RGBA: + return 2; + case SOCK_VECTOR: + return 1; + } + return -1; + case SOCK_BOOLEAN: + switch (from->type) { + case SOCK_BOOLEAN: + return 5; + case SOCK_INT: + return 4; + case SOCK_FLOAT: + return 3; + case SOCK_RGBA: + return 2; + case SOCK_VECTOR: + return 1; + } + return -1; + } + + /* The rest of the socket types only allow an internal link if both the input and output socket + * have the same type. If the sockets are custom, we check the idname instead. */ + if (to->type == from->type && (to->type != SOCK_CUSTOM || STREQ(to->idname, from->idname))) { + return 1; + } + + return -1; +} + +using TreeNodePair = std::pair<bNodeTree *, bNode *>; +using ObjectModifierPair = std::pair<Object *, ModifierData *>; +using NodeSocketPair = std::pair<bNode *, bNodeSocket *>; + +/** + * Cache common data about node trees from the #Main database that is expensive to retrieve on + * demand every time. + */ +struct NodeTreeRelations { + private: + Main *bmain_; + std::optional<Vector<bNodeTree *>> all_trees_; + std::optional<Map<bNodeTree *, ID *>> owner_ids_; + std::optional<MultiValueMap<bNodeTree *, TreeNodePair>> group_node_users_; + std::optional<MultiValueMap<bNodeTree *, ObjectModifierPair>> modifiers_users_; + + public: + NodeTreeRelations(Main *bmain) : bmain_(bmain) + { + } + + void ensure_all_trees() + { + if (all_trees_.has_value()) { + return; + } + all_trees_.emplace(); + owner_ids_.emplace(); + if (bmain_ == nullptr) { + return; + } + + FOREACH_NODETREE_BEGIN (bmain_, ntree, id) { + all_trees_->append(ntree); + if (&ntree->id != id) { + owner_ids_->add_new(ntree, id); + } + } + FOREACH_NODETREE_END; + } + + void ensure_owner_ids() + { + this->ensure_all_trees(); + } + + void ensure_group_node_users() + { + if (group_node_users_.has_value()) { + return; + } + group_node_users_.emplace(); + if (bmain_ == nullptr) { + return; + } + + this->ensure_all_trees(); + + for (bNodeTree *ntree : *all_trees_) { + LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { + if (node->id == nullptr) { + continue; + } + ID *id = node->id; + if (GS(id->name) == ID_NT) { + bNodeTree *group = (bNodeTree *)id; + group_node_users_->add(group, {ntree, node}); + } + } + } + } + + void ensure_modifier_users() + { + if (modifiers_users_.has_value()) { + return; + } + modifiers_users_.emplace(); + if (bmain_ == nullptr) { + return; + } + + LISTBASE_FOREACH (Object *, object, &bmain_->objects) { + LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) { + if (md->type == eModifierType_Nodes) { + NodesModifierData *nmd = (NodesModifierData *)md; + if (nmd->node_group != nullptr) { + modifiers_users_->add(nmd->node_group, {object, md}); + } + } + } + } + } + + Span<ObjectModifierPair> get_modifier_users(bNodeTree *ntree) + { + BLI_assert(modifiers_users_.has_value()); + return modifiers_users_->lookup(ntree); + } + + Span<TreeNodePair> get_group_node_users(bNodeTree *ntree) + { + BLI_assert(group_node_users_.has_value()); + return group_node_users_->lookup(ntree); + } + + ID *get_owner_id(bNodeTree *ntree) + { + BLI_assert(owner_ids_.has_value()); + return owner_ids_->lookup_default(ntree, &ntree->id); + } +}; + +struct TreeUpdateResult { + bool interface_changed = false; + bool output_changed = false; +}; + +class NodeTreeMainUpdater { + private: + Main *bmain_; + NodeTreeUpdateExtraParams *params_; + Map<bNodeTree *, TreeUpdateResult> update_result_by_tree_; + NodeTreeRelations relations_; + + public: + NodeTreeMainUpdater(Main *bmain, NodeTreeUpdateExtraParams *params) + : bmain_(bmain), params_(params), relations_(bmain) + { + } + + void update() + { + Vector<bNodeTree *> changed_ntrees; + FOREACH_NODETREE_BEGIN (bmain_, ntree, id) { + if (ntree->changed_flag != NTREE_CHANGED_NOTHING) { + changed_ntrees.append(ntree); + } + } + FOREACH_NODETREE_END; + this->update_rooted(changed_ntrees); + } + + void update_rooted(Span<bNodeTree *> root_ntrees) + { + if (root_ntrees.is_empty()) { + return; + } + + bool is_single_tree_update = false; + + if (root_ntrees.size() == 1) { + bNodeTree *ntree = root_ntrees[0]; + if (ntree->changed_flag == NTREE_CHANGED_NOTHING) { + return; + } + const TreeUpdateResult result = this->update_tree(*ntree); + update_result_by_tree_.add_new(ntree, result); + if (!result.interface_changed && !result.output_changed) { + is_single_tree_update = true; + } + } + + if (!is_single_tree_update) { + Vector<bNodeTree *> ntrees_in_order = this->get_tree_update_order(root_ntrees); + for (bNodeTree *ntree : ntrees_in_order) { + if (ntree->changed_flag == NTREE_CHANGED_NOTHING) { + continue; + } + if (!update_result_by_tree_.contains(ntree)) { + const TreeUpdateResult result = this->update_tree(*ntree); + update_result_by_tree_.add_new(ntree, result); + } + const TreeUpdateResult result = update_result_by_tree_.lookup(ntree); + Span<TreeNodePair> dependent_trees = relations_.get_group_node_users(ntree); + if (result.output_changed) { + for (const TreeNodePair &pair : dependent_trees) { + add_node_tag(pair.first, pair.second, NTREE_CHANGED_NODE_OUTPUT); + } + } + if (result.interface_changed) { + for (const TreeNodePair &pair : dependent_trees) { + add_node_tag(pair.first, pair.second, NTREE_CHANGED_NODE_PROPERTY); + } + } + } + } + + for (const auto item : update_result_by_tree_.items()) { + bNodeTree *ntree = item.key; + const TreeUpdateResult &result = item.value; + + this->reset_changed_flags(*ntree); + + if (result.interface_changed) { + if (ntree->type == NTREE_GEOMETRY) { + relations_.ensure_modifier_users(); + for (const ObjectModifierPair &pair : relations_.get_modifier_users(ntree)) { + Object *object = pair.first; + ModifierData *md = pair.second; + + if (md->type == eModifierType_Nodes) { + MOD_nodes_update_interface(object, (NodesModifierData *)md); + } + } + } + } + + if (params_) { + relations_.ensure_owner_ids(); + ID *id = relations_.get_owner_id(ntree); + if (params_->tree_changed_fn) { + params_->tree_changed_fn(id, ntree, params_->user_data); + } + if (params_->tree_output_changed_fn && result.output_changed) { + params_->tree_output_changed_fn(id, ntree, params_->user_data); + } + } + } + } + + private: + enum class ToposortMark { + None, + Temporary, + Permanent, + }; + + using ToposortMarkMap = Map<bNodeTree *, ToposortMark>; + + /** + * Finds all trees that depend on the given trees (through node groups). Then those trees are + * ordered such that all trees used by one tree come before it. + */ + Vector<bNodeTree *> get_tree_update_order(Span<bNodeTree *> root_ntrees) + { + relations_.ensure_group_node_users(); + + Set<bNodeTree *> trees_to_update = get_trees_to_update(root_ntrees); + + Vector<bNodeTree *> sorted_ntrees; + + ToposortMarkMap marks; + for (bNodeTree *ntree : trees_to_update) { + marks.add_new(ntree, ToposortMark::None); + } + for (bNodeTree *ntree : trees_to_update) { + if (marks.lookup(ntree) == ToposortMark::None) { + const bool cycle_detected = !this->get_tree_update_order__visit_recursive( + ntree, marks, sorted_ntrees); + /* This should be prevented by higher level operators. */ + BLI_assert(!cycle_detected); + UNUSED_VARS_NDEBUG(cycle_detected); + } + } + + std::reverse(sorted_ntrees.begin(), sorted_ntrees.end()); + + return sorted_ntrees; + } + + bool get_tree_update_order__visit_recursive(bNodeTree *ntree, + ToposortMarkMap &marks, + Vector<bNodeTree *> &sorted_ntrees) + { + ToposortMark &mark = marks.lookup(ntree); + if (mark == ToposortMark::Permanent) { + return true; + } + if (mark == ToposortMark::Temporary) { + /* There is a dependency cycle. */ + return false; + } + + mark = ToposortMark::Temporary; + + for (const TreeNodePair &pair : relations_.get_group_node_users(ntree)) { + this->get_tree_update_order__visit_recursive(pair.first, marks, sorted_ntrees); + } + sorted_ntrees.append(ntree); + + mark = ToposortMark::Permanent; + return true; + } + + Set<bNodeTree *> get_trees_to_update(Span<bNodeTree *> root_ntrees) + { + relations_.ensure_group_node_users(); + + Set<bNodeTree *> reachable_trees; + VectorSet<bNodeTree *> trees_to_check = root_ntrees; + + while (!trees_to_check.is_empty()) { + bNodeTree *ntree = trees_to_check.pop(); + if (reachable_trees.add(ntree)) { + for (const TreeNodePair &pair : relations_.get_group_node_users(ntree)) { + trees_to_check.add(pair.first); + } + } + } + + return reachable_trees; + } + + TreeUpdateResult update_tree(bNodeTree &ntree) + { + TreeUpdateResult result; + + /* Use a #NodeTreeRef to speedup certain queries. It is rebuilt whenever the node tree topology + * changes, which typically happens zero or one times during the entire update of the node + * tree. */ + std::unique_ptr<NodeTreeRef> tree_ref; + this->ensure_tree_ref(ntree, tree_ref); + + this->update_socket_link_and_use(*tree_ref); + this->update_individual_nodes(ntree, tree_ref); + this->update_internal_links(ntree, tree_ref); + this->update_generic_callback(ntree, tree_ref); + this->remove_unused_previews_when_necessary(ntree); + + this->ensure_tree_ref(ntree, tree_ref); + if (ntree.type == NTREE_GEOMETRY) { + if (node_field_inferencing::update_field_inferencing(*tree_ref)) { + result.interface_changed = true; + } + } + + result.output_changed = this->check_if_output_changed(*tree_ref); + + this->update_socket_link_and_use(*tree_ref); + this->update_node_levels(ntree); + this->update_link_validation(ntree); + + if (ntree.type == NTREE_TEXTURE) { + ntreeTexCheckCyclics(&ntree); + } + + if (ntree.changed_flag & NTREE_CHANGED_INTERFACE || ntree.changed_flag & NTREE_CHANGED_ANY) { + result.interface_changed = true; + } + + if (result.interface_changed) { + ntreeInterfaceTypeUpdate(&ntree); + } + + return result; + } + + void ensure_tree_ref(bNodeTree &ntree, std::unique_ptr<NodeTreeRef> &tree_ref) + { + if (!tree_ref) { + tree_ref = std::make_unique<NodeTreeRef>(&ntree); + } + } + + void update_socket_link_and_use(const NodeTreeRef &tree) + { + for (const InputSocketRef *socket : tree.input_sockets()) { + bNodeSocket *bsocket = socket->bsocket(); + if (socket->directly_linked_links().is_empty()) { + bsocket->link = nullptr; + } + else { + bsocket->link = socket->directly_linked_links()[0]->blink(); + } + } + + this->update_socket_used_tags(tree); + } + + void update_socket_used_tags(const NodeTreeRef &tree) + { + for (const SocketRef *socket : tree.sockets()) { + bNodeSocket *bsocket = socket->bsocket(); + bsocket->flag &= ~SOCK_IN_USE; + for (const LinkRef *link : socket->directly_linked_links()) { + if (!link->is_muted()) { + bsocket->flag |= SOCK_IN_USE; + break; + } + } + } + } + + void update_individual_nodes(bNodeTree &ntree, std::unique_ptr<NodeTreeRef> &tree_ref) + { + /* Iterate over nodes instead of #NodeTreeRef, because the #tree_ref might be outdated after + * some update functions. */ + LISTBASE_FOREACH (bNode *, bnode, &ntree.nodes) { + this->ensure_tree_ref(ntree, tree_ref); + const NodeRef &node = *tree_ref->find_node(*bnode); + if (this->should_update_individual_node(node)) { + const uint32_t old_changed_flag = ntree.changed_flag; + ntree.changed_flag = NTREE_CHANGED_NOTHING; + + /* This may set #ntree.changed_flag which is detected below. */ + this->update_individual_node(node); + + if (ntree.changed_flag != NTREE_CHANGED_NOTHING) { + /* The tree ref is outdated and needs to be rebuilt. Generally, only very few update + * functions change the node. Typically zero or one nodes change after an update. */ + tree_ref.reset(); + } + ntree.changed_flag |= old_changed_flag; + } + } + } + + bool should_update_individual_node(const NodeRef &node) + { + bNodeTree &ntree = *node.btree(); + bNode &bnode = *node.bnode(); + if (ntree.changed_flag & NTREE_CHANGED_ANY) { + return true; + } + if (bnode.changed_flag & NTREE_CHANGED_NODE_PROPERTY) { + return true; + } + if (ntree.changed_flag & NTREE_CHANGED_LINK) { + /* Node groups currently always rebuilt their sockets when they are updated. + * So avoid calling the update method when no new link was added to it. */ + if (node.is_group_input_node()) { + if (node.outputs().last()->is_directly_linked()) { + return true; + } + } + else if (node.is_group_output_node()) { + if (node.inputs().last()->is_directly_linked()) { + return true; + } + } + else { + /* Currently we have no way to tell if a node needs to be updated when a link changed. */ + return true; + } + } + if (ntree.changed_flag & NTREE_CHANGED_INTERFACE) { + if (node.is_group_input_node() || node.is_group_output_node()) { + return true; + } + } + return false; + } + + void update_individual_node(const NodeRef &node) + { + bNodeTree &ntree = *node.btree(); + bNode &bnode = *node.bnode(); + bNodeType &ntype = *bnode.typeinfo; + if (ntype.group_update_func) { + ntype.group_update_func(&ntree, &bnode); + } + if (ntype.updatefunc) { + ntype.updatefunc(&ntree, &bnode); + } + } + + void update_internal_links(bNodeTree &ntree, std::unique_ptr<NodeTreeRef> &tree_ref) + { + bool any_internal_links_updated = false; + this->ensure_tree_ref(ntree, tree_ref); + for (const NodeRef *node : tree_ref->nodes()) { + if (!this->should_update_individual_node(*node)) { + continue; + } + /* Find all expected internal links. */ + Vector<std::pair<bNodeSocket *, bNodeSocket *>> expected_internal_links; + for (const OutputSocketRef *output_socket : node->outputs()) { + if (!output_socket->is_available()) { + continue; + } + if (!output_socket->is_directly_linked()) { + continue; + } + if (output_socket->bsocket()->flag & SOCK_NO_INTERNAL_LINK) { + continue; + } + const InputSocketRef *input_socket = this->find_internally_linked_input(output_socket); + if (input_socket != nullptr) { + expected_internal_links.append({input_socket->bsocket(), output_socket->bsocket()}); + } + } + /* rebuilt internal links if they have changed. */ + if (node->internal_links().size() != expected_internal_links.size()) { + this->update_internal_links_in_node(ntree, *node->bnode(), expected_internal_links); + any_internal_links_updated = true; + } + else { + for (auto &item : expected_internal_links) { + const bNodeSocket *from_socket = item.first; + const bNodeSocket *to_socket = item.second; + bool found = false; + for (const InternalLinkRef *internal_link : node->internal_links()) { + if (from_socket == internal_link->from().bsocket() && + to_socket == internal_link->to().bsocket()) { + found = true; + } + } + if (!found) { + this->update_internal_links_in_node(ntree, *node->bnode(), expected_internal_links); + any_internal_links_updated = true; + break; + } + } + } + } + + if (any_internal_links_updated) { + tree_ref.reset(); + } + } + + const InputSocketRef *find_internally_linked_input(const OutputSocketRef *output_socket) + { + const InputSocketRef *selected_socket = nullptr; + int selected_priority = -1; + bool selected_is_linked = false; + for (const InputSocketRef *input_socket : output_socket->node().inputs()) { + if (!input_socket->is_available()) { + continue; + } + if (input_socket->bsocket()->flag & SOCK_NO_INTERNAL_LINK) { + continue; + } + const int priority = get_internal_link_type_priority(input_socket->bsocket()->typeinfo, + output_socket->bsocket()->typeinfo); + if (priority < 0) { + continue; + } + const bool is_linked = input_socket->is_directly_linked(); + const bool is_preferred = priority > selected_priority || (is_linked && !selected_is_linked); + if (!is_preferred) { + continue; + } + selected_socket = input_socket; + selected_priority = priority; + selected_is_linked = is_linked; + } + return selected_socket; + } + + void update_internal_links_in_node(bNodeTree &ntree, + bNode &node, + Span<std::pair<bNodeSocket *, bNodeSocket *>> links) + { + BLI_freelistN(&node.internal_links); + for (const auto &item : links) { + bNodeSocket *from_socket = item.first; + bNodeSocket *to_socket = item.second; + bNodeLink *link = (bNodeLink *)MEM_callocN(sizeof(bNodeLink), __func__); + link->fromnode = &node; + link->fromsock = from_socket; + link->tonode = &node; + link->tosock = to_socket; + link->flag |= NODE_LINK_VALID; + BLI_addtail(&node.internal_links, link); + } + BKE_ntree_update_tag_node_internal_link(&ntree, &node); + } + + void update_generic_callback(bNodeTree &ntree, std::unique_ptr<NodeTreeRef> &tree_ref) + { + if (ntree.typeinfo->update == nullptr) { + return; + } + + /* Reset the changed_flag to allow detecting when the update callback changed the node tree. */ + const uint32_t old_changed_flag = ntree.changed_flag; + ntree.changed_flag = NTREE_CHANGED_NOTHING; + + ntree.typeinfo->update(&ntree); + + if (ntree.changed_flag != NTREE_CHANGED_NOTHING) { + /* The tree ref is outdated and needs to be rebuilt. */ + tree_ref.reset(); + } + ntree.changed_flag |= old_changed_flag; + } + + void remove_unused_previews_when_necessary(bNodeTree &ntree) + { + /* Don't trigger preview removal when only those flags are set. */ + const uint32_t allowed_flags = NTREE_CHANGED_LINK | NTREE_CHANGED_SOCKET_PROPERTY | + NTREE_CHANGED_NODE_PROPERTY | NTREE_CHANGED_NODE_OUTPUT | + NTREE_CHANGED_INTERFACE; + if ((ntree.changed_flag & allowed_flags) == ntree.changed_flag) { + return; + } + BKE_node_preview_remove_unused(&ntree); + } + + void update_node_levels(bNodeTree &ntree) + { + ntreeUpdateNodeLevels(&ntree); + } + + void update_link_validation(bNodeTree &ntree) + { + LISTBASE_FOREACH (bNodeLink *, link, &ntree.links) { + link->flag |= NODE_LINK_VALID; + if (link->fromnode && link->tonode && link->fromnode->level <= link->tonode->level) { + link->flag &= ~NODE_LINK_VALID; + } + else if (ntree.typeinfo->validate_link) { + const eNodeSocketDatatype from_type = static_cast<eNodeSocketDatatype>( + link->fromsock->type); + const eNodeSocketDatatype to_type = static_cast<eNodeSocketDatatype>(link->tosock->type); + if (!ntree.typeinfo->validate_link(from_type, to_type)) { + link->flag &= ~NODE_LINK_VALID; + } + } + } + } + + bool check_if_output_changed(const NodeTreeRef &tree) + { + bNodeTree &btree = *tree.btree(); + + /* Compute a hash that represents the node topology connected to the output. This always has to + * be updated even if it is not used to detect changes right now. Otherwise + * #btree.output_topology_hash will go out of date. */ + const Vector<const SocketRef *> tree_output_sockets = this->find_output_sockets(tree); + const uint32_t old_topology_hash = btree.output_topology_hash; + const uint32_t new_topology_hash = this->get_combined_socket_topology_hash( + tree, tree_output_sockets); + btree.output_topology_hash = new_topology_hash; + + if (const AnimData *adt = BKE_animdata_from_id(&btree.id)) { + /* Drivers may copy values in the node tree around arbitrarily and may cause the output to + * change even if it wouldn't without drivers. Only some special drivers like `frame/5` can + * be used without causing updates all the time currently. In the future we could try to + * handle other drivers better as well. + * Note that this optimization only works in practice when the depsgraph didn't also get a + * copy-on-write tag for the node tree (which happens when changing node properties). It does + * work in a few situations like adding reroutes and duplicating nodes though. */ + LISTBASE_FOREACH (const FCurve *, fcurve, &adt->drivers) { + const ChannelDriver *driver = fcurve->driver; + const StringRef expression = driver->expression; + if (expression.startswith("frame")) { + const StringRef remaining_expression = expression.drop_known_prefix("frame"); + if (remaining_expression.find_first_not_of(" */+-0123456789.") == StringRef::not_found) { + continue; + } + } + /* Unrecognized driver, assume that the output always changes. */ + return true; + } + } + + if (btree.changed_flag & NTREE_CHANGED_ANY) { + return true; + } + + if (old_topology_hash != new_topology_hash) { + return true; + } + + /* The topology hash can only be used when only topology-changing operations have been done. */ + if (btree.changed_flag == + (btree.changed_flag & (NTREE_CHANGED_LINK | NTREE_CHANGED_REMOVED_NODE))) { + if (old_topology_hash == new_topology_hash) { + return false; + } + } + + if (!this->check_if_socket_outputs_changed_based_on_flags(tree, tree_output_sockets)) { + return false; + } + + return true; + } + + Vector<const SocketRef *> find_output_sockets(const NodeTreeRef &tree) + { + Vector<const SocketRef *> sockets; + for (const NodeRef *node : tree.nodes()) { + const bNode *bnode = node->bnode(); + if (bnode->typeinfo->nclass != NODE_CLASS_OUTPUT && bnode->type != NODE_GROUP_OUTPUT) { + continue; + } + for (const InputSocketRef *socket : node->inputs()) { + if (socket->idname() != "NodeSocketVirtual") { + sockets.append(socket); + } + } + } + return sockets; + } + + /** + * Computes a hash that changes when the node tree topology connected to an output node changes. + * Adding reroutes does not have an effect on the hash. + */ + uint32_t get_combined_socket_topology_hash(const NodeTreeRef &tree, + Span<const SocketRef *> sockets) + { + Array<uint32_t> hashes = this->get_socket_topology_hashes(tree, sockets); + uint32_t combined_hash = 0; + for (uint32_t hash : hashes) { + combined_hash = noise::hash(combined_hash, hash); + } + return combined_hash; + } + + Array<uint32_t> get_socket_topology_hashes(const NodeTreeRef &tree, + Span<const SocketRef *> sockets) + { + Array<std::optional<uint32_t>> hash_by_socket_id(tree.sockets().size()); + Stack<const SocketRef *> sockets_to_check = sockets; + + while (!sockets_to_check.is_empty()) { + const SocketRef &in_out_socket = *sockets_to_check.peek(); + const NodeRef &node = in_out_socket.node(); + + if (hash_by_socket_id[in_out_socket.id()].has_value()) { + sockets_to_check.pop(); + /* Socket is handled already. */ + continue; + } + + if (in_out_socket.is_input()) { + /* For input sockets, first compute the hashes of all linked sockets. */ + const InputSocketRef &socket = in_out_socket.as_input(); + bool all_origins_computed = true; + for (const OutputSocketRef *origin_socket : socket.logically_linked_sockets()) { + if (!hash_by_socket_id[origin_socket->id()].has_value()) { + sockets_to_check.push(origin_socket); + all_origins_computed = false; + } + } + if (!all_origins_computed) { + continue; + } + /* When the hashes for the linked sockets are ready, combine them into a hash for the input + * socket. */ + const uint64_t socket_ptr = (uintptr_t)socket.bsocket(); + uint32_t socket_hash = noise::hash(socket_ptr, socket_ptr >> 32); + for (const OutputSocketRef *origin_socket : socket.logically_linked_sockets()) { + const uint32_t origin_socket_hash = *hash_by_socket_id[origin_socket->id()]; + socket_hash = noise::hash(socket_hash, origin_socket_hash); + } + hash_by_socket_id[socket.id()] = socket_hash; + sockets_to_check.pop(); + } + else { + /* For output sockets, first compute the hashes of all available input sockets. */ + const OutputSocketRef &socket = in_out_socket.as_output(); + bool all_available_inputs_computed = true; + for (const InputSocketRef *input_socket : node.inputs()) { + if (input_socket->is_available()) { + if (!hash_by_socket_id[input_socket->id()].has_value()) { + sockets_to_check.push(input_socket); + all_available_inputs_computed = false; + } + } + } + if (!all_available_inputs_computed) { + continue; + } + /* When all input socket hashes have been computed, combine them into a hash for the output + * socket. */ + const uint64_t socket_ptr = (uintptr_t)socket.bsocket(); + uint32_t socket_hash = noise::hash(socket_ptr, socket_ptr >> 32); + for (const InputSocketRef *input_socket : node.inputs()) { + if (input_socket->is_available()) { + const uint32_t input_socket_hash = *hash_by_socket_id[input_socket->id()]; + socket_hash = noise::hash(socket_hash, input_socket_hash); + } + } + hash_by_socket_id[socket.id()] = socket_hash; + sockets_to_check.pop(); + } + } + + /* Create output array. */ + Array<uint32_t> hashes(sockets.size()); + for (const int i : sockets.index_range()) { + hashes[i] = *hash_by_socket_id[sockets[i]->id()]; + } + return hashes; + } + + /** + * Returns true when any of the provided sockets changed its values. A change is detected by + * checking the #changed_flag on connected sockets and nodes. + */ + bool check_if_socket_outputs_changed_based_on_flags(const NodeTreeRef &tree, + Span<const SocketRef *> sockets) + { + /* Avoid visiting the same socket twice when multiple links point to the same socket. */ + Array<bool> pushed_by_socket_id(tree.sockets().size(), false); + Stack<const SocketRef *> sockets_to_check = sockets; + + for (const SocketRef *socket : sockets) { + pushed_by_socket_id[socket->id()] = true; + } + + while (!sockets_to_check.is_empty()) { + const SocketRef &in_out_socket = *sockets_to_check.pop(); + const bNode &bnode = *in_out_socket.node().bnode(); + const bNodeSocket &bsocket = *in_out_socket.bsocket(); + if (bsocket.changed_flag != NTREE_CHANGED_NOTHING) { + return true; + } + if (bnode.changed_flag != NTREE_CHANGED_NOTHING) { + const bool only_unused_internal_link_changed = (bnode.flag & NODE_MUTED) == 0 && + bnode.changed_flag == + NTREE_CHANGED_INTERNAL_LINK; + if (!only_unused_internal_link_changed) { + return true; + } + } + if (in_out_socket.is_input()) { + const InputSocketRef &socket = in_out_socket.as_input(); + for (const OutputSocketRef *origin_socket : socket.logically_linked_sockets()) { + bool &pushed = pushed_by_socket_id[origin_socket->id()]; + if (!pushed) { + sockets_to_check.push(origin_socket); + pushed = true; + } + } + } + else { + const OutputSocketRef &socket = in_out_socket.as_output(); + for (const InputSocketRef *input_socket : socket.node().inputs()) { + if (input_socket->is_available()) { + bool &pushed = pushed_by_socket_id[input_socket->id()]; + if (!pushed) { + sockets_to_check.push(input_socket); + pushed = true; + } + } + } + } + } + return false; + } + + void reset_changed_flags(bNodeTree &ntree) + { + ntree.changed_flag = NTREE_CHANGED_NOTHING; + LISTBASE_FOREACH (bNode *, node, &ntree.nodes) { + node->changed_flag = NTREE_CHANGED_NOTHING; + node->update = 0; + LISTBASE_FOREACH (bNodeSocket *, socket, &node->inputs) { + socket->changed_flag = NTREE_CHANGED_NOTHING; + } + LISTBASE_FOREACH (bNodeSocket *, socket, &node->outputs) { + socket->changed_flag = NTREE_CHANGED_NOTHING; + } + } + } +}; + +} // namespace blender::bke + +void BKE_ntree_update_tag_all(bNodeTree *ntree) +{ + add_tree_tag(ntree, NTREE_CHANGED_ANY); +} + +void BKE_ntree_update_tag_node_property(bNodeTree *ntree, bNode *node) +{ + add_node_tag(ntree, node, NTREE_CHANGED_NODE_PROPERTY); +} + +void BKE_ntree_update_tag_node_new(bNodeTree *ntree, bNode *node) +{ + add_node_tag(ntree, node, NTREE_CHANGED_NODE_PROPERTY); +} + +void BKE_ntree_update_tag_socket_property(bNodeTree *ntree, bNodeSocket *socket) +{ + add_socket_tag(ntree, socket, NTREE_CHANGED_SOCKET_PROPERTY); +} + +void BKE_ntree_update_tag_socket_new(bNodeTree *ntree, bNodeSocket *socket) +{ + add_socket_tag(ntree, socket, NTREE_CHANGED_SOCKET_PROPERTY); +} + +void BKE_ntree_update_tag_socket_removed(bNodeTree *ntree) +{ + add_tree_tag(ntree, NTREE_CHANGED_REMOVED_SOCKET); +} + +void BKE_ntree_update_tag_socket_type(bNodeTree *ntree, bNodeSocket *socket) +{ + add_socket_tag(ntree, socket, NTREE_CHANGED_SOCKET_PROPERTY); +} + +void BKE_ntree_update_tag_socket_availability(bNodeTree *ntree, bNodeSocket *socket) +{ + add_socket_tag(ntree, socket, NTREE_CHANGED_SOCKET_PROPERTY); +} + +void BKE_ntree_update_tag_node_removed(bNodeTree *ntree) +{ + add_tree_tag(ntree, NTREE_CHANGED_REMOVED_NODE); +} + +void BKE_ntree_update_tag_node_internal_link(bNodeTree *ntree, bNode *node) +{ + add_node_tag(ntree, node, NTREE_CHANGED_INTERNAL_LINK); +} + +void BKE_ntree_update_tag_link_changed(bNodeTree *ntree) +{ + add_tree_tag(ntree, NTREE_CHANGED_LINK); +} + +void BKE_ntree_update_tag_link_removed(bNodeTree *ntree) +{ + add_tree_tag(ntree, NTREE_CHANGED_LINK); +} + +void BKE_ntree_update_tag_link_added(bNodeTree *ntree, bNodeLink *UNUSED(link)) +{ + add_tree_tag(ntree, NTREE_CHANGED_LINK); +} + +void BKE_ntree_update_tag_link_mute(bNodeTree *ntree, bNodeLink *UNUSED(link)) +{ + add_tree_tag(ntree, NTREE_CHANGED_LINK); +} + +void BKE_ntree_update_tag_missing_runtime_data(bNodeTree *ntree) +{ + add_tree_tag(ntree, NTREE_CHANGED_ALL); +} + +void BKE_ntree_update_tag_interface(bNodeTree *ntree) +{ + add_tree_tag(ntree, NTREE_CHANGED_INTERFACE); +} + +void BKE_ntree_update_tag_id_changed(Main *bmain, ID *id) +{ + FOREACH_NODETREE_BEGIN (bmain, ntree, ntree_id) { + LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { + if (node->id == id) { + node->update |= NODE_UPDATE_ID; + add_node_tag(ntree, node, NTREE_CHANGED_NODE_PROPERTY); + } + } + } + FOREACH_NODETREE_END; +} + +/** + * Protect from recursive calls into the updating function. Some node update functions might + * trigger this from Python or in other cases. + * + * This could be added to #Main, but given that there is generally only one #Main, that's not + * really worth it now. + */ +static bool is_updating = false; + +void BKE_ntree_update_main(Main *bmain, NodeTreeUpdateExtraParams *params) +{ + if (is_updating) { + return; + } + + is_updating = true; + blender::bke::NodeTreeMainUpdater updater{bmain, params}; + updater.update(); + is_updating = false; +} + +void BKE_ntree_update_main_tree(Main *bmain, bNodeTree *ntree, NodeTreeUpdateExtraParams *params) +{ + if (ntree == nullptr) { + BKE_ntree_update_main(bmain, params); + return; + } + + if (is_updating) { + return; + } + + is_updating = true; + blender::bke::NodeTreeMainUpdater updater{bmain, params}; + updater.update_rooted({ntree}); + is_updating = false; +} diff --git a/source/blender/blenkernel/intern/spline_nurbs.cc b/source/blender/blenkernel/intern/spline_nurbs.cc index 69afb82baa8..e9d4ba7c7ef 100644 --- a/source/blender/blenkernel/intern/spline_nurbs.cc +++ b/source/blender/blenkernel/intern/spline_nurbs.cc @@ -257,13 +257,13 @@ void NURBSpline::calculate_knots() const Span<float> NURBSpline::knots() const { if (!knots_dirty_) { - BLI_assert(knots_.size() == this->size() + order_); + BLI_assert(knots_.size() == this->knots_size()); return knots_; } std::lock_guard lock{knots_mutex_}; if (!knots_dirty_) { - BLI_assert(knots_.size() == this->size() + order_); + BLI_assert(knots_.size() == this->knots_size()); return knots_; } diff --git a/source/blender/blenkernel/intern/volume.cc b/source/blender/blenkernel/intern/volume.cc index 130aa957491..63807f90334 100644 --- a/source/blender/blenkernel/intern/volume.cc +++ b/source/blender/blenkernel/intern/volume.cc @@ -537,7 +537,7 @@ static void volume_copy_data(Main *UNUSED(bmain), #ifdef WITH_OPENVDB if (volume_src->runtime.grids) { const VolumeGridVector &grids_src = *(volume_src->runtime.grids); - volume_dst->runtime.grids = OBJECT_GUARDED_NEW(VolumeGridVector, grids_src); + volume_dst->runtime.grids = MEM_new<VolumeGridVector>(__func__, grids_src); } #endif @@ -551,7 +551,8 @@ static void volume_free_data(ID *id) BKE_volume_batch_cache_free(volume); MEM_SAFE_FREE(volume->mat); #ifdef WITH_OPENVDB - OBJECT_GUARDED_SAFE_DELETE(volume->runtime.grids, VolumeGridVector); + MEM_delete(volume->runtime.grids); + volume->runtime.grids = nullptr; #endif } @@ -683,7 +684,7 @@ void BKE_volume_init_grids(Volume *volume) { #ifdef WITH_OPENVDB if (volume->runtime.grids == nullptr) { - volume->runtime.grids = OBJECT_GUARDED_NEW(VolumeGridVector); + volume->runtime.grids = MEM_new<VolumeGridVector>(__func__); } #else UNUSED_VARS(volume); @@ -1129,16 +1130,16 @@ void BKE_volume_grids_backup_restore(Volume *volume, VolumeGridVector *grids, co if (!grids->is_loaded()) { /* No grids loaded in CoW datablock, nothing lost by discarding. */ - OBJECT_GUARDED_DELETE(grids, VolumeGridVector); + MEM_delete(grids); } else if (!STREQ(volume->filepath, filepath)) { /* Filepath changed, discard grids from CoW datablock. */ - OBJECT_GUARDED_DELETE(grids, VolumeGridVector); + MEM_delete(grids); } else { /* Keep grids from CoW datablock. We might still unload them a little * later in BKE_volume_eval_geometry if the frame changes. */ - OBJECT_GUARDED_DELETE(volume->runtime.grids, VolumeGridVector); + MEM_delete(volume->runtime.grids); volume->runtime.grids = grids; } #else |