diff options
author | scroggo@google.com <scroggo@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81> | 2013-09-26 21:35:39 +0000 |
---|---|---|
committer | scroggo@google.com <scroggo@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81> | 2013-09-26 21:35:39 +0000 |
commit | 83fd2c7c43fea7ea49adc68681e9ed3ed180d568 (patch) | |
tree | a4f8f829b240f9866597d0f7eda7690717316d34 | |
parent | f4f9df4c193620167dc6f202f1b72245f4a260cd (diff) |
Add a buffered SkStream class.
This is used by Android to buffer an input stream which may not
otherwise be able to rewind.
Add a test for the new class.
R=bungeman@google.com, mtklein@google.com, reed@google.com
Review URL: https://codereview.chromium.org/23717055
git-svn-id: http://skia.googlecode.com/svn/trunk@11488 2bbb7eff-a529-9590-31e7-b0007b416f81
-rw-r--r-- | gyp/tests.gyp | 1 | ||||
-rw-r--r-- | gyp/utils.gyp | 2 | ||||
-rw-r--r-- | include/utils/SkFrontBufferedStream.h | 77 | ||||
-rw-r--r-- | src/utils/SkFrontBufferedStream.cpp | 147 | ||||
-rw-r--r-- | tests/FrontBufferedStreamTest.cpp | 160 |
5 files changed, 387 insertions, 0 deletions
diff --git a/gyp/tests.gyp b/gyp/tests.gyp index 8b3ceb8a97..e08a1757bd 100644 --- a/gyp/tests.gyp +++ b/gyp/tests.gyp @@ -62,6 +62,7 @@ '../tests/FontHostTest.cpp', '../tests/FontMgrTest.cpp', '../tests/FontNamesTest.cpp', + '../tests/FrontBufferedStreamTest.cpp', '../tests/GeometryTest.cpp', '../tests/GLInterfaceValidation.cpp', '../tests/GLProgramsTest.cpp', diff --git a/gyp/utils.gyp b/gyp/utils.gyp index d3bac16600..172591095d 100644 --- a/gyp/utils.gyp +++ b/gyp/utils.gyp @@ -34,6 +34,7 @@ '../src/utils/SkThreadPool.cpp', '../include/utils/SkBoundaryPatch.h', + '../include/utils/SkFrontBufferedStream.h', '../include/utils/SkCamera.h', '../include/utils/SkCanvasStateUtils.h', '../include/utils/SkCubicInterval.h', @@ -65,6 +66,7 @@ '../src/utils/SkBitSet.cpp', '../src/utils/SkBitSet.h', '../src/utils/SkBoundaryPatch.cpp', + '../src/utils/SkFrontBufferedStream.cpp', '../src/utils/SkCamera.cpp', '../src/utils/SkCanvasStack.h', '../src/utils/SkCanvasStack.cpp', diff --git a/include/utils/SkFrontBufferedStream.h b/include/utils/SkFrontBufferedStream.h new file mode 100644 index 0000000000..e3eb4dbad1 --- /dev/null +++ b/include/utils/SkFrontBufferedStream.h @@ -0,0 +1,77 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkStream.h" +#include "SkTemplates.h" + +/** + * Specialized stream that only buffers the first X bytes of a stream, + * where X is passed in by the user. Note that unlike some buffered + * stream APIs, once more than those bytes are read, no more buffering + * is done. This stream is designed for a use case where the caller + * knows that rewind will only be called from within X bytes (inclusive), + * and the wrapped stream is not necessarily able to rewind at all. + */ +class SkFrontBufferedStream : public SkStreamRewindable { +public: + /** + * Creates a new stream that wraps and buffers SkStream. + * @param stream SkStream to buffer. If NULL, NULL is returned. After + * this call, unref stream and do not refer to it. + * SkFrontBufferedStream is expected to be its only owner. + * @param bufferSize Exact size of the buffer to be used. + * @return An SkStream that can buffer up to bufferSize. + */ + static SkStreamRewindable* Create(SkStream* stream, size_t bufferSize); + + virtual size_t read(void* buffer, size_t size) SK_OVERRIDE; + + virtual bool isAtEnd() const SK_OVERRIDE; + + virtual bool rewind() SK_OVERRIDE; + + virtual bool hasPosition() const SK_OVERRIDE { return true; } + + virtual size_t getPosition() const SK_OVERRIDE { return fOffset; } + + virtual bool hasLength() const SK_OVERRIDE; + + virtual size_t getLength() const SK_OVERRIDE; + + virtual SkStreamRewindable* duplicate() const SK_OVERRIDE { return NULL; } + +private: + SkAutoTUnref<SkStream> fStream; + // Current offset into the stream. Always >= 0. + size_t fOffset; + // Amount that has been buffered by calls to read. Will always be less than + // fBufferSize. + size_t fBufferedSoFar; + // Total size of the buffer. + const size_t fBufferSize; + SkAutoTMalloc<char> fBuffer; + + // Private. Use Create. + SkFrontBufferedStream(SkStream*, size_t bufferSize); + + // Read up to size bytes from already buffered data, and copy to + // dst, if non-NULL. Updates fOffset. Assumes that fOffset is less + // than fBufferedSoFar. + size_t readFromBuffer(char* dst, size_t size); + + // Buffer up to size bytes from the stream, and copy to dst if non- + // NULL. Updates fOffset and fBufferedSoFar. Assumes that fOffset is + // less than fBufferedSoFar, and size is greater than 0. + size_t bufferAndWriteTo(char* dst, size_t size); + + // Read up to size bytes directly from the stream and into dst if non- + // NULL. Updates fOffset. Assumes fOffset is at or beyond the buffered + // data, and size is greater than 0. + size_t readDirectlyFromStream(char* dst, size_t size); + + typedef SkStream INHERITED; +}; diff --git a/src/utils/SkFrontBufferedStream.cpp b/src/utils/SkFrontBufferedStream.cpp new file mode 100644 index 0000000000..12030da8fb --- /dev/null +++ b/src/utils/SkFrontBufferedStream.cpp @@ -0,0 +1,147 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkFrontBufferedStream.h" + +SkStreamRewindable* SkFrontBufferedStream::Create(SkStream* stream, size_t bufferSize) { + if (NULL == stream) { + return NULL; + } + return SkNEW_ARGS(SkFrontBufferedStream, (stream, bufferSize)); +} + +SkFrontBufferedStream::SkFrontBufferedStream(SkStream* stream, size_t bufferSize) + : fStream(SkRef(stream)) + , fOffset(0) + , fBufferedSoFar(0) + , fBufferSize(bufferSize) + , fBuffer(bufferSize) {} + +bool SkFrontBufferedStream::isAtEnd() const { + if (fOffset < fBufferedSoFar) { + // Even if the underlying stream is at the end, this stream has been + // rewound after buffering, so it is not at the end. + return false; + } + + return fStream->isAtEnd(); +} + +bool SkFrontBufferedStream::rewind() { + // Only allow a rewind if we have not exceeded the buffer. + if (fOffset <= fBufferSize) { + fOffset = 0; + return true; + } + return false; +} + +bool SkFrontBufferedStream::hasLength() const { + return fStream->hasLength(); +} + +size_t SkFrontBufferedStream::getLength() const { + return fStream->getLength(); +} + +size_t SkFrontBufferedStream::readFromBuffer(char* dst, size_t size) { + SkASSERT(fOffset < fBufferedSoFar); + // Some data has already been copied to fBuffer. Read up to the + // lesser of the size requested and the remainder of the buffered + // data. + const size_t bytesToCopy = SkTMin(size, fBufferedSoFar - fOffset); + if (dst != NULL) { + memcpy(dst, fBuffer + fOffset, bytesToCopy); + } + + // Update fOffset to the new position. It is guaranteed to be + // within the buffered data. + fOffset += bytesToCopy; + SkASSERT(fOffset <= fBufferedSoFar); + + return bytesToCopy; +} + +size_t SkFrontBufferedStream::bufferAndWriteTo(char* dst, size_t size) { + SkASSERT(size > 0); + SkASSERT(fOffset >= fBufferedSoFar); + // Data needs to be buffered. Buffer up to the lesser of the size requested + // and the remainder of the max buffer size. + const size_t bytesToBuffer = SkTMin(size, fBufferSize - fBufferedSoFar); + char* buffer = fBuffer + fOffset; + const size_t buffered = fStream->read(buffer, bytesToBuffer); + + fBufferedSoFar += buffered; + fOffset = fBufferedSoFar; + SkASSERT(fBufferedSoFar <= fBufferSize); + + // Copy the buffer to the destination buffer and update the amount read. + if (dst != NULL) { + memcpy(dst, buffer, buffered); + } + + return buffered; +} + +size_t SkFrontBufferedStream::readDirectlyFromStream(char* dst, size_t size) { + SkASSERT(size > 0); + // If we get here, we have buffered all that can be buffered. + SkASSERT(fBufferSize == fBufferedSoFar && fOffset >= fBufferSize); + + const size_t bytesReadDirectly = fStream->read(dst, size); + fOffset += bytesReadDirectly; + + // If we have read past the end of the buffer, rewinding is no longer + // supported, so we can go ahead and free the memory. + if (bytesReadDirectly > 0) { + fBuffer.reset(0); + } + + return bytesReadDirectly; +} + +size_t SkFrontBufferedStream::read(void* voidDst, size_t size) { + // Cast voidDst to a char* for easy addition. + char* dst = reinterpret_cast<char*>(voidDst); + SkDEBUGCODE(const size_t totalSize = size;) + const size_t start = fOffset; + + // First, read any data that was previously buffered. + if (fOffset < fBufferedSoFar) { + const size_t bytesCopied = this->readFromBuffer(dst, size); + + // Update the remaining number of bytes needed to read + // and the destination buffer. + size -= bytesCopied; + SkASSERT(size + (fOffset - start) == totalSize); + if (dst != NULL) { + dst += bytesCopied; + } + } + + // Buffer any more data that should be buffered, and copy it to the + // destination. + if (size > 0 && fBufferedSoFar < fBufferSize) { + const size_t buffered = this->bufferAndWriteTo(dst, size); + + // Update the remaining number of bytes needed to read + // and the destination buffer. + size -= buffered; + SkASSERT(size + (fOffset - start) == totalSize); + if (dst != NULL) { + dst += buffered; + } + } + + if (size > 0 && !fStream->isAtEnd()) { + const size_t bytesReadDirectly = this->readDirectlyFromStream(dst, size); + SkDEBUGCODE(size -= bytesReadDirectly;) + SkASSERT(size + (fOffset - start) == totalSize); + } + + return fOffset - start; +} diff --git a/tests/FrontBufferedStreamTest.cpp b/tests/FrontBufferedStreamTest.cpp new file mode 100644 index 0000000000..5f8544e199 --- /dev/null +++ b/tests/FrontBufferedStreamTest.cpp @@ -0,0 +1,160 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkFrontBufferedStream.h" +#include "SkRefCnt.h" +#include "SkTypes.h" +#include "Test.h" + +static void test_read(skiatest::Reporter* reporter, SkStream* bufferedStream, + const void* expectations, size_t bytesToRead) { + // output for reading bufferedStream. + SkAutoMalloc storage(bytesToRead); + + size_t bytesRead = bufferedStream->read(storage.get(), bytesToRead); + REPORTER_ASSERT(reporter, bytesRead == bytesToRead || bufferedStream->isAtEnd()); + REPORTER_ASSERT(reporter, memcmp(storage.get(), expectations, bytesRead) == 0); +} + +static void test_rewind(skiatest::Reporter* reporter, + SkStream* bufferedStream, bool shouldSucceed) { + const bool success = bufferedStream->rewind(); + REPORTER_ASSERT(reporter, success == shouldSucceed); +} + +// All tests will buffer this string, and compare output to the original. +// The string is long to ensure that all of our lengths being tested are +// smaller than the string length. +const char gAbcs[] = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx"; + +// Tests reading the stream across boundaries of what has been buffered so far and what +// the total buffer size is. +static void test_incremental_buffering(skiatest::Reporter* reporter, size_t bufferSize) { + SkMemoryStream memStream(gAbcs, strlen(gAbcs), false); + + SkAutoTUnref<SkStream> bufferedStream(SkFrontBufferedStream::Create(&memStream, bufferSize)); + + // First, test reading less than the max buffer size. + test_read(reporter, bufferedStream, gAbcs, bufferSize / 2); + + // Now test rewinding back to the beginning and reading less than what was + // already buffered. + test_rewind(reporter, bufferedStream, true); + test_read(reporter, bufferedStream, gAbcs, bufferSize / 4); + + // Now test reading part of what was buffered, and buffering new data. + test_read(reporter, bufferedStream, gAbcs + bufferedStream->getPosition(), bufferSize / 2); + + // Now test reading what was buffered, buffering new data, and + // reading directly from the stream. + test_rewind(reporter, bufferedStream, true); + test_read(reporter, bufferedStream, gAbcs, bufferSize << 1); + + // We have reached the end of the buffer, so rewinding will fail. + // This test assumes that the stream is larger than the buffer; otherwise the + // result of rewind should be true. + test_rewind(reporter, bufferedStream, false); +} + +static void test_perfectly_sized_buffer(skiatest::Reporter* reporter, size_t bufferSize) { + SkMemoryStream memStream(gAbcs, strlen(gAbcs), false); + SkAutoTUnref<SkStream> bufferedStream(SkFrontBufferedStream::Create(&memStream, bufferSize)); + + // Read exactly the amount that fits in the buffer. + test_read(reporter, bufferedStream, gAbcs, bufferSize); + + // Rewinding should succeed. + test_rewind(reporter, bufferedStream, true); + + // Once again reading buffered info should succeed + test_read(reporter, bufferedStream, gAbcs, bufferSize); + + // Read past the size of the buffer. At this point, we cannot return. + test_read(reporter, bufferedStream, gAbcs + bufferedStream->getPosition(), 1); + test_rewind(reporter, bufferedStream, false); +} + +static void test_skipping(skiatest::Reporter* reporter, size_t bufferSize) { + SkMemoryStream memStream(gAbcs, strlen(gAbcs), false); + SkAutoTUnref<SkStream> bufferedStream(SkFrontBufferedStream::Create(&memStream, bufferSize)); + + // Skip half the buffer. + bufferedStream->skip(bufferSize / 2); + + // Rewind, then read part of the buffer, which should have been read. + test_rewind(reporter, bufferedStream, true); + test_read(reporter, bufferedStream, gAbcs, bufferSize / 4); + + // Now skip beyond the buffered piece, but still within the total buffer. + bufferedStream->skip(bufferSize / 2); + + // Test that reading will still work. + test_read(reporter, bufferedStream, gAbcs + bufferedStream->getPosition(), bufferSize / 4); + + test_rewind(reporter, bufferedStream, true); + test_read(reporter, bufferedStream, gAbcs, bufferSize); +} + +// A custom class whose isAtEnd behaves the way Android's stream does - since it is an adaptor to a +// Java InputStream, it does not know that it is at the end until it has attempted to read beyond +// the end and failed. Used by test_read_beyond_buffer. +class AndroidLikeMemoryStream : public SkMemoryStream { +public: + AndroidLikeMemoryStream(void* data, size_t size, bool ownMemory) + : INHERITED(data, size, ownMemory) + , fIsAtEnd(false) {} + + size_t read(void* dst, size_t requested) SK_OVERRIDE { + size_t bytesRead = this->INHERITED::read(dst, requested); + if (bytesRead < requested) { + fIsAtEnd = true; + } + return bytesRead; + } + + bool isAtEnd() const SK_OVERRIDE { + return fIsAtEnd; + } + +private: + bool fIsAtEnd; + typedef SkMemoryStream INHERITED; +}; + +// This test ensures that buffering the exact length of the stream and attempting to read beyond it +// does not invalidate the buffer. +static void test_read_beyond_buffer(skiatest::Reporter* reporter, size_t bufferSize) { + // Use a stream that behaves like Android's stream. + AndroidLikeMemoryStream memStream((void*)gAbcs, bufferSize, false); + + // Create a buffer that matches the length of the stream. + SkAutoTUnref<SkStream> bufferedStream(SkFrontBufferedStream::Create(&memStream, bufferSize)); + + // Attempt to read one more than the bufferSize + test_read(reporter, bufferedStream.get(), gAbcs, bufferSize + 1); + test_rewind(reporter, bufferedStream.get(), true); + + // Ensure that the initial read did not invalidate the buffer. + test_read(reporter, bufferedStream, gAbcs, bufferSize); +} + +static void test_buffers(skiatest::Reporter* reporter, size_t bufferSize) { + test_incremental_buffering(reporter, bufferSize); + test_perfectly_sized_buffer(reporter, bufferSize); + test_skipping(reporter, bufferSize); + test_read_beyond_buffer(reporter, bufferSize); +} + +static void TestStreams(skiatest::Reporter* reporter) { + // Test 6 and 64, which are used by Android, as well as another arbitrary length. + test_buffers(reporter, 6); + test_buffers(reporter, 15); + test_buffers(reporter, 64); +} + +#include "TestClassDef.h" +DEFINE_TESTCLASS("FrontBufferedStream", FrontBufferedStreamTestClass, TestStreams) |