diff --git a/stream/clients/io_stream.cpp b/stream/clients/io_stream.cpp new file mode 100644 index 0000000000..5f1edc2217 --- /dev/null +++ b/stream/clients/io_stream.cpp @@ -0,0 +1,221 @@ +#include "io_stream.hpp" + +// This seems to work +#ifndef EOF +#define EOF -1 +#endif + +using namespace Mangle::Stream; + +#define BSIZE 1024 + +// Streambuf for normal stream reading +class _istreambuf : public std::streambuf +{ + StreamPtr client; + char buf[BSIZE]; + +public: + _istreambuf(StreamPtr strm) : client(strm) + { + // Make sure we picked the right class + assert(client->isReadable); + assert(!client->hasPtr); + + // Tell streambuf to delegate reading operations to underflow() + setg(NULL,NULL,NULL); + + // Disallow writing + setp(NULL,NULL); + } + + /* Underflow is called when there is no more info to read in the + input buffer. We need to refill buf with new data (if any), and + set up the internal pointers with setg() to reflect the new + state. + */ + int underflow() + { + // Read some more data + size_t read = client->read(buf, BSIZE); + assert(read <= BSIZE); + + // If we're out of data, then EOF + if(read == 0) + return EOF; + + // Otherwise, set up input buffer + setg(buf, buf, buf+read); + + // Return the first char + return *((unsigned char*)buf); + } + + // Seek stream, if the source supports it. Ignores the second + // parameter as Mangle doesn't separate input and output pointers. + std::streampos seekpos(std::streampos pos, std::ios_base::openmode = std::ios_base::in) + { + // Does this stream know how to seek? + if(!client->isSeekable || !client->hasPosition) + // If not, signal an error. + return -1; + + // Set stream position and reset the buffer. + client->seek(pos); + setg(NULL,NULL,NULL); + + return client->tell(); + } +}; + +// Streambuf optimized for pointer-based input streams +class _ptrstreambuf : public std::streambuf +{ + StreamPtr client; + +public: + _ptrstreambuf(StreamPtr strm) : client(strm) + { + // Make sure we picked the right class + assert(client->isReadable); + assert(client->hasPtr); + + // seekpos() does all the work + seekpos(0); + } + + // Underflow is only called when we're at the end of the file + int underflow() { return EOF; } + + // Seek to a new position within the memory stream. This bypasses + // client->seek() entirely so isSeekable doesn't have to be set. + std::streampos seekpos(std::streampos pos, std::ios_base::openmode = std::ios_base::in) + { + // All pointer streams need a size + assert(client->hasSize); + + // Figure out how much will be left of the stream after seeking + size_t size = client->size() - pos; + + // Get a pointer + char* ptr = (char*)client->getPtr(pos,size); + + // And use it + setg(ptr,ptr,ptr+size); + + return pos; + } +}; + +// Streambuf for stream writing +class _ostreambuf : public std::streambuf +{ + StreamPtr client; + char buf[BSIZE]; + +public: + _ostreambuf(StreamPtr strm) : client(strm) + { + // Make sure we picked the right class + assert(client->isWritable); + + // Inform streambuf about our nice buffer + setp(buf, buf+BSIZE); + + // Disallow reading + setg(NULL,NULL,NULL); + } + + /* Sync means to flush (write) all current data to the output + stream. It will also set up the entire output buffer to be usable + again. + */ + int sync() + { + // Get the number of bytes that streambuf wants us to write + int num = pptr() - pbase(); + assert(num >= 0); + + // Is there any work to do? + if(num == 0) return 0; + + if((int)client->write(pbase(), num) != num) + // Inform caller that writing failed + return -1; + + // Reset output buffer pointers + setp(buf, buf+BSIZE); + + // No error + return 0; + } + + /* Called whenever the output buffer is full. + */ + int overflow(int c) + { + // First, write all existing data + if(sync()) return EOF; + + // Put the requested character in the next round of output + if(c != EOF) + { + *pptr() = c; + pbump(1); + } + + // No error + return 0; + } + + // Seek stream, if the source supports it. + std::streampos seekpos(std::streampos pos, std::ios_base::openmode = std::ios_base::out) + { + if(!client->isSeekable || !client->hasPosition) + return -1; + + // Flush data and reset buffers + sync(); + + // Set stream position + client->seek(pos); + + return client->tell(); + } +}; + +MangleIStream::MangleIStream(StreamPtr inp) + : std::istream(NULL) +{ + assert(inp->isReadable); + + // Pick the right streambuf implementation based on whether the + // input supports pointers or not. + if(inp->hasPtr) + buf = new _ptrstreambuf(inp); + else + buf = new _istreambuf(inp); + + rdbuf(buf); +} + +MangleIStream::~MangleIStream() +{ + delete buf; +} + +MangleOStream::MangleOStream(StreamPtr out) + : std::ostream(NULL) +{ + assert(out->isWritable); + buf = new _ostreambuf(out); + + rdbuf(buf); +} + +MangleOStream::~MangleOStream() +{ + // Make sure we don't have lingering data on exit + flush(); + delete buf; +} diff --git a/stream/clients/io_stream.hpp b/stream/clients/io_stream.hpp index 150f84cae2..98c6252ed2 100644 --- a/stream/clients/io_stream.hpp +++ b/stream/clients/io_stream.hpp @@ -4,173 +4,39 @@ #include #include "../stream.hpp" #include -#include - -// This seems to work (TODO: test) -#ifndef EOF -#define EOF -1 -#endif namespace Mangle { namespace Stream { /** This file contains classes for wrapping an std::istream or - std::ostream around a Mangle::Stream. Not to be confused with - servers/std_(o)stream.hpp, which do the opposite (wrap a - Mangle::Stream around an std::istream/ostream.) + std::ostream around a Mangle::Stream. This allows you to use Mangle streams in places that require std - streams. The std::iostream interface is horrible and NOT - designed for easy subclassing. Defining your custom streams as - Mangle streams and then wrapping them here will usually be much - easier. + streams. + + This is much easier than trying to make your own custom streams + into iostreams. The std::iostream interface is horrible and NOT + designed for easy subclassing. Create a Mangle::Stream instead, + and use this wrapper. */ - class IOStreamBuffer : public std::streambuf - { - StreamPtr client; - std::vector ibuf, obuf; - - public: - IOStreamBuffer(StreamPtr strm) : client(strm) - { - // Set up input buffer - setg(NULL,NULL,NULL); - if(client->isReadable) - { - if(client->hasPtr) - { - assert(client->hasSize); - - // If the client supports direct pointer reading, then - // this is really easy. No internal buffer is needed. - char *ptr = (char*) client->getPtr(); - - // Set up the entire file as the input buffer - setg(ptr,ptr,ptr+client->size()); - } - else - { - // We need to do some manual slave labor. Set up an - // empty input buffer and let underflow() handle it. - ibuf.resize(1024); - } - } - - // Only create output buffer if the stream is writable - if(client->isWritable) - { - obuf.resize(1024); - /* Set beginning and end pointers, tells streambuf to write - to this area and call overflow() when it's full. - - Several examples use size-1, but the documentation for - streambuf clearly states that the end pointers is just - _past_ the last accessible position. - */ - char *beg = &obuf[0]; - setp(beg, beg+obuf.size()); - } - else - // Writing not permitted - setp(NULL, NULL); - } - - /* Underflow is called when there is no more info to read in the - input buffer. We need to refill ibuf with data (if any), and - set up the internal pointers with setg() to reflect the new - state. - */ - int underflow() - { - assert(client->isReadable); - - // If we've exhausted a pointer stream, then there's no more to - // be had. - if(client->hasPtr) - return EOF; - - // Read some more data - assert(ibuf.size()); - char *iptr = &ibuf[0]; - size_t read = client->read(iptr, ibuf.size()); - - // If we're out of data, then EOF - if(read == 0) - return EOF; - - // Otherwise, set up input buffer - setg(iptr, iptr, iptr+read); - - // Return the first char - return *((unsigned char*)iptr); - } - - /* Sync means to flush (write) all current data to the output - stream. It will also set up the entire output buffer to be - usable again. - */ - int sync() - { - assert(client->isWritable); - assert(obuf.size() > 0); - - // Get the number of bytes that streambuf wants us to write - int num = pptr() - pbase(); - assert(num >= 0); - - // Nothing to do - if(num == 0) return 0; - - if((int)client->write(pbase(), num) != num) - return -1; - - // Reset output buffer pointers - char *beg = &obuf[0]; - setp(beg, beg+obuf.size()); - - return 0; - } - - int overflow(int c=EOF) - { - // First, write all existing data - if(sync()) return EOF; - - // Put the requested character in the output - if(c != EOF) - { - *pptr() = c; - pbump(1); - } - - return 0; - } - }; + // An istream wrapping a readable Mangle::Stream. Has extra + // optimizations for pointer-based streams. class MangleIStream : public std::istream { - IOStreamBuffer buf; + std::streambuf *buf; public: - MangleIStream(StreamPtr inp) - : std::istream(&buf) - , buf(inp) - { - assert(inp->isReadable); - } + MangleIStream(StreamPtr inp); + ~MangleIStream(); }; + // An ostream wrapping a writable Mangle::Stream. class MangleOStream : public std::ostream { - IOStreamBuffer buf; + std::streambuf *buf; public: - MangleOStream(StreamPtr inp) - : std::ostream(&buf) - , buf(inp) - { - assert(inp->isWritable); - } - - ~MangleOStream() { flush(); } + MangleOStream(StreamPtr inp); + ~MangleOStream(); }; }} // namespaces diff --git a/stream/tests/Makefile b/stream/tests/Makefile index e4e32d1156..5f43978190 100644 --- a/stream/tests/Makefile +++ b/stream/tests/Makefile @@ -12,8 +12,8 @@ ogre_client_test: ogre_client_test.cpp ../stream.hpp ../clients/ogre_datastream. audiere_client_test: audiere_client_test.cpp ../stream.hpp ../clients/audiere_file.hpp ../clients/audiere_file.cpp $(GCC) $< -o $@ ../clients/audiere_file.cpp $(L_AUDIERE) -iostream_test: iostream_test.cpp ../clients/io_stream.hpp - $(GCC) $< -o $@ +iostream_test: iostream_test.cpp ../clients/io_stream.cpp + $(GCC) $^ -o $@ file_server_test: file_server_test.cpp ../stream.hpp ../servers/file_stream.hpp ../servers/std_stream.hpp $(GCC) $< -o $@ diff --git a/stream/tests/iostream_test.cpp b/stream/tests/iostream_test.cpp index 7304e82fc7..6acebbbd61 100644 --- a/stream/tests/iostream_test.cpp +++ b/stream/tests/iostream_test.cpp @@ -46,7 +46,7 @@ public: void test2() { - cout << "Testing binary reading from non-memory:\n"; + cout << "\nTesting binary reading from non-memory:\n"; StreamPtr input(new Dummy); MangleIStream inp(input); @@ -95,7 +95,7 @@ struct Dummy2 : Stream void test3() { - cout << "Writing to dummy stream:\n"; + cout << "\nWriting to dummy stream:\n"; cout << " Pure dummy test:\n"; StreamPtr output(new Dummy2); @@ -123,10 +123,54 @@ void test3() out << "blah bleh blob"; } +struct Dummy3 : Stream +{ + int pos; + + Dummy3() : pos(0) + { + hasPosition = true; + isSeekable = true; + } + + size_t read(void*, size_t num) + { + cout << " Reading " << num << " bytes from " << pos << endl; + pos += num; + return num; + } + + void seek(size_t npos) { pos = npos; } + size_t tell() const { return pos; } +}; + +void test4() +{ + cout << "\nTesting seeking;\n"; + StreamPtr input(new Dummy3); + + cout << " Direct reading:\n"; + input->read(0,10); + input->read(0,5); + + MangleIStream inp(input); + + cout << " Reading from istream:\n"; + char buf[20]; + inp.read(buf, 20); + inp.read(buf, 20); + inp.read(buf, 20); + + cout << " Seeking to 30 and reading again:\n"; + inp.seekg(30); + inp.read(buf, 20); +} + int main() { test1(); test2(); test3(); + test4(); return 0; } diff --git a/stream/tests/output/iostream_test.out b/stream/tests/output/iostream_test.out index 9c28cade3f..b6da80c80d 100644 --- a/stream/tests/output/iostream_test.out +++ b/stream/tests/output/iostream_test.out @@ -3,8 +3,10 @@ Got: hello Got: you Got: world Got: you + Testing binary reading from non-memory: Done + Writing to dummy stream: Pure dummy test: Got: t e s t i n g @@ -19,3 +21,12 @@ Writing to dummy stream: Got: z Writing some more and exiting: Got: b l a h b l e h b l o b + +Testing seeking; + Direct reading: + Reading 10 bytes from 0 + Reading 5 bytes from 10 + Reading from istream: + Reading 1024 bytes from 15 + Seeking to 30 and reading again: + Reading 1024 bytes from 30