Skip to content

Instantly share code, notes, and snippets.

@aras-p
Last active January 21, 2022 08:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aras-p/1421266aedd029884972aed602c62fae to your computer and use it in GitHub Desktop.
Save aras-p/1421266aedd029884972aed602c62fae to your computer and use it in GitHub Desktop.
Blender 3.1 obj_export_io.hh output buffering
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