Files
pip/libs/main/serialization/pichunkstream.h
2026-03-12 14:46:57 +03:00

308 lines
13 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*! \file pichunkstream.h
* \ingroup Serialization
* \~\brief
* \~english Binary markup de/serializator stream
* \~russian Бинарный поток для де/сериализации с разметкой
*/
/*
PIP - Platform Independent Primitives
Binary markup serializator
Ivan Pelipenko peri4ko@yandex.ru
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef PICHUNKSTREAM_H
#define PICHUNKSTREAM_H
#include "pibytearray.h"
//! \~\ingroup Serialization
//! \~\brief
//! \~english Class for binary de/serialization.
//! \~russian Класс для бинарной де/сериализации.
//! \~\details
//! \~english PIChunkStream provides a binary markup serialization stream that allows reading and writing structured data chunks with IDs.
//! \~russian PIChunkStream обеспечивает бинарный поток с разметкой для чтения и записи структурированных данных-чанков с ID.
class PIP_EXPORT PIChunkStream {
public:
//! \~english Version of data packing.
//! \~russian Версия хранения данных.
//! \~\details
//! \~english Read-access %PIChunkStream automatic detect version, but write-access %PIChunkStream by default write in new version, be
//! careful!
//! \~russian %PIChunkStream на чтение автоматически определяет версию, но для записи использует по умолчанию новую, осторожно!
enum Version {
Version_1 /*! \~english First, old version \~russian Первая, старая версия */,
Version_2 /*! \~english Second, more optimized version \~russian Вторая, более оптимизированная версия */ = 2,
};
//! \~english Constructs stream for read from "data"
//! \~russian Создает поток на чтение из "data"
PIChunkStream(const PIByteArray & data): version_(Version_2) { setSource(data); }
//! \~english Constructs stream for read or write to/from "data", or empty stream for write if "data" = 0
//! \~russian Создает поток на чтение или запись из/в "data", или пустой поток на запись если "data" = 0
PIChunkStream(PIByteArray * data = 0, Version v = Version_2): version_(v) { setSource(data); }
//! \~english Constructs empty stream for write with version \"v\"
//! \~russian Создает пустой поток на запись с версией \"v\"
PIChunkStream(Version v): version_(v) { setSource(0); }
//! \~english Destructor
//! \~russian Деструктор
~PIChunkStream();
//! \~english Nested template struct for chunk data
//! \~russian Вложенный шаблонный структура для данных чанка
template<typename T>
struct Chunk {
//! \~english Constructor with ID and data
//! \~russian Конструктор с ID и данными
Chunk(int i, const T & d): id(i), data(d) {}
//! \~english Chunk ID
//! \~russian ID чанка
int id;
//! \~english Chunk data
//! \~russian Данные чанка
T data;
};
//! \~english Nested template struct for constant chunk data
//! \~russian Вложенный шаблонный структура для константных данных чанка
template<typename T>
struct ChunkConst {
//! \~english Constructor with ID and data
//! \~russian Конструктор с ID и данными
ChunkConst(int i, const T & d): id(i), data(d) {}
//! \~english Chunk ID
//! \~russian ID чанка
int id;
//! \~english Constant chunk data reference
//! \~russian Константная ссылка на данные чанка
const T & data;
};
//! \~english Returns chunk with ID "id" and value "data" for write to stream
//! \~russian Возвращает чанк с ID "id" и значением "data" для записи в поток
//! \~\sa chunk()
template<typename T>
static ChunkConst<T> chunk(int id, const T & data) {
return ChunkConst<T>(id, data);
}
//! \~english Add to this stream chunk with ID "id" and value "data"
//! \~russian Добавляет в этот поток чанк с ID "id" и значением "data"
//! \~\sa chunk()
template<typename T>
PIChunkStream & add(int id, const T & data) {
*this << ChunkConst<T>(id, data);
return *this;
}
//! \~english
//! Extract %PIByteArray from "stream" and set it current stream.
//! If "read_all" then call \a readAll() after extract.
//! Returns if has data to read.
//! \~russian
//! Извлекает %PIByteArray из "stream" и инициализирует им поток.
//! Если указан "read_all", то вызывает \a readAll() после инициализации.
//! Возвращает если ли данные для чтения.
//! \~\note
//! \~english You should call \a readAll() before using \a getData() or \a get() methods
//! \~russian Перед использованием методов \a getData() или \a get() следует вызвать \a readAll()
template<typename T>
bool extract(PIBinaryStream<T> & stream, bool read_all = false) {
stream >> tmp_data;
if (stream.wasReadError() || tmp_data.size_s() < 4) return false;
data_ = &tmp_data;
_init();
if (read_all) readAll();
return true;
}
//! \~english Sets source buffer for read from "data"
//! \~russian Устанавливает исходный буфер для чтения из "data"
void setSource(const PIByteArray & data);
//! \~english Sets source buffer for read or write to/from "data", or empty stream for write if "data" = 0
//! \~russian Устанавливает исходный буфер для чтения или записи из/в "data", или пустой поток на запись если "data" = 0
void setSource(PIByteArray * data);
//! \~english Returns internal buffer with written data
//! \~russian Возвращает внутренний буфер с записанными данными
PIByteArray data() const;
//! \~english Returns if there is end of stream
//! \~russian Возвращает достигнут ли конец потока
bool atEnd() const { return data_->size_s() <= 1; }
//! \~english Returns stream version
//! \~russian Возвращает версию потока
Version version() const { return (Version)version_; }
//! \~english Reads one chunk from stream and returns its ID
//! \~russian Читает один чанк из потока и возвращает его ID
//! \~\details
//! \~english Reads the next chunk from the stream and stores its ID for subsequent \a getData() or \a get() calls
//! \~russian Читает следующий чанк из потока и сохраняет его ID для последующих вызовов \a getData() или \a get()
int read();
//! \~english Read all chunks from stream. This function just index input data
//! \~russian Читает все чанки из потока. Данный метод лишь индексирует данные
//! \~\details
//! \~english This function indexes input data to allow random access via \a getData(int id) and \a get(int id, T & v)
//! \~russian Эта функция индексирует входные данные для возможности случайного доступа через \a getData(int id) и \a get(int id, T & v)
void readAll();
//! \~english Returns last read chunk ID
//! \~russian Возвращает ID последнего прочитанного чанка
//! \~\details
//! \~english Returns the ID of the last chunk read by \a read() method
//! \~russian Возвращает ID последнего чанка, прочитанного методом \a read()
int getID() { return last_id; }
//! \~english Returns value of last read chunk
//! \~russian Возвращает значение последнего прочитанного чанка
//! \~\note
//! \~english Call \a read() first to read a chunk before using this method
//! \~russian Сначала вызовите \a read() для чтения чанка перед использованием этого метода
template<typename T>
T getData() const {
T ret{};
PIByteArray s(last_data);
s >> ret;
return ret;
}
//! \~english Returns value of chunk with ID \"id\"
//! \~russian Возвращает значение чанка с ID \"id\"
//! \~\note
//! \~english You should call \a readAll() before using this function!
//! \~russian Необходимо вызвать \a readAll() перед использованием этого метода!
template<typename T>
T getData(int id) const {
T ret{};
CacheEntry pos = data_map.value(id);
if (pos.start < 0 || pos.length == 0) return ret;
PIByteArray ba(data_->data(pos.start), pos.length);
if (!ba.isEmpty()) ba >> ret;
return ret;
}
//! \~english Places value of last read chunk into \"v\"
//! \~russian Записывает значение последнего прочитанного чанка в \"v\"
//! \~\note
//! \~english Call \a read() first to read a chunk before using this method
//! \~russian Сначала вызовите \a read() для чтения чанка перед использованием этого метода
template<typename T>
void get(T & v) const {
v = getData<T>();
}
//! \~english Places value of chunk with ID \"id\" into \"v\"
//! \~russian Записывает значение чанка с ID \"id\" в \"v\"
//! \~\note
//! \~english You should call \a readAll() before using this function!
//! \~russian Необходимо вызвать \a readAll() перед использованием этого метода!
template<typename T>
const PIChunkStream & get(int id, T & v) const {
CacheEntry pos = data_map.value(id);
if (pos.start < 0 || pos.length == 0) return *this;
PIByteArray ba(data_->data(pos.start), pos.length);
if (!ba.isEmpty()) ba >> v;
return *this;
}
//! \~english Replaces value of chunk with ID \"id\" to \"v\"
//! \~russian Заменяет значение чанка с ID \"id\" на \"v\"
//! \~\note
//! \~english You should call \a readAll() before using this function!
//! \~russian Необходимо вызвать \a readAll() перед использованием этого метода!
template<typename T>
PIChunkStream & set(int id, const T & v) {
PIByteArray ba;
ba << v;
replaceChunk(id, ba);
return *this;
}
private:
void _init();
struct CacheEntry {
CacheEntry(int s = 0, int l = 0, int b = 0): start(s), length(l), size_bytes(b) {}
int start;
int length;
int size_bytes;
};
static uint readVInt(PIByteArray & s, uchar * bytes = 0);
static void writeVInt(PIByteArray & s, uint val);
static int peekVInt(Version version_, uchar * data_, int sz, uint & ret);
void replaceChunk(int id, const PIByteArray & v);
int last_id;
uchar version_;
mutable PIByteArray *data_, last_data, tmp_data;
PIMap<int, CacheEntry> data_map;
bool first_byte_taken;
template<typename T>
friend PIChunkStream & operator<<(PIChunkStream & s, const PIChunkStream::Chunk<T> & c);
template<typename T>
friend PIChunkStream & operator<<(PIChunkStream & s, const PIChunkStream::ChunkConst<T> & c);
};
//! \~english Stream output operator for Chunk
//! \~russian Оператор вывода в поток для Chunk
template<typename T>
PIChunkStream & operator<<(PIChunkStream & s, const PIChunkStream::Chunk<T> & c) {
PIByteArray ba;
ba << c.data;
switch (s.version_) {
case PIChunkStream::Version_1: (*(s.data_)) << c.id << ba; break;
case PIChunkStream::Version_2:
if (s.data_->isEmpty()) (*(s.data_)) << uchar(uchar(s.version_) | 0x80);
PIChunkStream::writeVInt(*(s.data_), c.id);
PIChunkStream::writeVInt(*(s.data_), ba.size());
s.data_->append(ba);
break;
default: break;
}
return s;
}
//! \~english Stream output operator for ChunkConst
//! \~russian Оператор вывода в поток для ChunkConst
template<typename T>
PIChunkStream & operator<<(PIChunkStream & s, const PIChunkStream::ChunkConst<T> & c) {
PIByteArray ba;
ba << c.data;
switch (s.version_) {
case PIChunkStream::Version_1: (*(s.data_)) << c.id << ba; break;
case PIChunkStream::Version_2:
if (s.data_->isEmpty()) (*(s.data_)) << uchar(uchar(s.version_) | 0x80);
PIChunkStream::writeVInt(*(s.data_), c.id);
PIChunkStream::writeVInt(*(s.data_), ba.size());
s.data_->append(ba);
break;
default: break;
}
return s;
}
#endif // PICHUNKSTREAM_H