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");