Files
renderdoc/renderdoc/serialise/serialiser.h
T
baldurk 2484bc8bc7 Use UTF-8 everywhere possible and only use wchar_t where required.
* This means that all APIs pass byte string types. ALL strings everywhere
  in the entire codebase must be assumed to be and treated as UTF-8 content
  not ASCII.
* Gets rid of all the horrible %hs specifiers that caused warnings on
  linux! Hooray.
* We convert to wide strings, or use wide characters, only when necessary
  to use the Win32 API. Some windows specific code will stay in wide chars
  just for convenience.
* Files are already serialised as UTF-8 strings for linux/windows binary
  compatibility, so this change doesn't break backwards compatibility.
2014-11-23 14:45:16 +00:00

864 lines
24 KiB
C++

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2014 Crytek
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#pragma once
#include "common/common.h"
#include "os/os_specific.h"
#include "api/replay/basic_types.h"
#include "replay/type_helpers.h"
#include <stdint.h>
#include <string.h>
#include <string>
#include <vector>
#include <list>
#include <utility>
#include <set>
using std::set;
using std::string;
// template helpers
template <class T>
struct is_pointer
{
enum {value = false};
};
template <class T>
struct is_pointer<T *>
{
enum {value = true};
};
template <class T>
struct is_pointer<const T *>
{
enum {value = true};
};
template<bool isptr, class T>
struct ToStrHelper
{
static string Get(const T &el);
};
struct ToStr
{
template<class T>
static string Get(const T &el)
{
return ToStrHelper<is_pointer<T>::value, T>::Get(el);
}
};
typedef const char *(*ChunkLookup)(uint32_t chunkType);
class Serialiser;
class ScopedContext;
// holds the memory, length and type for a given chunk, so that it can be
// passed around and moved between owners before being serialised out
class Chunk
{
public:
~Chunk();
const char *GetDebugString() { return m_DebugStr.c_str(); }
byte *GetData() { return m_Data; }
uint32_t GetLength() { return m_Length; }
uint32_t GetChunkType() { return m_ChunkType; }
bool IsAligned() { return m_AlignedData; }
bool IsTemporary() { return m_Temporary; }
#if !defined(RELEASE)
static uint64_t NumLiveChunks() { return m_LiveChunks; }
static uint64_t TotalMem() { return m_TotalMem; }
#else
static uint64_t NumLiveChunks() { return 0; }
static uint64_t TotalMem() { return 0; }
#endif
// grab current contents of the serialiser into this chunk
Chunk(Serialiser *ser, uint32_t chunkType, bool temp);
private:
// no copy semantics
Chunk(const Chunk &);
Chunk &operator =(const Chunk &);
friend class ScopedContext;
bool m_AlignedData;
bool m_Temporary;
uint32_t m_ChunkType;
uint32_t m_Length;
byte *m_Data;
string m_DebugStr;
#if !defined(RELEASE)
static int64_t m_LiveChunks, m_MaxChunks, m_TotalMem;
#endif
};
// this class has a few functions. It can be used to serialise chunks - on writing it enforces
// that we only ever write a single chunk, then pull out the data into a Chunk class and erase
// the contents of the serialiser ready to serialise the next (see the RDCASSERT at the start
// of PushContext).
//
// We use this functionality for sending and receiving data across the network as well as saving
// out to the capture logfile format.
//
// It's also used on reading where it will contain the stream of chunks that were written out
// to the logfile on capture.
//
// When reading, the Serialiser allocates a window of memory and scans through the file by reading
// data into that window and moving along through the file. The window will expand to accomodate
// whichever is the biggest single element within a chunk that's read (so that you can always guarantee
// while reading that the element you're interested in is always in memory).
class Serialiser
{
public:
enum Mode
{
NONE = 0,
READING,
WRITING,
DEBUGWRITING,
};
enum SerialiserError
{
eSerError_None = 0,
eSerError_FileIO,
eSerError_Corrupt,
eSerError_UnsupportedVersion,
};
// version number of overall file format or chunk organisation. If the contents/meaning/order of
// chunks have changed this does not need to be bumped, there are version numbers within each
// API that interprets the stream that can be bumped.
static const uint64_t SERIALISE_VERSION = 0x00000031;
//////////////////////////////////////////
// Init and error handling
Serialiser(size_t length, const byte *memoryBuf, bool fileheader);
Serialiser(const char *path, Mode mode, bool debugMode = false);
~Serialiser();
bool HasError() { return m_HasError; }
SerialiserError ErrorCode() { return m_ErrorCode; }
//////////////////////////////////////////
// Utility functions
bool AtEnd()
{
return GetOffset() >= m_BufferSize;
}
bool HasAlignedData()
{
return m_AlignedData;
}
uint64_t GetOffset() const
{
if(m_HasError)
{
RDCERR("Getting offset with error state serialiser");
return 0;
}
RDCASSERT(m_BufferHead && m_Buffer && m_BufferHead >= m_Buffer);
return m_BufferHead - m_Buffer + m_ReadOffset;
}
uint64_t GetSize()
{
if(m_Mode == READING)
return m_BufferSize;
return m_BufferHead - m_Buffer;
}
byte *GetRawPtr(size_t offs) const
{
return m_Buffer+offs;
}
// Set up the base pointer and size. Serialiser will allocate enough for
// the rest of the file and keep it all in memory (useful to keep everything
// in actual frame data resident in memory).
void SetBase(uint64_t offs)
{
FreeAlignedBuffer(m_Buffer);
RDCASSERT(m_BufferSize - offs < 0xffffffff);
m_CurrentBufferSize = (size_t)(m_BufferSize - offs);
m_BufferHead = m_Buffer = AllocAlignedBuffer(m_CurrentBufferSize);
m_ReadOffset = offs;
ReadFromFile(offs, m_CurrentBufferSize);
FileIO::fclose(m_ReadFileHandle);
m_ReadFileHandle = 0;
}
void SetOffset(uint64_t offs)
{
if(m_HasError)
{
RDCERR("Setting offset with error state serialiser");
return;
}
// if we're jumping back before our in-memory window just reset the window
// and load it all in from scratch.
if(m_Mode == READING && offs < m_ReadOffset)
{
FreeAlignedBuffer(m_Buffer);
m_CurrentBufferSize = (size_t)RDCMIN(m_BufferSize, (uint64_t)64*1024);
m_BufferHead = m_Buffer = AllocAlignedBuffer(m_CurrentBufferSize);
m_ReadOffset = offs;
ReadFromFile(offs, m_CurrentBufferSize);
}
RDCASSERT(m_BufferHead && m_Buffer && offs <= GetSize());
m_BufferHead = m_Buffer + offs - m_ReadOffset;
m_Indent = 0;
}
void Rewind()
{
m_DebugText = "";
m_Indent = 0;
m_AlignedData = false;
SetOffset(0);
}
// assumes buffer head is sitting before a chunk (ie. pushcontext will be valid)
void SkipToChunk(uint32_t chunkIdx)
{
do
{
size_t offs = m_BufferHead-m_Buffer + (size_t)m_ReadOffset;
uint32_t c = PushContext(NULL, 1, false);
// found
if(c == chunkIdx)
{
m_Indent--;
m_BufferHead = (m_Buffer+offs)-(size_t)m_ReadOffset;
return;
}
else
{
SkipCurrentChunk();
PopContext(NULL, 1);
}
} while(!AtEnd());
}
// assumes buffer head is sitting in a chunk (ie. immediately after a pushcontext)
void SkipCurrentChunk()
{
ReadBytes(m_LastChunkLen);
}
void InitCallstackResolver();
bool HasCallstacks() { return m_HasResolver; }
// get callstack resolver, created with the DB in the file
Callstack::StackResolver *GetCallstackResolver()
{
return m_pResolver;
}
void SetCallstack(uint64_t *levels, size_t numLevels);
// get the callstack associated with the last scope
Callstack::Stackwalk *GetLastCallstack()
{
return m_pCallstack;
}
//////////////////////////////////////////
// Public serialisation interface
int GetContextLevel() { return m_Indent; }
uint32_t PushContext(const char *name, uint32_t chunkIdx, bool smallChunk);
void PopContext(const char *name, uint32_t chunkIdx);
// Write a chunk to disk
void Insert(Chunk *el);
// serialise a fixed-size array.
template<int Num, class T>
void Serialise(const char *name, T *el)
{
size_t n = Num;
Serialise(name, el, n);
}
// serialise a normal array. Typically this should be a small array,
// for large buffers use SerialiseBuffer which is optimised for that
//
// If serialising in, el must either be NULL in which case allocated
// memory will be returned, or it must be already large enough.
template<class T>
void Serialise(const char *name, T *&el, size_t &Num)
{
uint32_t numElems = (uint32_t)Num;
if(m_Mode == WRITING)
{
WriteFrom(numElems);
WriteBytes((byte *)el, sizeof(T)*numElems);
}
else if(m_Mode == READING)
{
ReadInto(numElems);
if(el == NULL) el = new T[numElems];
size_t length = numElems*sizeof(T);
memcpy(el, ReadBytes(length), length);
}
Num = (size_t)numElems;
if(name != NULL && m_DebugTextWriting)
{
for(size_t i=0; i < Num; i++)
DebugPrint("%s[%d] = %s\n", name, i, ToStr::Get<T>(el[i]).c_str());
}
}
// serialise a single element
template<class T> void Serialise(const char *name, T &el)
{
if(m_Mode == WRITING)
{
WriteFrom(el);
}
else if(m_Mode == READING)
{
ReadInto(el);
}
if(name != NULL && m_DebugTextWriting)
DebugPrint("%s: %s\n", name, ToStr::Get<T>(el).c_str());
}
template<typename X>
void Serialise(const char *name, std::vector<X> &el)
{
uint64_t sz = el.size();
Serialise(name, sz);
if(m_Mode == WRITING)
{
for(size_t i=0; i < sz; i++)
Serialise("[]", el[i]);
}
else
{
el.clear();
el.reserve((size_t)sz);
for(size_t i=0; i < sz; i++)
{
X x = X();
Serialise("", x);
el.push_back(x);
}
}
}
template<typename X>
void Serialise(const char *name, rdctype::array<X> &el)
{
int32_t sz = el.count;
Serialise(name, sz);
if(m_Mode == WRITING)
{
for(int32_t i=0; i < sz; i++)
Serialise("[]", el.elems[i]);
}
else
{
create_array_uninit(el, sz);
for(int32_t i=0; i < sz; i++)
Serialise("", el.elems[i]);
}
}
void Serialise(const char *name, rdctype::str &el)
{
int32_t sz = el.count;
Serialise(name, sz);
if(m_Mode == WRITING)
{
for(int32_t i=0; i < sz; i++)
Serialise("[]", el.elems[i]);
}
else
{
create_array_uninit(el, sz);
for(int32_t i=0; i < sz; i++)
Serialise("", el.elems[i]);
}
}
template<typename X, typename Y>
void Serialise(const char *name, std::pair<X, Y> &el)
{
Serialise(name, el.first);
Serialise(name, el.second);
}
template<typename X, typename Y>
void Serialise(const char *name, rdctype::pair<X, Y> &el)
{
Serialise(name, el.first);
Serialise(name, el.second);
}
template<typename X>
void Serialise(const char *name, std::list<X> &el)
{
uint64_t sz = el.size();
Serialise(name, sz);
if(m_Mode == WRITING)
{
for(auto it=el.begin(); it != el.end(); ++it)
Serialise("[]", *it);
}
else
{
el.clear();
for(uint64_t i=0; i < sz; i++)
{
X x = X();
Serialise("", x);
el.push_back(x);
}
}
}
// not sure if I still neeed these specialisations anymore.
void SerialiseString(const char *name, string &el);
// serialise a buffer.
//
// If serialising in, buf must either be NULL in which case allocated
// memory will be returned, or it must be already large enough.
void SerialiseBuffer(const char *name, byte *&buf, size_t &len);
void SkipBuffer();
// NOT recommended interface. Useful for specific situations if e.g. you have
// a buffer of data that is not arbitrary in size and can be determined by a 'type' or
// similar elsewhere in the stream, so you want to skip the type-safety of the above
// and write directly into the stream. Must be matched by a RawReadBytes.
void RawWriteBytes(const void *data, size_t bytes)
{
WriteBytes((const byte *)data, bytes);
}
const void *RawReadBytes(size_t bytes)
{
return ReadBytes(bytes);
}
// prints to the debug output log
void DebugPrint(const char *fmt, ...);
static byte *AllocAlignedBuffer(size_t size);
static void FreeAlignedBuffer(byte *buf);
uint64_t FlushToDisk();
// set a function used when serialising a text representation
// of the chunks
void SetChunkNameLookup(ChunkLookup lookup)
{
m_ChunkLookup = lookup;
}
void SetDebugText(bool enabled)
{
m_DebugTextWriting = enabled;
}
bool GetDebugText()
{
return m_DebugTextWriting;
}
string GetDebugStr()
{
return m_DebugText;
}
// debug-only output must be locked since it's global across all serialisers
// essentially, which might not be thread safe in the normal flow
void DebugLock()
{
m_DebugLock.Lock();
}
void DebugUnlock()
{
m_DebugLock.Unlock();
}
private:
//////////////////////////////////////////
// Raw memory buffer read/write
void WriteBytes(const byte *buf, size_t nBytes)
{
#ifdef DEBUG_TEXT_SERIALISER
if(m_Mode == DEBUGWRITING)
return;
#endif
if(m_HasError)
{
RDCERR("Writing bytes with error state serialiser");
return;
}
if(m_Buffer+m_BufferSize < m_BufferHead+nBytes+8)
{
// reallocate
while(m_Buffer+m_BufferSize < m_BufferHead+nBytes+8)
{
m_BufferSize += 128*1024;
}
byte *newBuf = AllocAlignedBuffer((size_t)m_BufferSize);
size_t curUsed = m_BufferHead-m_Buffer;
memcpy(newBuf, m_Buffer, curUsed);
FreeAlignedBuffer(m_Buffer);
m_Buffer = newBuf;
m_BufferHead = newBuf + curUsed;
}
memcpy(m_BufferHead, buf, nBytes);
m_BufferHead += nBytes;
}
void *ReadBytes(size_t nBytes)
{
if(m_HasError)
{
RDCERR("Reading bytes with error state serialiser");
return NULL;
}
// if we would read off the end of our current window
if(m_BufferHead+nBytes > m_Buffer+m_CurrentBufferSize)
{
size_t BufferOffset = m_BufferHead-m_Buffer;
if(nBytes+64 > m_CurrentBufferSize)
{
FreeAlignedBuffer(m_Buffer);
m_CurrentBufferSize = nBytes+64;
m_Buffer = AllocAlignedBuffer(m_CurrentBufferSize);
}
if(BufferOffset > 64)
{
m_ReadOffset += BufferOffset-64;
m_BufferHead = m_Buffer+64;
}
else
{
m_BufferHead = m_Buffer+BufferOffset;
}
// if there's anything left of the file to read in, do so now
ReadFromFile(m_ReadOffset, RDCMIN(m_CurrentBufferSize, (size_t)(m_BufferSize-m_ReadOffset)));
}
void *ret = m_BufferHead;
m_BufferHead += nBytes;
RDCASSERT(m_BufferHead <= m_Buffer+m_CurrentBufferSize);
return ret;
}
void ReadFromFile(uint64_t destOffs, size_t chunkLen);
template<class T> void WriteFrom(const T &f)
{
WriteBytes((byte *)&f, sizeof(T));
}
template<class T> void ReadInto(T &f)
{
if(m_HasError)
{
RDCERR("Reading into with error state serialiser");
return;
}
char *data = (char *)ReadBytes(sizeof(T));
f = *((T *)data);
}
// no copies
Serialiser(const Serialiser &other);
static void CreateResolver(void *ths);
// clean out for before constructor and after destructor (and other times probably)
void Reset();
string GetIndent()
{
if(m_Mode == READING)
return string(m_Indent > 0 ? 4 : 0, ' ');
return string((size_t)m_Indent*4, ' ');
}
//////////////////////////////////////////
static const uint32_t MAGIC_HEADER;
static const size_t BufferAlignment;
struct DebuggerHeader
{
DebuggerHeader()
{
magic = MAGIC_HEADER;
version = SERIALISE_VERSION;
}
uint64_t magic;
uint64_t version;
uint64_t fileSize;
uint64_t resolveDBSize;
};
//////////////////////////////////////////
Mode m_Mode;
SerialiserError m_ErrorCode;
bool m_HasError;
bool m_DebugEnabled;
int m_Indent;
bool m_HasResolver;
Callstack::Stackwalk *m_pCallstack;
Callstack::StackResolver *m_pResolver;
Threading::ThreadHandle m_ResolverThread;
volatile bool m_ResolverThreadKillSignal;
string m_Filename;
// raw binary buffer
uint64_t m_BufferSize;
byte *m_Buffer;
byte *m_BufferHead;
size_t m_LastChunkLen;
bool m_AlignedData;
vector<uint64_t> m_ChunkFixups;
// reading from file:
// where in the actual on-disk file does the data start (ie. after header and symbol DB)
uint64_t m_FileStartOffset;
// where does our in-memory window point to in the data stream. ie. m_pBuffer[0] is
// m_ReadOffset into the disk stream
uint64_t m_ReadOffset;
// how big is the current in-memory window
size_t m_CurrentBufferSize;
// the file pointer to read from
FILE *m_ReadFileHandle;
// writing to file
vector<Chunk *> m_Chunks;
// a database of strings read from the file, useful when serialised structures
// expect a char* to return and point to static memory
set<string> m_StringDB;
// debug buffer
bool m_DebugTextWriting;
string m_DebugText;
ChunkLookup m_ChunkLookup;
Threading::CriticalSection m_DebugLock;
};
template<> void Serialiser::Serialise(const char *name, string &el);
// floats need aligned reads
template<> void Serialiser::ReadInto(float &f);
class ScopedContext
{
public:
ScopedContext(Serialiser *s, Serialiser *debugser, const char *n, const char *t, uint32_t i, bool smallChunk)
: m_Idx(i), m_Ser(s), m_Ended(false)
#ifdef DEBUG_TEXT_SERIALISER
, m_DebugSer(debugser)
#endif
{
m_Name = string(n) + " = " + t;
m_Ser->PushContext(m_Name.c_str(), m_Idx, smallChunk);
#ifdef DEBUG_TEXT_SERIALISER
if(m_DebugSer)
{
m_DebugSer->DebugLock();
m_DebugSer->PushContext(m_Name.c_str(), m_Idx, smallChunk);
}
#endif
}
ScopedContext(Serialiser *s, Serialiser *debugser, const char *n, uint32_t i, bool smallChunk)
: m_Idx(i), m_Ser(s), m_Ended(false)
#ifdef DEBUG_TEXT_SERIALISER
, m_DebugSer(debugser)
#endif
{
m_Name = n;
m_Ser->PushContext(m_Name.c_str(), m_Idx, smallChunk);
#ifdef DEBUG_TEXT_SERIALISER
if(m_DebugSer)
{
m_DebugSer->DebugLock();
m_DebugSer->PushContext(m_Name.c_str(), m_Idx, smallChunk);
}
#endif
}
~ScopedContext()
{
if(!m_Ended)
End();
}
Chunk *Get(bool temporary = false)
{
End();
return new Chunk(m_Ser, m_Idx, temporary);
}
private:
std::string m_Name;
uint32_t m_Idx;
Serialiser *m_Ser;
#ifdef DEBUG_TEXT_SERIALISER
Serialiser *m_DebugSer;
#endif
bool m_Ended;
void End()
{
RDCASSERT(!m_Ended);
m_Ser->PopContext(m_Name.c_str(), m_Idx);
#ifdef DEBUG_TEXT_SERIALISER
if(m_DebugSer)
{
m_DebugSer->PopContext(m_Name.c_str(), m_Idx);
m_DebugSer->DebugUnlock();
}
#endif
m_Ended = true;
}
};
#ifdef DEBUG_TEXT_SERIALISER
#define SCOPED_SERIALISE_CONTEXT(n) ScopedContext scope(m_pSerialiser, m_pDebugSerialiser, GetChunkName(n), n, false);
#define SCOPED_SERIALISE_SMALL_CONTEXT(n) ScopedContext scope(m_pSerialiser, m_pDebugSerialiser, GetChunkName(n), n, true);
#define SERIALISE_ELEMENT(type, name, inValue) type name; if(m_State >= WRITING) name = (inValue); m_pSerialiser->Serialise(#name, name); m_pDebugSerialiser->Serialise(#name, name);
#define SERIALISE_ELEMENT_OPT(type, name, inValue, Condition) type name = type(); if(Condition) { if(m_State >= WRITING) name = (inValue); m_pSerialiser->Serialise(#name, name); m_pDebugSerialiser->Serialise(#name, name); }
#define SERIALISE_ELEMENT_ARR(type, name, inValues, count) type *name = new type[count]; for(size_t serialiseIdx=0; serialiseIdx < count; serialiseIdx++) { if(m_State >= WRITING) name[serialiseIdx] = (inValues)[serialiseIdx]; m_pSerialiser->Serialise(#name, name[serialiseIdx]); m_pDebugSerialiser->Serialise(#name, name[serialiseIdx]); }
#define SERIALISE_ELEMENT_ARR_OPT(type, name, inValues, count, Condition) type *name = NULL; if(Condition) { name = new type[count]; for(size_t serialiseIdx=0; serialiseIdx < count; serialiseIdx++) { if(m_State >= WRITING) name[serialiseIdx] = (inValues)[serialiseIdx]; m_pSerialiser->Serialise(#name, name[serialiseIdx]); m_pDebugSerialiser->Serialise(#name, name[serialiseIdx]); } }
#define SERIALISE_ELEMENT_PTR(type, name, inValue) type name; if(inValue && m_State >= WRITING) name = *(inValue); m_pSerialiser->Serialise(#name, name); m_pDebugSerialiser->Serialise(#name, name);
#define SERIALISE_ELEMENT_PTR_OPT(type, name, inValue, Condition) type name; if(Condition) { if(inValue && m_State >= WRITING) name = *(inValue); m_pSerialiser->Serialise(#name, name); m_pDebugSerialiser->Serialise(#name, name); }
#define SERIALISE_ELEMENT_BUF(type, name, inBuf, Len) type name = (type)NULL; if(m_State >= WRITING) name = (type)(inBuf); size_t CONCAT(buflen, __LINE__) = Len; m_pSerialiser->SerialiseBuffer(#name, name, CONCAT(buflen, __LINE__)); m_pDebugSerialiser->SerialiseBuffer(#name, name, CONCAT(buflen, __LINE__));
#define SERIALISE_ELEMENT_BUF_OPT(type, name, inBuf, Len, Condition) type name = (type)NULL; if(Condition) { if(m_State >= WRITING) name = (type)(inBuf); size_t CONCAT(buflen, __LINE__) = Len; m_pSerialiser->SerialiseBuffer(#name, name, CONCAT(buflen, __LINE__)); m_pDebugSerialiser->SerialiseBuffer(#name, name, CONCAT(buflen, __LINE__)); }
#else
#define SCOPED_SERIALISE_CONTEXT(n) ScopedContext scope(m_pSerialiser, NULL, GetChunkName(n), n, false);
#define SCOPED_SERIALISE_SMALL_CONTEXT(n) ScopedContext scope(m_pSerialiser, NULL, GetChunkName(n), n, true);
#define SERIALISE_ELEMENT(type, name, inValue) type name; if(m_State >= WRITING) name = (inValue); m_pSerialiser->Serialise(#name, name);
#define SERIALISE_ELEMENT_OPT(type, name, inValue, Condition) type name = type(); if(Condition) { if(m_State >= WRITING) name = (inValue); m_pSerialiser->Serialise(#name, name); }
#define SERIALISE_ELEMENT_ARR(type, name, inValues, count) type *name = new type[count]; for(size_t serialiseIdx=0; serialiseIdx < count; serialiseIdx++) { if(m_State >= WRITING) name[serialiseIdx] = (inValues)[serialiseIdx]; m_pSerialiser->Serialise(#name, name[serialiseIdx]); }
#define SERIALISE_ELEMENT_ARR_OPT(type, name, inValues, count, Condition) type *name = NULL; if(Condition) { name = new type[count]; for(size_t serialiseIdx=0; serialiseIdx < count; serialiseIdx++) { if(m_State >= WRITING) name[serialiseIdx] = (inValues)[serialiseIdx]; m_pSerialiser->Serialise(#name, name[serialiseIdx]); } }
#define SERIALISE_ELEMENT_PTR(type, name, inValue) type name; if(inValue && m_State >= WRITING) name = *(inValue); m_pSerialiser->Serialise(#name, name);
#define SERIALISE_ELEMENT_PTR_OPT(type, name, inValue, Condition) type name; if(Condition) { if(inValue && m_State >= WRITING) name = *(inValue); m_pSerialiser->Serialise(#name, name); }
#define SERIALISE_ELEMENT_BUF(type, name, inBuf, Len) type name = (type)NULL; if(m_State >= WRITING) name = (type)(inBuf); size_t CONCAT(buflen, __LINE__) = Len; m_pSerialiser->SerialiseBuffer(#name, name, CONCAT(buflen, __LINE__));
#define SERIALISE_ELEMENT_BUF_OPT(type, name, inBuf, Len, Condition) type name = (type)NULL; if(Condition) { if(m_State >= WRITING) name = (type)(inBuf); size_t CONCAT(buflen, __LINE__) = Len; m_pSerialiser->SerialiseBuffer(#name, name, CONCAT(buflen, __LINE__)); }
#endif
// forward declare generic pointer version to void*
template<class T>
struct ToStrHelper<true, T>
{
static string Get(const T &el)
{
void *ptr = (void *)el;
return ToStrHelper<false, void*>::Get(ptr);
}
};
#define TOSTR_CASE_STRINGIZE(a) case a: return #a;
#define TOSTR_CASE_STRINGIZE_CONCAT(a, b) case CONCAT(a, b): return #b;
#define TOSTR_CASE_STRINGIZE_NAMESPACE(a, b) case a::b: return #b;