Blender 3.1 obj_export_io.hh output buffering
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc | |
index 92d478c20a1a2bd9beebda270848e369dfde3024..009b3eff8a836b5178d17f6675190160d1082d82 100644 | |
--- a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc | |
+++ b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc | |
@@ -234,6 +234,42 @@ TEST(obj_exporter_writer, mtllib) | |
BLI_delete(out_file_path.c_str(), false, false); | |
} | |
+TEST(obj_exporter_writer, buffer_flushing) | |
+{ | |
+ /* Because testing doesn't fully initialize Blender, we need the following. */ | |
+ BKE_tempdir_init(nullptr); | |
+ std::string out_file_path = blender::tests::flags_test_release_dir() + "/" + temp_file_path; | |
+ { | |
+ /* Use a tiny buffer, so that the test below exercises several flushes and fallbacks */ | |
+ typedef FileHandler<eFileType::OBJ, 16, 8> FHType; | |
+ std::unique_ptr<FHType> h = std::make_unique<FHType>(out_file_path); | |
+ h->write<eOBJSyntaxElement::object_name>("abc"); | |
+ h->write<eOBJSyntaxElement::object_name>("abcd"); | |
+ h->write<eOBJSyntaxElement::object_name>("abcde"); | |
+ h->write<eOBJSyntaxElement::object_name>("abcdef"); | |
+ h->write<eOBJSyntaxElement::object_name>("012345678901234567890123456789abcd"); | |
+ h->write<eOBJSyntaxElement::object_name>("123"); | |
+ h->write<eOBJSyntaxElement::curve_element_begin>(); | |
+ h->write<eOBJSyntaxElement::new_line>(); | |
+ h->write<eOBJSyntaxElement::nurbs_parameter_begin>(); | |
+ h->write<eOBJSyntaxElement::new_line>(); | |
+ } | |
+ const std::string result = read_temp_file_in_string(out_file_path); | |
+ using namespace std::string_literals; | |
+ const char *expected = R"(o abc | |
+o abcd | |
+o abcde | |
+o abcdef | |
+o 012345678901234567890123456789abcd | |
+o 123 | |
+curv 0.0 1.0 | |
+parm 0.0 | |
+)"; | |
+ ASSERT_EQ(result, expected); | |
+ BLI_delete(out_file_path.c_str(), false, false); | |
+} | |
+ | |
+ | |
/* Return true if string #a and string #b are equal after their first newline. */ | |
static bool strings_equal_after_first_lines(const std::string &a, const std::string &b) | |
{ | |
diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_io.hh b/source/blender/io/wavefront_obj/exporter/obj_export_io.hh | |
index a6f0174d68bb853d4dedc66afcd951fa3c608215..2862b50438bb904321dbc9c98676f886f1e9c4cc 100644 | |
--- a/source/blender/io/wavefront_obj/exporter/obj_export_io.hh | |
+++ b/source/blender/io/wavefront_obj/exporter/obj_export_io.hh | |
@@ -261,10 +261,16 @@ syntax_elem_to_formatting(const eMTLSyntaxElement key) | |
} | |
} | |
-template<eFileType filetype> class FileHandler : NonCopyable, NonMovable { | |
+template<eFileType filetype, size_t buffer_capacity = 64 * 1024, size_t flush_when_below = 1024> | |
+class FileHandler : NonCopyable, NonMovable { | |
+ BLI_STATIC_ASSERT(buffer_capacity > 0 && flush_when_below > 0 && buffer_capacity > flush_when_below, "Invalid buffer configuration"); | |
private: | |
FILE *outfile_ = nullptr; | |
std::string outfile_path_; | |
+ /* Manually implemented output buffer to avoid the fprintf overhead (while it does | |
+ * buffering internally, each call into fprintf also involves mutex locks and so on). */ | |
+ mutable char buffer_[buffer_capacity]; | |
+ mutable size_t buffer_used_ = 0; | |
public: | |
FileHandler(std::string outfile_path) noexcept(false) : outfile_path_(std::move(outfile_path)) | |
@@ -277,6 +283,7 @@ template<eFileType filetype> class FileHandler : NonCopyable, NonMovable { | |
~FileHandler() | |
{ | |
+ flush_buffer(); | |
if (outfile_ && std::fclose(outfile_)) { | |
std::cerr << "Error: could not close the file '" << outfile_path_ | |
<< "' properly, it may be corrupted." << std::endl; | |
@@ -323,17 +330,52 @@ template<eFileType filetype> class FileHandler : NonCopyable, NonMovable { | |
} | |
} | |
+ void flush_buffer() const | |
+ { | |
+ if (buffer_used_ != 0) { | |
+ fwrite(buffer_, 1, buffer_used_, outfile_); | |
+ buffer_used_ = 0; | |
+ } | |
+ } | |
template<int total_args, typename... T> | |
constexpr std::enable_if_t<(total_args != 0), void> write__impl(const char *fmt, | |
T &&...args) const | |
{ | |
- std::fprintf(outfile_, fmt, string_to_primitive(std::forward<T>(args))...); | |
+ size_t space_left = buffer_capacity - buffer_used_; | |
+ /* Flush accumulated output buffer if we're near the end of it. */ | |
+ if (space_left < flush_when_below) { | |
+ flush_buffer(); | |
+ space_left = buffer_capacity; | |
+ } | |
+ int written = std::snprintf(buffer_+buffer_used_, space_left, fmt, string_to_primitive(std::forward<T>(args))...); | |
+ if (written >= 0 && written < space_left) { | |
+ buffer_used_ += written; | |
+ } | |
+ else { | |
+ /* Failed to format the string into available space (e.g. got an extremely long | |
+ * string as an argument). Flush any output so far, and fallback to fprintf. */ | |
+ flush_buffer(); | |
+ std::fprintf(outfile_, fmt, string_to_primitive(std::forward<T>(args))...); | |
+ } | |
} | |
template<int total_args, typename... T> | |
constexpr std::enable_if_t<(total_args == 0), void> write__impl(const char *fmt, | |
T &&...args) const | |
{ | |
- std::fputs(fmt, outfile_); | |
+ size_t len = strlen(fmt); | |
+ if (len >= buffer_capacity) { | |
+ /* Input string longer than output buffer; flush | |
+ * any previous output and directly write to file. */ | |
+ flush_buffer(); | |
+ fwrite(fmt, 1, len, outfile_); | |
+ } | |
+ else { | |
+ size_t space_left = buffer_capacity - buffer_used_; | |
+ if (space_left < len) | |
+ flush_buffer(); | |
+ memcpy(buffer_ + buffer_used_, fmt, len); | |
+ buffer_used_ += len; | |
+ } | |
} | |
}; | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment