diff --git a/stream/clients/io_stream.hpp b/stream/clients/io_stream.hpp new file mode 100644 index 000000000..150f84cae --- /dev/null +++ b/stream/clients/io_stream.hpp @@ -0,0 +1,177 @@ +#ifndef MANGLE_STREAM_IOSTREAM_H +#define MANGLE_STREAM_IOSTREAM_H + +#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.) + + 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. + */ + 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; + } + }; + + class MangleIStream : public std::istream + { + IOStreamBuffer buf; + public: + MangleIStream(StreamPtr inp) + : std::istream(&buf) + , buf(inp) + { + assert(inp->isReadable); + } + }; + + class MangleOStream : public std::ostream + { + IOStreamBuffer buf; + public: + MangleOStream(StreamPtr inp) + : std::ostream(&buf) + , buf(inp) + { + assert(inp->isWritable); + } + + ~MangleOStream() { flush(); } + }; + +}} // namespaces +#endif diff --git a/stream/servers/std_ostream.hpp b/stream/servers/std_ostream.hpp index 797622631..f406e1a93 100644 --- a/stream/servers/std_ostream.hpp +++ b/stream/servers/std_ostream.hpp @@ -25,11 +25,7 @@ class StdOStream : public Stream hasPosition = true; hasSize = true; isWritable = true; - } - - size_t read(void*,size_t) - { - assert(0&&"reading not supported by StdOStream"); + isReadable = false; } size_t write(const void* buf, size_t len) diff --git a/stream/stream.hpp b/stream/stream.hpp index aa9c14a9e..2ee4fcbd8 100644 --- a/stream/stream.hpp +++ b/stream/stream.hpp @@ -24,28 +24,38 @@ class Stream bool hasSize; /// If true, write() works. Writing through pointer operations is - /// not supported. + /// not (yet) supported. bool isWritable; + /// If true, read() and eof() works. + bool isReadable; + /// If true, the getPtr() functions work bool hasPtr; - /// Initialize all bools to false by default + /// Initialize all bools to false by default, except isReadable. Stream() : isSeekable(false), hasPosition(false), hasSize(false), - isWritable(false), hasPtr(false) {} + isWritable(false), isReadable(true), hasPtr(false) {} /// Virtual destructor virtual ~Stream() {} /** Read a given number of bytes from the stream. Returns the actual number read. If the return value is less than count, then the - stream is empty or an error occured. + stream is empty or an error occured. Only required for readable + streams. */ - virtual size_t read(void* buf, size_t count) = 0; + virtual size_t read(void* buf, size_t count) { assert(0); return 0; } /** Write a given number of bytes from the stream. Semantics is - similar to read(). Only valid if isWritable is true + similar to read(). Only valid if isWritable is true. + + The returned value is the number of bytes written. However in + most cases, unlike for read(), a write-count less than requested + usually indicates an error. The implementation should throw such + errors as exceptions rather than expect the caller to handle + them. Since most implementations do NOT support writing we default to an assert(0) here. @@ -57,18 +67,20 @@ class Stream /// Seek to an absolute position in this stream. Not all streams are /// seekable. - virtual void seek(size_t pos) = 0; + virtual void seek(size_t pos) { assert(0); } /// Get the current position in the stream. Non-seekable streams are /// not required to keep track of this. - virtual size_t tell() const = 0; + virtual size_t tell() const { assert(0); return 0; } - /// Return the total size of the stream. For streams where this is - /// not applicable, size() should return zero. - virtual size_t size() const = 0; + /// Return the total size of the stream. For streams hasSize is + /// false, size() should fail in some way, since it is an error to + /// call it in those cases. + virtual size_t size() const { assert(0); return 0; } - /// Returns true if the stream is empty - virtual bool eof() const = 0; + /// Returns true if the stream is empty. Required for readable + /// streams. + virtual bool eof() const { assert(0); return 0; } /// Return a pointer to the entire stream. This function (and the /// other getPtr() variants below) should only be implemented for diff --git a/stream/tests/Makefile b/stream/tests/Makefile index e64b90076..e4e32d115 100644 --- a/stream/tests/Makefile +++ b/stream/tests/Makefile @@ -1,6 +1,6 @@ -GCC=g++ -I../ -Wall +GCC=g++ -I../ -Wall -Werror -all: ogre_client_test audiere_client_test memory_server_test buffer_filter_test file_server_test slice_filter_test file_write_test +all: ogre_client_test audiere_client_test memory_server_test buffer_filter_test file_server_test slice_filter_test file_write_test iostream_test I_OGRE=$(shell pkg-config --cflags OGRE) L_OGRE=$(shell pkg-config --libs OGRE) @@ -12,6 +12,9 @@ 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 $@ + 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 new file mode 100644 index 000000000..7304e82fc --- /dev/null +++ b/stream/tests/iostream_test.cpp @@ -0,0 +1,132 @@ +#include +#include "../clients/io_stream.hpp" +#include "../servers/memory_stream.hpp" + +using namespace Mangle::Stream; +using namespace std; + +void test1() +{ + cout << "Testing ASCII reading from memory:\n"; + StreamPtr input(new MemoryStream("hello you world you", 19)); + MangleIStream inp(input); + + string str; + while(!inp.eof()) + { + inp >> str; + cout << "Got: " << str << endl; + } +} + +class Dummy : public Stream +{ + int count; + +public: + + Dummy() : count(0) + { + } + + size_t read(void *ptr, size_t num) + { + char *p = (char*)ptr; + char *start = p; + for(; (count < 2560) && (p-start < (int)num); count++) + { + *p = count / 10; + p++; + } + return p-start; + } + + bool eof() const { return count == 2560; } +}; + +void test2() +{ + cout << "Testing binary reading from non-memory:\n"; + + StreamPtr input(new Dummy); + MangleIStream inp(input); + + int x = 0; + while(!inp.eof()) + { + unsigned char buf[5]; + inp.read((char*)buf,5); + + // istream doesn't set eof() until we read _beyond_ the end of + // the stream, so we need an extra check. + if(inp.gcount() == 0) break; + + /* + for(int i=0;i<5;i++) + cout << (int)buf[i] << " "; + cout << endl; + */ + + assert(buf[4] == buf[0]); + assert(buf[0] == x/2); + x++; + } + cout << " Done\n"; +} + +struct Dummy2 : Stream +{ + Dummy2() + { + isWritable = true; + isReadable = false; + } + + size_t write(const void *ptr, size_t num) + { + const char *p = (const char*)ptr; + cout << " Got: "; + for(unsigned i=0;iwrite("testing", 7); + + cout << " Running through MangleOStream:\n"; + MangleOStream out(output); + out << "hello"; + out << " - are you ok?"; + cout << " Flushing:\n"; + out.flush(); + + cout << " Writing a hell of a lot of characters:\n"; + for(int i=0; i<127; i++) + out << "xxxxxxxx"; // 127 * 8 = 1016 + out << "fffffff"; // +7 = 1023 + cout << " Just one more:\n"; + out << "y"; + cout << " And oooone more:\n"; + out << "z"; + + cout << " Flushing again:\n"; + out.flush(); + cout << " Writing some more and exiting:\n"; + out << "blah bleh blob"; +} + +int main() +{ + test1(); + test2(); + test3(); + return 0; +} diff --git a/stream/tests/output/iostream_test.out b/stream/tests/output/iostream_test.out new file mode 100644 index 000000000..9c28cade3 --- /dev/null +++ b/stream/tests/output/iostream_test.out @@ -0,0 +1,21 @@ +Testing ASCII reading from memory: +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 + Running through MangleOStream: + Flushing: + Got: h e l l o - a r e y o u o k ? + Writing a hell of a lot of characters: + Just one more: + And oooone more: + Got: x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x f f f f f f f y + Flushing again: + Got: z + Writing some more and exiting: + Got: b l a h b l e h b l o b