From 53aa26dec5befea9b34ed01e0aaf87d31f4d0b3f Mon Sep 17 00:00:00 2001 From: Dmitry Marakasov Date: Thu, 18 Dec 2014 16:23:22 +0300 Subject: [PATCH] Make StreamRWops work with istream/ostream Streams do not generally work well with RWops because * streams have separate read and write pointers * ostream doesn't allow you to determine how many bytes were actually written * istream and ostream have separate set of functions Try my best to support streams in RWops though, engaging some template magic: * provide separate template implementations of all operations which depend on whether stream is an istream or ostream. This allows to e.g. return 0 immediately for an attempt to write() to istream. * disallow StreamRWops for classes which are both istream and ostream to not run into ambiguity of separate read/write pointers * for read failure, but partially read object back to the stream to not lose data for following read (not sure that e.g. fread behaves so though; I'll anyway expect user to Seek() after read or write failure) * for write failure, there's no way to avoid leaking partial data to the stream In general, it is best to use this container as read-only. Also add tests for StreamRWops --- SDL2pp/StreamRWops.hh | 123 +++++++++++++++++++++++++++++++----------- tests/test_rwops.cc | 61 +++++++++++++++++++++ 2 files changed, 152 insertions(+), 32 deletions(-) diff --git a/SDL2pp/StreamRWops.hh b/SDL2pp/StreamRWops.hh index 8de7725..aa8ca0e 100644 --- a/SDL2pp/StreamRWops.hh +++ b/SDL2pp/StreamRWops.hh @@ -26,16 +26,97 @@ #include #include +#include namespace SDL2pp { -template +template class StreamRWops : public CustomRWops { + // since STL streams have different pointers for reading and writing, + // supporting both at the same time is impossible + static_assert(!(std::is_base_of::value && std::is_base_of::value), "StreamRWops does not support reading and writing at the same time"); + protected: S& stream_; +private: + template + typename std::enable_if::value && !std::is_base_of::value, void>::type SeekHelper(off_t off, std::ios_base::seekdir dir) { + stream_.seekg(off, dir); + } + + template + typename std::enable_if::value && std::is_base_of::value, void>::type SeekHelper(off_t off, std::ios_base::seekdir dir) { + stream_.seekp(off, dir); + } + + template + typename std::enable_if::value && !std::is_base_of::value, off_t>::type TellHelper() { + return stream_.tellg(); + } + + template + typename std::enable_if::value && std::is_base_of::value, off_t>::type TellHelper() { + return stream_.tellp(); + } + + template + typename std::enable_if::value, size_t>::type ReadHelper(void* ptr, size_t size, size_t maxnum) { + stream_.read(static_cast(ptr), size * maxnum); + size_t nread = stream_.gcount(); + + // eof is OK + if (stream_.rdstate() == (std::ios_base::eofbit | std::ios_base::failbit)) + stream_.clear(); + + if (nread != size * maxnum) { + // short read + char* pos = static_cast(ptr); + pos += nread; + + int count = nread % size; + + // put partially read object back into the stream + while (--count >= 0) + stream_.putback(*--pos); + } + + return nread / size; + } + + template + typename std::enable_if::value, size_t>::type ReadHelper(void*, size_t, size_t) { + return 0; + } + + template + typename std::enable_if::value, size_t>::type WriteHelper(const void* ptr, size_t size, size_t maxnum) { + stream_.write(static_cast(ptr), size * maxnum); + // XXX: there seem to be no reliable way to tell how much + // was actually written + if (stream_.rdstate() & std::ios_base::badbit) + return 0; + return maxnum; + } + + template + typename std::enable_if::value, size_t>::type WriteHelper(const void*, size_t, size_t) { + return 0; + } + + template + typename std::enable_if::value, int>::type CloseHelper() { + stream_.flush(); + return stream_.rdstate() & std::ios_base::badbit; + } + + template + typename std::enable_if::value, int>::type CloseHelper() { + return 0; + } + public: - StreamRWops(const S& stream) : stream_(stream) { + StreamRWops(S& stream) : stream_(stream) { } StreamRWops(const StreamRWops&) = default; @@ -46,52 +127,30 @@ public: virtual Sint64 Seek(Sint64 offset, int whence) override { switch (whence) { case RW_SEEK_SET: - stream_.seekg(offset, std::ios_base::beg); + SeekHelper(offset, std::ios_base::beg); break; case RW_SEEK_CUR: - stream_.seekg(offset, std::ios_base::cur); + SeekHelper(offset, std::ios_base::cur); break; case RW_SEEK_END: - stream_.seekg(offset, std::ios_base::end); + SeekHelper(offset, std::ios_base::end); break; default: - throw Exception("Unexpected whence value for StreamRWops::Seek"); + throw std::logic_error("Unexpected whence value for StreamRWops::Seek"); } - return stream_.tellg(); + return TellHelper(); } virtual size_t Read(void* ptr, size_t size, size_t maxnum) override { - stream_.read(ptr, size * maxnum); - size_t nread = stream_.gcount(); - - if (nread != size * maxnum) { - // short read - unsigned char* pos = static_cast(ptr); - pos += nread + 1; - - int count = nread % size; - - // put partially read object back into the stream - while (--count >= 0) - stream_.putback(*--pos); - - stream_.seekg(-count, std::ios_base::cur); - } - return nread / size; + return ReadHelper(ptr, size, maxnum); } virtual size_t Write(const void* ptr, size_t size, size_t maxnum) override { - stream_.write(ptr, size * maxnum); - // XXX: there seem to be no reliable way to tell how much - // was actually written - if (stream_.restate() & std::ios_base::badbit) - return 0; - return maxnum; + return WriteHelper(ptr, size, maxnum); } virtual int Close() override { - stream_.flush(); - return stream_.restate() & std::ios_base::badbit; + return CloseHelper(); } }; diff --git a/tests/test_rwops.cc b/tests/test_rwops.cc index bd766df..7ae3858 100644 --- a/tests/test_rwops.cc +++ b/tests/test_rwops.cc @@ -2,6 +2,7 @@ #include #include +#include #include #include "testing.h" @@ -141,6 +142,66 @@ BEGIN_TEST() } } + // Test for StreamRWops + { + { + // write test + + std::stringstream test; + RWops rw((StreamRWops(test))); + + char buf[4] = { 'a', 'b', 'c', 'd' }; + EXPECT_TRUE(rw.Write(buf, 1, 4) == 4); + + EXPECT_TRUE(rw.Seek(0, RW_SEEK_CUR) == 4); + + EXPECT_TRUE(rw.Seek(2, RW_SEEK_SET) == 2); + + EXPECT_TRUE(rw.Write(buf, 1, 4) == 4); + + EXPECT_EQUAL(test.str(), "ababcd"); + } + + { + // read test + + std::stringstream test("abcdef"); + RWops rw((StreamRWops(test))); + + char buf[4]; + EXPECT_EQUAL(rw.Read(buf, 1, 4), 4UL); + + EXPECT_EQUAL(std::string(buf, 4), "abcd"); + + EXPECT_EQUAL(rw.Seek(0, RW_SEEK_CUR), 4); + + EXPECT_EQUAL(rw.Seek(2, RW_SEEK_SET), 2); + + EXPECT_EQUAL(rw.Read(buf, 1, 4), 4UL); + + EXPECT_EQUAL(std::string(buf, 4), "cdef"); + + // short read + EXPECT_EQUAL(rw.Seek(4, RW_SEEK_SET), 4); + + EXPECT_EQUAL(rw.Read(buf, 1, 4), 2UL); + + EXPECT_EQUAL(std::string(buf, 2), "ef"); + + // short object read + EXPECT_EQUAL(rw.Seek(4, RW_SEEK_SET), 4); + + EXPECT_EQUAL(rw.Read(buf, 4, 1), 0UL); + + EXPECT_EQUAL(rw.Seek(0, RW_SEEK_CUR), 4); + + // read end + EXPECT_EQUAL(rw.Read(buf, 1, 2), 2UL); + + EXPECT_EQUAL(std::string(buf, 2), "ef"); + } + } + // SDL file read test { RWops rw = RWops::FromFile(TESTDATA_DIR "/test.txt");