mirror of
https://github.com/hrydgard/ppsspp.git
synced 2025-04-02 11:01:50 -04:00
232 lines
5.1 KiB
C++
232 lines
5.1 KiB
C++
#pragma once
|
|
|
|
#include "Common/Log.h"
|
|
#include "Common/Data/Collections/Slice.h"
|
|
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <string_view>
|
|
|
|
// Queue with a dynamic size, optimized for bulk inserts and retrievals - and optimized
|
|
// to be fast in debug builds, hence it's pretty much C internally.
|
|
class CharQueue {
|
|
public:
|
|
explicit CharQueue(size_t blockSize = 16384) : blockSize_(blockSize) {
|
|
head_ = new Block{};
|
|
tail_ = head_;
|
|
head_->data = (char *)malloc(blockSize_);
|
|
head_->size = (int)blockSize_;
|
|
head_->next = nullptr;
|
|
}
|
|
|
|
// Remove copy constructors.
|
|
CharQueue(const CharQueue &) = delete;
|
|
void operator=(const CharQueue &) = delete;
|
|
|
|
// But let's have a move constructor.
|
|
CharQueue(CharQueue &&src) noexcept {
|
|
// Steal the data from the other queue.
|
|
blockSize_ = src.blockSize_;
|
|
head_ = src.head_;
|
|
tail_ = src.tail_;
|
|
// Give the old queue a new block. Could probably also leave it in an invalid state and get rid of it.
|
|
src.head_ = new Block{};
|
|
src.tail_ = src.head_;
|
|
src.head_->data = (char *)malloc(src.blockSize_);
|
|
src.head_->size = (int)src.blockSize_;
|
|
}
|
|
|
|
~CharQueue() {
|
|
clear();
|
|
_dbg_assert_(head_ == tail_);
|
|
_dbg_assert_(head_->size == blockSize_);
|
|
_dbg_assert_(size() == 0);
|
|
// delete the final block
|
|
delete head_;
|
|
}
|
|
|
|
char *push_back_write(size_t size) {
|
|
int remain = tail_->size - tail_->tail;
|
|
_dbg_assert_(remain >= 0);
|
|
if (remain >= (int)size) {
|
|
char *retval = tail_->data + tail_->tail;
|
|
tail_->tail += (int)size;
|
|
return retval;
|
|
} else {
|
|
// Can't fit? Just allocate a new block and fill it up with the new data.
|
|
int bsize = (int)blockSize_;
|
|
if ((int)size > bsize) {
|
|
bsize = (int)size;
|
|
}
|
|
Block *b = new Block{};
|
|
b->head = 0;
|
|
b->tail = (int)size;
|
|
b->size = bsize;
|
|
b->data = (char *)malloc(bsize);
|
|
tail_->next = b;
|
|
tail_ = b;
|
|
return tail_->data;
|
|
}
|
|
}
|
|
|
|
void push_back(const char *data, size_t size) {
|
|
memcpy(push_back_write(size), data, size);
|
|
}
|
|
|
|
void push_back(std::string_view chars) {
|
|
memcpy(push_back_write(chars.size()), chars.data(), chars.size());
|
|
}
|
|
|
|
// For debugging, mainly.
|
|
size_t block_count() const {
|
|
int count = 0;
|
|
Block *b = head_;
|
|
do {
|
|
count++;
|
|
b = b->next;
|
|
} while (b);
|
|
return count;
|
|
}
|
|
|
|
size_t size() const {
|
|
size_t s = 0;
|
|
Block *b = head_;
|
|
do {
|
|
s += b->tail - b->head;
|
|
b = b->next;
|
|
} while (b);
|
|
return s;
|
|
}
|
|
|
|
char peek(size_t peekOff) {
|
|
Block *b = head_;
|
|
do {
|
|
int remain = b->tail - b->head;
|
|
if (remain > (int)peekOff) {
|
|
return b->data[b->head + peekOff];
|
|
} else {
|
|
peekOff -= remain;
|
|
}
|
|
b = b->next;
|
|
} while (b);
|
|
// Ran out of data.
|
|
_dbg_assert_(false);
|
|
return 0;
|
|
}
|
|
|
|
bool empty() const {
|
|
return size() == 0;
|
|
}
|
|
|
|
// Pass in a lambda that takes each partial buffer as char*, size_t.
|
|
template<typename Func>
|
|
bool iterate_blocks(Func callback) const {
|
|
Block *b = head_;
|
|
do {
|
|
if (b->tail > b->head) {
|
|
if (!callback(b->data + b->head, b->tail - b->head)) {
|
|
return false;
|
|
}
|
|
}
|
|
b = b->next;
|
|
} while (b);
|
|
return true;
|
|
}
|
|
|
|
size_t pop_front_bulk(char *dest, size_t size) {
|
|
int popSize = (int)size;
|
|
int writeOff = 0;
|
|
while (popSize > 0) {
|
|
int remain = head_->tail - head_->head;
|
|
int readSize = popSize;
|
|
if (readSize > remain) {
|
|
readSize = remain;
|
|
}
|
|
if (dest) {
|
|
memcpy(dest + writeOff, head_->data + head_->head, readSize);
|
|
}
|
|
writeOff += readSize;
|
|
head_->head += readSize;
|
|
popSize -= readSize;
|
|
if (head_->head == head_->tail) {
|
|
// Ran out of data in this block. Let's hope there's more...
|
|
if (head_ == tail_) {
|
|
// Can't read any more, bail.
|
|
break;
|
|
}
|
|
Block *next = head_->next;
|
|
delete head_;
|
|
head_ = next;
|
|
}
|
|
}
|
|
return (int)size - popSize;
|
|
}
|
|
|
|
size_t skip(size_t size) {
|
|
return pop_front_bulk(nullptr, size);
|
|
}
|
|
|
|
void clear() {
|
|
Block *b = head_;
|
|
// Delete all blocks except the last.
|
|
while (b != tail_) {
|
|
Block *next = b->next;
|
|
delete b;
|
|
b = next;
|
|
}
|
|
if (b->size != blockSize_) {
|
|
// Restore the remaining block to default size.
|
|
free(b->data);
|
|
b->data = (char *)malloc(blockSize_);
|
|
b->size = (int)blockSize_;
|
|
}
|
|
b->head = 0;
|
|
b->tail = 0;
|
|
// head and tail are now equal.
|
|
head_ = b;
|
|
}
|
|
|
|
// If return value is negative, one wasn't found.
|
|
int next_crlf_offset() {
|
|
int offset = 0;
|
|
Block *b = head_;
|
|
do {
|
|
int remain = b->tail - b->head;
|
|
for (int i = 0; i < remain; i++) {
|
|
if (b->data[b->head + i] == '\r') {
|
|
// Use peek to avoid handling edge cases.
|
|
if (peek(offset + i + 1) == '\n') {
|
|
return offset + i;
|
|
}
|
|
}
|
|
}
|
|
offset += remain;
|
|
b = b->next;
|
|
} while (b);
|
|
// Ran out of data.
|
|
return -1;
|
|
}
|
|
|
|
private:
|
|
struct Block {
|
|
~Block() {
|
|
if (data) {
|
|
free(data);
|
|
data = 0;
|
|
}
|
|
size = 0;
|
|
}
|
|
Block *next;
|
|
char *data;
|
|
int size; // Can be bigger than the default block size if a push is very large.
|
|
// Internal head and tail inside the block.
|
|
int head;
|
|
int tail;
|
|
};
|
|
|
|
// There's always at least one block, initialized in the constructor.
|
|
Block *head_;
|
|
Block *tail_;
|
|
// Default min block size for new blocks.
|
|
size_t blockSize_;
|
|
};
|