/* Ousía Copyright (C) 2014 Benjamin Paaßen, Andreas Stöckel This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include "Buffer.hpp" namespace ousia { namespace utils { /* Class Buffer */ Buffer::Buffer(ReadCallback callback, void *userData) : callback(callback), userData(userData), reachedEnd(false), startBucket(buckets.end()), endBucket(buckets.end()), startOffset(0), firstDead(0) { // Insert a first empty bucket and set the start buffer correctly nextBucket(); startBucket = buckets.begin(); } Buffer::Buffer(const std::string &str) : callback(nullptr), userData(nullptr), reachedEnd(true), startBucket(buckets.end()), endBucket(buckets.end()), startOffset(0), firstDead(0) { // Copy the given string into a first buffer and set the start buffer // correctly Bucket &bucket = nextBucket(); bucket.resize(str.size()); std::copy(str.begin(), str.end(), bucket.begin()); startBucket = buckets.begin(); } void Buffer::advance(BucketIterator &it) { it++; if (it == buckets.end()) { it = buckets.begin(); } } void Buffer::advance(BucketList::const_iterator &it) const { it++; if (it == buckets.cend()) { it = buckets.cbegin(); } } Buffer::Bucket &Buffer::nextBucket() { // Fetch the minimum bucket index size_t minBucketIdx = 0; for (size_t i = 0; i < cursors.size(); i++) { if (alive[i]) { minBucketIdx = std::min(minBucketIdx, cursors[i].bucketIdx); } } // If there is space between the current start bucket and the read // cursor, the start bucket can be safely overridden. if (minBucketIdx > 0) { // All cursor bucket indices will be decreased by one for (size_t i = 0; i < cursors.size(); i++) { cursors[i].bucketIdx--; } // Increment the start offset startOffset += startBucket->size(); // The old start bucket is the new end bucket endBucket = startBucket; // Advance the start bucket, wrap around at the end of the list advance(startBucket); } else { // No free bucket, insert a new one before the start bucket endBucket = buckets.emplace(startBucket); } return *endBucket; } Buffer::CursorId Buffer::nextCursor() { bool hasCursor = false; CursorId res = 0; // Search for the next free cursor starting with minNextCursorId for (size_t i = firstDead; i < alive.size(); i++) { if (!alive[i]) { res = i; hasCursor = true; break; } } // Add a new cursor to the cursor list if no cursor is currently free if (!hasCursor) { res = cursors.size(); cursors.resize(res + 1); alive.resize(res + 1); } // The next dead cursor is at least the next cursor firstDead = res + 1; // Mark the new cursor as alive alive[res] = true; return res; } void Buffer::stream() { // Fetch the bucket into which the data should be inserted, make sure it // has the correct size Bucket &tar = nextBucket(); tar.resize(REQUEST_SIZE); // Read data from the stream into the target buffer size_t size = callback(tar.data(), REQUEST_SIZE, userData); // If not enough bytes were returned, we're at the end of the stream if (size < REQUEST_SIZE) { tar.resize(size); reachedEnd = true; } } Buffer::CursorId Buffer::createCursor() { CursorId res = nextCursor(); cursors[res].bucket = startBucket; cursors[res].bucketIdx = 0; cursors[res].bucketOffs = 0; return res; } Buffer::CursorId Buffer::createCursor(Buffer::CursorId ref) { CursorId res = nextCursor(); cursors[res] = cursors[ref]; return res; } void Buffer::copyCursor(Buffer::CursorId from, Buffer::CursorId to) { cursors[to] = cursors[from]; } void Buffer::deleteCursor(Buffer::CursorId cursor) { alive[cursor] = false; firstDead = std::min(firstDead, cursor); } size_t Buffer::offset(Buffer::CursorId cursor) const { const Cursor &c = cursors[cursor]; size_t offs = startOffset + c.bucketOffs; BucketList::const_iterator it = startBucket; while (it != c.bucket) { offs += it->size(); advance(it); } return offs; } bool Buffer::atEnd(Buffer::CursorId cursor) const { const Cursor &c = cursors[cursor]; return reachedEnd && (c.bucket == endBucket && c.bucketOffs == endBucket->size()); } bool Buffer::read(Buffer::CursorId cursor, char &c) { Cursor &cur = cursors[cursor]; while (true) { // Reference at the current bucket Bucket &bucket = *(cur.bucket); // If there is still data in the current bucket, return this data if (cur.bucketOffs < bucket.size()) { c = bucket[cur.bucketOffs]; cur.bucketOffs++; return true; } else if (cur.bucket == endBucket) { // Return false if the end of the stream has been reached, otherwise // load new data if (reachedEnd) { return false; } stream(); } // Go to the next bucket cur.bucketIdx++; cur.bucketOffs = 0; advance(cur.bucket); } } } }