From 1b61e419a621bc0e408bcc8c311daf2b263ea560 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Mon, 5 May 2025 19:25:05 +0200 Subject: [PATCH] Geometry Nodes: support caching imported files Currently, the import nodes always reimport on each evaluation. This patch adds support for caching the loaded geometries. This is integrated with `BLI_memory_cache.hh` and thus also takes the cache size limit into account. If an imported file is modified on disk, the cache is invalidated. However, Geometry Nodes will not automatically reevaluate when a file changes, so the user would have to trigger the evaluation in some other way. This is an alternative solution to #124369. The main benefits are that the cache invalidation happens automatically and that the cache system is more general and does not have to know about e.g. the different file types. Caching speeds up node setups that heavily rely on import nodes significantly. Pull Request: https://projects.blender.org/blender/blender/pulls/138425 --- .../blender/blenlib/BLI_generic_key_string.hh | 48 ++++++ .../blenlib/BLI_memory_cache_file_load.hh | 34 +++++ source/blender/blenlib/CMakeLists.txt | 3 + .../blenlib/intern/memory_cache_file_load.cc | 143 ++++++++++++++++++ .../blender/nodes/NOD_geometry_nodes_log.hh | 4 + .../geometry/nodes/node_geo_import_csv.cc | 59 +++++--- .../geometry/nodes/node_geo_import_obj.cc | 73 +++++---- .../geometry/nodes/node_geo_import_ply.cc | 55 ++++--- .../geometry/nodes/node_geo_import_stl.cc | 60 +++++--- .../geometry/nodes/node_geo_import_text.cc | 54 +++++-- .../nodes/intern/geometry_nodes_log.cc | 14 ++ 11 files changed, 440 insertions(+), 107 deletions(-) create mode 100644 source/blender/blenlib/BLI_generic_key_string.hh create mode 100644 source/blender/blenlib/BLI_memory_cache_file_load.hh create mode 100644 source/blender/blenlib/intern/memory_cache_file_load.cc diff --git a/source/blender/blenlib/BLI_generic_key_string.hh b/source/blender/blenlib/BLI_generic_key_string.hh new file mode 100644 index 00000000000..b7c7de0bbbf --- /dev/null +++ b/source/blender/blenlib/BLI_generic_key_string.hh @@ -0,0 +1,48 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BLI_generic_key.hh" +#include "BLI_string_ref.hh" +#include "BLI_struct_equality_utils.hh" +#include "BLI_utility_mixins.hh" + +namespace blender { + +/** Utility class that to easy create a #GenericKey from a string. */ +class GenericStringKey : public GenericKey, NonMovable { + private: + std::string value_; + /** This may reference the string stored in value_. */ + StringRef value_ref_; + + public: + GenericStringKey(StringRef value) : value_ref_(value) {} + + uint64_t hash() const override + { + return get_default_hash(value_ref_); + } + + BLI_STRUCT_EQUALITY_OPERATORS_1(GenericStringKey, value_ref_) + + bool equal_to(const GenericKey &other) const override + { + if (const auto *other_typed = dynamic_cast(&other)) { + return value_ref_ == other_typed->value_ref_; + } + return false; + } + + std::unique_ptr to_storable() const override + { + auto storable_key = std::make_unique(""); + storable_key->value_ = value_ref_; + storable_key->value_ref_ = storable_key->value_; + return storable_key; + } +}; + +} // namespace blender diff --git a/source/blender/blenlib/BLI_memory_cache_file_load.hh b/source/blender/blenlib/BLI_memory_cache_file_load.hh new file mode 100644 index 00000000000..ac7c28210bc --- /dev/null +++ b/source/blender/blenlib/BLI_memory_cache_file_load.hh @@ -0,0 +1,34 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BLI_memory_cache.hh" +#include "BLI_string_ref.hh" + +namespace blender::memory_cache { + +/** + * Call the given loader function if its result has not been cached yet. The cache key is a + * combination of loader_key and file_paths. load_fn is responsible for still producing a valid + * cache value even if a file is not found. + */ +template +std::shared_ptr get_loaded(const GenericKey &loader_key, + Span file_paths, + FunctionRef()> load_fn); + +std::shared_ptr get_loaded_base(const GenericKey &loader_key, + Span file_paths, + FunctionRef()> load_fn); + +template +inline std::shared_ptr get_loaded(const GenericKey &loader_key, + Span file_paths, + FunctionRef()> load_fn) +{ + return std::dynamic_pointer_cast(get_loaded_base(loader_key, file_paths, load_fn)); +} + +} // namespace blender::memory_cache diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt index 57dcb473f51..a7d0b5057b4 100644 --- a/source/blender/blenlib/CMakeLists.txt +++ b/source/blender/blenlib/CMakeLists.txt @@ -116,6 +116,7 @@ set(SRC intern/math_vector.cc intern/math_vector_inline.cc intern/memory_cache.cc + intern/memory_cache_file_load.cc intern/memory_counter.cc intern/memory_utils.cc intern/mesh_boolean.cc @@ -239,6 +240,7 @@ set(SRC BLI_function_ref.hh BLI_generic_array.hh BLI_generic_key.hh + BLI_generic_key_string.hh BLI_generic_pointer.hh BLI_generic_span.hh BLI_generic_value_map.hh @@ -325,6 +327,7 @@ set(SRC BLI_memblock.h BLI_memiter.h BLI_memory_cache.hh + BLI_memory_cache_file_load.hh BLI_memory_counter.hh BLI_memory_counter_fwd.hh BLI_memory_utils.h diff --git a/source/blender/blenlib/intern/memory_cache_file_load.cc b/source/blender/blenlib/intern/memory_cache_file_load.cc new file mode 100644 index 00000000000..4c651b7e368 --- /dev/null +++ b/source/blender/blenlib/intern/memory_cache_file_load.cc @@ -0,0 +1,143 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include +#include + +#include "BLI_fileops.hh" +#include "BLI_hash.hh" +#include "BLI_map.hh" +#include "BLI_memory_cache_file_load.hh" +#include "BLI_task.hh" +#include "BLI_vector.hh" +#include "BLI_vector_set.hh" + +namespace blender::memory_cache { + +/** + * A key used to identify data loaded from one or more files. + */ +class LoadFileKey : public GenericKey { + private: + /** The files to load from. */ + Vector file_paths_; + /** + * The key used to identify the loader. The same files might be loaded with different loaders + * which can result in different data that needs to be cached separately. + */ + std::shared_ptr loader_key_; + + public: + LoadFileKey(Vector file_paths, std::shared_ptr loader_key) + : file_paths_(std::move(file_paths)), loader_key_(std::move(loader_key)) + { + } + + Span file_paths() const + { + return file_paths_; + } + + uint64_t hash() const override + { + return get_default_hash(file_paths_, *loader_key_); + } + + friend bool operator==(const LoadFileKey &a, const LoadFileKey &b) + { + return a.file_paths_ == b.file_paths_ && *a.loader_key_ == *b.loader_key_; + } + + bool equal_to(const GenericKey &other) const override + { + if (const auto *other_typed = dynamic_cast(&other)) { + return *this == *other_typed; + } + return false; + } + + std::unique_ptr to_storable() const override + { + /* Currently #LoadFileKey is always storable, i.e. it owns all the data it references. A + * potential future optimization could be to support just referencing the paths and loader key, + * but that causes some boilerplate now that is not worth it. */ + return std::make_unique(*this); + } +}; + +static std::optional get_file_modification_time(const StringRefNull path) +{ + BLI_stat_t stat; + if (BLI_stat(path.c_str(), &stat) == -1) { + return std::nullopt; + } + return stat.st_mtime; +} + +struct FileStatMap { + std::mutex mutex; + Map> map; +}; + +static FileStatMap &get_file_stat_map() +{ + static FileStatMap file_stat_map; + return file_stat_map; +} + +static void invalidate_outdated_caches_if_necessary(const Span file_paths) +{ + FileStatMap &file_stat_map = get_file_stat_map(); + + /* Retrieve the file modification times before the lock because there is no need for the lock + * yet. While not guaranteed, retrieving the modification time is often optimized by the OS so + * that no actual access to the hard drive is necessary. */ + Array> new_times(file_paths.size()); + for (const int i : file_paths.index_range()) { + new_times[i] = get_file_modification_time(file_paths[i]); + } + + std::lock_guard lock{file_stat_map.mutex}; + + /* Find all paths that have changed on disk. */ + VectorSet outdated_paths; + for (const int i : file_paths.index_range()) { + const StringRefNull path = file_paths[i]; + const std::optional new_time = new_times[i]; + std::optional &old_time = file_stat_map.map.lookup_or_add_as(path, new_time); + if (old_time != new_time) { + outdated_paths.add(path); + old_time = new_time; + } + } + /* If any referenced file was changed, invalidate the caches that use it. */ + if (!outdated_paths.is_empty()) { + /* Isolate because a mutex is locked. */ + threading::isolate_task([&]() { + /* Invalidation is done while the mutex is locked so that other threads won't see the old + * cached value anymore after we've detected that it's oudated. */ + memory_cache::remove_if([&](const GenericKey &other_key) { + if (const auto *other_key_typed = dynamic_cast(&other_key)) { + const Span other_key_paths = other_key_typed->file_paths(); + return std::any_of( + other_key_paths.begin(), other_key_paths.end(), [&](const StringRefNull path) { + return outdated_paths.contains(path); + }); + } + return false; + }); + }); + } +} + +std::shared_ptr get_loaded_base(const GenericKey &loader_key, + Span file_paths, + FunctionRef()> load_fn) +{ + invalidate_outdated_caches_if_necessary(file_paths); + const LoadFileKey key{file_paths, loader_key.to_storable()}; + return memory_cache::get_base(key, load_fn); +} + +} // namespace blender::memory_cache diff --git a/source/blender/nodes/NOD_geometry_nodes_log.hh b/source/blender/nodes/NOD_geometry_nodes_log.hh index 43ad494fcfc..404069b8d0a 100644 --- a/source/blender/nodes/NOD_geometry_nodes_log.hh +++ b/source/blender/nodes/NOD_geometry_nodes_log.hh @@ -50,6 +50,7 @@ struct SpaceNode; struct NodesModifierData; +struct Report; namespace blender::nodes::geo_eval_log { @@ -69,6 +70,9 @@ struct NodeWarning { NodeWarningType type; std::string message; + NodeWarning(NodeWarningType type, StringRef message) : type(type), message(message) {} + NodeWarning(const Report &report); + uint64_t hash() const { return get_default_hash(this->type, this->message); diff --git a/source/blender/nodes/geometry/nodes/node_geo_import_csv.cc b/source/blender/nodes/geometry/nodes/node_geo_import_csv.cc index 7b394eb8b8c..f4228bd5b02 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_import_csv.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_import_csv.cc @@ -2,7 +2,11 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ +#include + +#include "BLI_generic_key_string.hh" #include "BLI_listbase.h" +#include "BLI_memory_cache_file_load.hh" #include "BLI_string.h" #include "BKE_report.hh" @@ -25,6 +29,17 @@ static void node_declare(NodeDeclarationBuilder &b) b.add_output("Point Cloud"); } +class LoadCsvCache : public memory_cache::CachedValue { + public: + GeometrySet geometry; + Vector warnings; + + void count_memory(MemoryCounter &counter) const override + { + this->geometry.count_memory(counter); + } +}; + static void node_geo_exec(GeoNodeExecParams params) { #ifdef WITH_IO_CSV @@ -47,31 +62,35 @@ static void node_geo_exec(GeoNodeExecParams params) return; } - blender::io::csv::CSVImportParams import_params{}; - import_params.delimiter = delimiter[0]; - STRNCPY(import_params.filepath, path->c_str()); + /* Encode delimiter in key because it affects the result. */ + const std::string loader_key = fmt::format("import_csv_node_{}", delimiter[0]); + std::shared_ptr cached_value = memory_cache::get_loaded( + GenericStringKey{loader_key}, {StringRefNull(*path)}, [&]() { + blender::io::csv::CSVImportParams import_params{}; + import_params.delimiter = delimiter[0]; + STRNCPY(import_params.filepath, path->c_str()); - ReportList reports; - BKE_reports_init(&reports, RPT_STORE); - BLI_SCOPED_DEFER([&]() { BKE_reports_free(&reports); }) - import_params.reports = &reports; + ReportList reports; + BKE_reports_init(&reports, RPT_STORE); + BLI_SCOPED_DEFER([&]() { BKE_reports_free(&reports); }); + import_params.reports = &reports; - PointCloud *pointcloud = blender::io::csv::import_csv_as_pointcloud(import_params); + PointCloud *pointcloud = blender::io::csv::import_csv_as_pointcloud(import_params); - LISTBASE_FOREACH (Report *, report, &(import_params.reports)->list) { - NodeWarningType type; - switch (report->type) { - case RPT_ERROR: - type = NodeWarningType::Error; - break; - default: - type = NodeWarningType::Info; - break; - } - params.error_message_add(type, TIP_(report->message)); + auto cached_value = std::make_unique(); + cached_value->geometry = GeometrySet::from_pointcloud(pointcloud); + + LISTBASE_FOREACH (Report *, report, &(import_params.reports)->list) { + cached_value->warnings.append_as(*report); + } + return cached_value; + }); + + for (const geo_eval_log::NodeWarning &warning : cached_value->warnings) { + params.error_message_add(warning.type, warning.message); } - params.set_output("Point Cloud", GeometrySet::from_pointcloud(pointcloud)); + params.set_output("Point Cloud", cached_value->geometry); #else params.error_message_add(NodeWarningType::Error, TIP_("Disabled, Blender was compiled without CSV I/O")); diff --git a/source/blender/nodes/geometry/nodes/node_geo_import_obj.cc b/source/blender/nodes/geometry/nodes/node_geo_import_obj.cc index 87d3d3c07ae..bb3b4575b8a 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_import_obj.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_import_obj.cc @@ -4,7 +4,9 @@ #include "node_geometry_util.hh" +#include "BLI_generic_key_string.hh" #include "BLI_listbase.h" +#include "BLI_memory_cache_file_load.hh" #include "BLI_string.h" #include "BKE_instances.hh" @@ -25,6 +27,17 @@ static void node_declare(NodeDeclarationBuilder &b) b.add_output("Instances"); } +class LoadObjCache : public memory_cache::CachedValue { + public: + GeometrySet geometry; + Vector warnings; + + void count_memory(MemoryCounter &counter) const override + { + this->geometry.count_memory(counter); + } +}; + static void node_geo_exec(GeoNodeExecParams params) { #ifdef WITH_IO_WAVEFRONT_OBJ @@ -35,42 +48,40 @@ static void node_geo_exec(GeoNodeExecParams params) return; } - OBJImportParams import_params; - STRNCPY(import_params.filepath, path->c_str()); + std::shared_ptr cached_value = memory_cache::get_loaded( + GenericStringKey{"import_obj_node"}, {StringRefNull(*path)}, [&]() { + OBJImportParams import_params; + STRNCPY(import_params.filepath, path->c_str()); - ReportList reports; - BKE_reports_init(&reports, RPT_STORE); - BLI_SCOPED_DEFER([&]() { BKE_reports_free(&reports); }); - import_params.reports = &reports; + ReportList reports; + BKE_reports_init(&reports, RPT_STORE); + BLI_SCOPED_DEFER([&]() { BKE_reports_free(&reports); }); + import_params.reports = &reports; - Vector geometries; - OBJ_import_geometries(&import_params, geometries); + Vector geometries; + OBJ_import_geometries(&import_params, geometries); - LISTBASE_FOREACH (Report *, report, &(import_params.reports)->list) { - NodeWarningType type; - switch (report->type) { - case RPT_ERROR: - type = NodeWarningType::Error; - break; - default: - type = NodeWarningType::Info; - break; - } - params.error_message_add(type, TIP_(report->message)); + bke::Instances *instances = new bke::Instances(); + for (GeometrySet geometry : geometries) { + const int handle = instances->add_reference(bke::InstanceReference{std::move(geometry)}); + instances->add_instance(handle, float4x4::identity()); + } + + auto cached_value = std::make_unique(); + cached_value->geometry = GeometrySet::from_instances(instances); + + LISTBASE_FOREACH (Report *, report, &(import_params.reports)->list) { + cached_value->warnings.append_as(*report); + } + + return cached_value; + }); + + for (const geo_eval_log::NodeWarning &warning : cached_value->warnings) { + params.error_message_add(warning.type, warning.message); } - if (geometries.is_empty()) { - params.set_default_remaining_outputs(); - return; - } - - bke::Instances *instances = new bke::Instances(); - for (GeometrySet geometry : geometries) { - const int handle = instances->add_reference(bke::InstanceReference{std::move(geometry)}); - instances->add_instance(handle, float4x4::identity()); - } - - params.set_output("Instances", GeometrySet::from_instances(instances)); + params.set_output("Instances", cached_value->geometry); #else params.error_message_add(NodeWarningType::Error, TIP_("Disabled, Blender was compiled without OBJ I/O")); diff --git a/source/blender/nodes/geometry/nodes/node_geo_import_ply.cc b/source/blender/nodes/geometry/nodes/node_geo_import_ply.cc index dc3673836de..8c3e5dcd320 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_import_ply.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_import_ply.cc @@ -4,7 +4,9 @@ #include "node_geometry_util.hh" +#include "BLI_generic_key_string.hh" #include "BLI_listbase.h" +#include "BLI_memory_cache_file_load.hh" #include "BLI_string.h" #include "BKE_report.hh" @@ -24,6 +26,17 @@ static void node_declare(NodeDeclarationBuilder &b) b.add_output("Mesh"); } +class LoadPlyCache : public memory_cache::CachedValue { + public: + GeometrySet geometry; + Vector warnings; + + void count_memory(MemoryCounter &counter) const override + { + this->geometry.count_memory(counter); + } +}; + static void node_geo_exec(GeoNodeExecParams params) { #ifdef WITH_IO_PLY @@ -34,31 +47,33 @@ static void node_geo_exec(GeoNodeExecParams params) return; } - PLYImportParams import_params; - STRNCPY(import_params.filepath, path->c_str()); - import_params.import_attributes = true; + std::shared_ptr cached_value = memory_cache::get_loaded( + GenericStringKey{"import_ply_node"}, {StringRefNull(*path)}, [&]() { + PLYImportParams import_params; + STRNCPY(import_params.filepath, path->c_str()); + import_params.import_attributes = true; - ReportList reports; - BKE_reports_init(&reports, RPT_STORE); - BLI_SCOPED_DEFER([&]() { BKE_reports_free(&reports); }) - import_params.reports = &reports; + ReportList reports; + BKE_reports_init(&reports, RPT_STORE); + BLI_SCOPED_DEFER([&]() { BKE_reports_free(&reports); }); + import_params.reports = &reports; - Mesh *mesh = PLY_import_mesh(import_params); + Mesh *mesh = PLY_import_mesh(import_params); - LISTBASE_FOREACH (Report *, report, &(import_params.reports)->list) { - NodeWarningType type; - switch (report->type) { - case RPT_ERROR: - type = NodeWarningType::Error; - break; - default: - type = NodeWarningType::Info; - break; - } - params.error_message_add(type, TIP_(report->message)); + auto cached_value = std::make_unique(); + cached_value->geometry = GeometrySet::from_mesh(mesh); + + LISTBASE_FOREACH (Report *, report, &(import_params.reports)->list) { + cached_value->warnings.append_as(*report); + } + return cached_value; + }); + + for (const geo_eval_log::NodeWarning &warning : cached_value->warnings) { + params.error_message_add(warning.type, warning.message); } - params.set_output("Mesh", GeometrySet::from_mesh(mesh)); + params.set_output("Mesh", cached_value->geometry); #else params.error_message_add(NodeWarningType::Error, diff --git a/source/blender/nodes/geometry/nodes/node_geo_import_stl.cc b/source/blender/nodes/geometry/nodes/node_geo_import_stl.cc index 687bd65866a..e1c229ec8fc 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_import_stl.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_import_stl.cc @@ -4,9 +4,13 @@ #include "node_geometry_util.hh" +#include "BLI_generic_key_string.hh" #include "BLI_listbase.h" +#include "BLI_memory_cache_file_load.hh" #include "BLI_string.h" +#include "DNA_mesh_types.h" + #include "BKE_report.hh" #include "IO_stl.hh" @@ -24,6 +28,17 @@ static void node_declare(NodeDeclarationBuilder &b) b.add_output("Mesh"); } +class LoadStlCache : public memory_cache::CachedValue { + public: + GeometrySet geometry; + Vector warnings; + + void count_memory(MemoryCounter &counter) const override + { + this->geometry.count_memory(counter); + } +}; + static void node_geo_exec(GeoNodeExecParams params) { #ifdef WITH_IO_STL @@ -34,33 +49,36 @@ static void node_geo_exec(GeoNodeExecParams params) return; } - STLImportParams import_params; - STRNCPY(import_params.filepath, path->c_str()); + std::shared_ptr cached_value = memory_cache::get_loaded( + GenericStringKey{"import_stl_node"}, {StringRefNull(*path)}, [&]() { + STLImportParams import_params; + STRNCPY(import_params.filepath, path->c_str()); - import_params.forward_axis = IO_AXIS_NEGATIVE_Z; - import_params.up_axis = IO_AXIS_Y; + import_params.forward_axis = IO_AXIS_NEGATIVE_Z; + import_params.up_axis = IO_AXIS_Y; - ReportList reports; - BKE_reports_init(&reports, RPT_STORE); - BLI_SCOPED_DEFER([&]() { BKE_reports_free(&reports); }) - import_params.reports = &reports; + ReportList reports; + BKE_reports_init(&reports, RPT_STORE); + BLI_SCOPED_DEFER([&]() { BKE_reports_free(&reports); }) + import_params.reports = &reports; - Mesh *mesh = STL_import_mesh(&import_params); + Mesh *mesh = STL_import_mesh(&import_params); - LISTBASE_FOREACH (Report *, report, &(import_params.reports)->list) { - NodeWarningType type; - switch (report->type) { - case RPT_ERROR: - type = NodeWarningType::Error; - break; - default: - type = NodeWarningType::Info; - break; - } - params.error_message_add(type, TIP_(report->message)); + auto cached_value = std::make_unique(); + cached_value->geometry = GeometrySet::from_mesh(mesh); + + LISTBASE_FOREACH (Report *, report, &(import_params.reports)->list) { + cached_value->warnings.append_as(*report); + } + + return cached_value; + }); + + for (const geo_eval_log::NodeWarning &warning : cached_value->warnings) { + params.error_message_add(warning.type, warning.message); } - params.set_output("Mesh", GeometrySet::from_mesh(mesh)); + params.set_output("Mesh", cached_value->geometry); #else params.error_message_add(NodeWarningType::Error, diff --git a/source/blender/nodes/geometry/nodes/node_geo_import_text.cc b/source/blender/nodes/geometry/nodes/node_geo_import_text.cc index b98ac6ce98e..04aa60adffa 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_import_text.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_import_text.cc @@ -3,6 +3,9 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ #include "BLI_fileops.h" +#include "BLI_generic_key_string.hh" +#include "BLI_memory_cache_file_load.hh" +#include "BLI_memory_counter.hh" #include "BLI_string_utf8.h" #include "node_geometry_util.hh" @@ -22,6 +25,17 @@ static void node_declare(NodeDeclarationBuilder &b) b.add_output("String"); } +class LoadTextCache : public memory_cache::CachedValue { + public: + std::string text; + Vector warnings; + + void count_memory(MemoryCounter &counter) const override + { + counter.add(this->text.size()); + } +}; + static void node_geo_exec(GeoNodeExecParams params) { const std::optional path = params.ensure_absolute_path( @@ -31,23 +45,33 @@ static void node_geo_exec(GeoNodeExecParams params) return; } - size_t buffer_len; - void *buffer = BLI_file_read_text_as_mem(path->c_str(), 0, &buffer_len); - if (!buffer) { - const std::string message = fmt::format(fmt::runtime(TIP_("Cannot open file: {}")), *path); - params.error_message_add(NodeWarningType::Error, message); - params.set_default_remaining_outputs(); - return; - } - BLI_SCOPED_DEFER([&]() { MEM_freeN(buffer); }); - if (BLI_str_utf8_invalid_byte(static_cast(buffer), buffer_len) != -1) { - params.error_message_add(NodeWarningType::Error, - TIP_("File contains invalid UTF-8 characters")); - params.set_default_remaining_outputs(); - return; + std::shared_ptr cached_value = memory_cache::get_loaded( + GenericStringKey{"import_text_node"}, {StringRefNull(*path)}, [&]() { + auto cached_value = std::make_unique(); + + size_t buffer_len; + void *buffer = BLI_file_read_text_as_mem(path->c_str(), 0, &buffer_len); + if (!buffer) { + const std::string message = fmt::format(fmt::runtime(TIP_("Cannot open file: {}")), + *path); + cached_value->warnings.append({NodeWarningType::Error, message}); + return cached_value; + } + BLI_SCOPED_DEFER([&]() { MEM_freeN(buffer); }); + if (BLI_str_utf8_invalid_byte(static_cast(buffer), buffer_len) != -1) { + cached_value->warnings.append( + {NodeWarningType::Error, TIP_("File contains invalid UTF-8 characters")}); + return cached_value; + } + cached_value->text = std::string(static_cast(buffer), buffer_len); + return cached_value; + }); + + for (const geo_eval_log::NodeWarning &warning : cached_value->warnings) { + params.error_message_add(warning.type, warning.message); } - params.set_output("String", std::string(static_cast(buffer), buffer_len)); + params.set_output("String", cached_value->text); } static void node_register() diff --git a/source/blender/nodes/intern/geometry_nodes_log.cc b/source/blender/nodes/intern/geometry_nodes_log.cc index 26288cd4d50..3b073597552 100644 --- a/source/blender/nodes/intern/geometry_nodes_log.cc +++ b/source/blender/nodes/intern/geometry_nodes_log.cc @@ -2,6 +2,7 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ +#include "DNA_windowmanager_types.h" #include "NOD_geometry_nodes_bundle.hh" #include "NOD_geometry_nodes_closure.hh" #include "NOD_geometry_nodes_log.hh" @@ -246,6 +247,19 @@ ClosureValueLog::ClosureValueLog(Vector inputs, } } +NodeWarning::NodeWarning(const Report &report) +{ + switch (report.type) { + case RPT_ERROR: + this->type = NodeWarningType::Error; + break; + default: + this->type = NodeWarningType::Info; + break; + } + this->message = report.message; +} + /* Avoid generating these in every translation unit. */ GeoModifierLog::GeoModifierLog() = default; GeoModifierLog::~GeoModifierLog() = default;