1
0
forked from 0ad/0ad

Convert serialization code to use less virtuals and allow more inlining.

This was SVN commit r7582.
This commit is contained in:
Ykkrosh 2010-05-25 19:01:30 +00:00
parent 9090d25ef8
commit 01bec7a454
12 changed files with 250 additions and 201 deletions

View File

@ -23,14 +23,11 @@
#include "simulation2/serialization/BinarySerializer.h"
#include "simulation2/serialization/StdDeserializer.h"
/**
* Serializer instance that writes directly to a buffer (which must be long enough).
*/
class CBufferBinarySerializer : public CBinarySerializer
class CBufferBinarySerializerImpl
{
public:
CBufferBinarySerializer(ScriptInterface& scriptInterface, u8* buffer) :
CBinarySerializer(scriptInterface), m_Buffer(buffer)
CBufferBinarySerializerImpl(u8* buffer) :
m_Buffer(buffer)
{
}
@ -44,16 +41,25 @@ public:
};
/**
* Serializer instance that simply counts how many bytes would be written.
* Serializer instance that writes directly to a buffer (which must be long enough).
*/
class CLengthBinarySerializer : public CBinarySerializer
class CBufferBinarySerializer : public CBinarySerializer<CBufferBinarySerializerImpl>
{
public:
CLengthBinarySerializer(ScriptInterface& scriptInterface) :
CBinarySerializer(scriptInterface), m_Length(0)
CBufferBinarySerializer(ScriptInterface& scriptInterface, u8* buffer) :
CBinarySerializer<CBufferBinarySerializerImpl>(scriptInterface, buffer)
{
}
u8* GetBuffer()
{
return m_Impl.m_Buffer;
}
};
class CLengthBinarySerializerImpl
{
public:
void Put(const char* UNUSED(name), const u8* UNUSED(data), size_t len)
{
m_Length += len;
@ -62,6 +68,23 @@ public:
size_t m_Length;
};
/**
* Serializer instance that simply counts how many bytes would be written.
*/
class CLengthBinarySerializer : public CBinarySerializer<CLengthBinarySerializerImpl>
{
public:
CLengthBinarySerializer(ScriptInterface& scriptInterface) :
CBinarySerializer<CLengthBinarySerializerImpl>(scriptInterface)
{
}
size_t GetLength()
{
return m_Impl.m_Length;
}
};
CSimulationMessage::CSimulationMessage(ScriptInterface& scriptInterface) :
CNetMessage(NMT_SIMULATION_COMMAND), m_ScriptInterface(scriptInterface)
{
@ -84,7 +107,7 @@ u8* CSimulationMessage::Serialize(u8* pBuffer) const
serializer.NumberI32_Unbounded("player", m_Player);
serializer.NumberU32_Unbounded("turn", m_Turn);
serializer.ScriptVal("command", m_Data);
return serializer.m_Buffer;
return serializer.GetBuffer();
}
const u8* CSimulationMessage::Deserialize(const u8* pStart, const u8* pEnd)
@ -112,7 +135,7 @@ size_t CSimulationMessage::GetSerializedLength() const
serializer.NumberI32_Unbounded("player", m_Player);
serializer.NumberU32_Unbounded("turn", m_Turn);
serializer.ScriptVal("command", m_Data);
return CNetMessage::GetSerializedLength() + serializer.m_Length;
return CNetMessage::GetSerializedLength() + serializer.GetLength();
}
CStr CSimulationMessage::ToString() const

View File

@ -21,10 +21,7 @@
#include "SerializedScriptTypes.h"
#include "lib/byte_order.h"
#include "lib/utf8.h"
#include "ps/CLogger.h"
#include "ps/utf16string.h"
#include "scriptinterface/ScriptInterface.h"
#include "scriptinterface/AutoRooters.h"
@ -42,77 +39,12 @@
# pragma warning(pop)
#endif
CBinarySerializer::CBinarySerializer(ScriptInterface& scriptInterface) :
m_ScriptInterface(scriptInterface), m_ScriptBackrefsNext(1), m_Rooter(scriptInterface)
CBinarySerializerScriptImpl::CBinarySerializerScriptImpl(ScriptInterface& scriptInterface, ISerializer& serializer) :
m_ScriptInterface(scriptInterface), m_Serializer(serializer), m_ScriptBackrefsNext(1), m_Rooter(m_ScriptInterface)
{
}
/*
The Put* implementations here are designed for subclasses
that want an efficient, portable, deserializable representation.
(Subclasses with different requirements should override these methods.)
Numbers are converted to little-endian byte strings, for portability
and efficiency.
Data is not aligned, for storage efficiency.
*/
void CBinarySerializer::PutNumber(const char* name, uint8_t value)
{
Put(name, (const u8*)&value, sizeof(uint8_t));
}
void CBinarySerializer::PutNumber(const char* name, int32_t value)
{
int32_t v = (i32)to_le32((u32)value);
Put(name, (const u8*)&v, sizeof(int32_t));
}
void CBinarySerializer::PutNumber(const char* name, uint32_t value)
{
uint32_t v = to_le32(value);
Put(name, (const u8*)&v, sizeof(uint32_t));
}
void CBinarySerializer::PutNumber(const char* name, float value)
{
Put(name, (const u8*)&value, sizeof(float));
}
void CBinarySerializer::PutNumber(const char* name, double value)
{
Put(name, (const u8*)&value, sizeof(double));
}
void CBinarySerializer::PutNumber(const char* name, fixed value)
{
PutNumber(name, value.GetInternalValue());
}
void CBinarySerializer::PutBool(const char* name, bool value)
{
NumberU8(name, value ? 1 : 0, 0, 1);
}
void CBinarySerializer::PutString(const char* name, const std::string& value)
{
// TODO: should intern strings, particularly to save space with script property names
PutNumber("string length", (uint32_t)value.length());
Put(name, (u8*)value.data(), value.length());
}
void CBinarySerializer::PutScriptVal(const char* UNUSED(name), jsval value)
{
HandleScriptVal(value);
}
////////////////////////////////////////////////////////////////
void CBinarySerializer::HandleScriptVal(jsval val)
void CBinarySerializerScriptImpl::HandleScriptVal(jsval val)
{
JSContext* cx = m_ScriptInterface.GetContext();
@ -120,19 +52,19 @@ void CBinarySerializer::HandleScriptVal(jsval val)
{
case JSTYPE_VOID:
{
NumberU8_Unbounded("type", SCRIPT_TYPE_VOID);
m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_VOID);
break;
}
case JSTYPE_NULL: // This type is never actually returned (it's a JS2 feature)
{
NumberU8_Unbounded("type", SCRIPT_TYPE_NULL);
m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_NULL);
break;
}
case JSTYPE_OBJECT:
{
if (JSVAL_IS_NULL(val))
{
NumberU8_Unbounded("type", SCRIPT_TYPE_NULL);
m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_NULL);
break;
}
@ -142,19 +74,19 @@ void CBinarySerializer::HandleScriptVal(jsval val)
u32 tag = GetScriptBackrefTag(obj);
if (tag)
{
NumberU8_Unbounded("type", SCRIPT_TYPE_BACKREF);
NumberU32_Unbounded("tag", tag);
m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_BACKREF);
m_Serializer.NumberU32_Unbounded("tag", tag);
break;
}
if (JS_IsArrayObject(cx, obj))
{
NumberU8_Unbounded("type", SCRIPT_TYPE_ARRAY);
m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_ARRAY);
// TODO: probably should have a more efficient storage format
}
else
{
NumberU8_Unbounded("type", SCRIPT_TYPE_OBJECT);
m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_OBJECT);
// if (JS_GetClass(cx, obj))
// {
@ -184,7 +116,7 @@ void CBinarySerializer::HandleScriptVal(jsval val)
// we can't distinguish that from really having 0 properties without performing the actual iteration,
// so just assume the object always returns the correct count
NumberU32_Unbounded("num props", (uint32_t)n);
m_Serializer.NumberU32_Unbounded("num props", (uint32_t)n);
jsid id;
@ -230,7 +162,7 @@ void CBinarySerializer::HandleScriptVal(jsval val)
}
case JSTYPE_STRING:
{
NumberU8_Unbounded("type", SCRIPT_TYPE_STRING);
m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_STRING);
ScriptString("string", JSVAL_TO_STRING(val));
break;
}
@ -239,24 +171,24 @@ void CBinarySerializer::HandleScriptVal(jsval val)
// For efficiency, handle ints and doubles separately.
if (JSVAL_IS_INT(val))
{
NumberU8_Unbounded("type", SCRIPT_TYPE_INT);
m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_INT);
// jsvals are limited to JSVAL_INT_BITS == 31 bits, even on 64-bit platforms
NumberI32("value", (int32_t)JSVAL_TO_INT(val), JSVAL_INT_MIN, JSVAL_INT_MAX);
m_Serializer.NumberI32("value", (int32_t)JSVAL_TO_INT(val), JSVAL_INT_MIN, JSVAL_INT_MAX);
}
else
{
debug_assert(JSVAL_IS_DOUBLE(val));
NumberU8_Unbounded("type", SCRIPT_TYPE_DOUBLE);
m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_DOUBLE);
jsdouble* dbl = JSVAL_TO_DOUBLE(val);
NumberDouble_Unbounded("value", *dbl);
m_Serializer.NumberDouble_Unbounded("value", *dbl);
}
break;
}
case JSTYPE_BOOLEAN:
{
NumberU8_Unbounded("type", SCRIPT_TYPE_BOOLEAN);
m_Serializer.NumberU8_Unbounded("type", SCRIPT_TYPE_BOOLEAN);
JSBool b = JSVAL_TO_BOOLEAN(val);
NumberU8_Unbounded("value", b ? 1 : 0);
m_Serializer.NumberU8_Unbounded("value", b ? 1 : 0);
break;
}
case JSTYPE_XML:
@ -272,7 +204,7 @@ void CBinarySerializer::HandleScriptVal(jsval val)
}
}
void CBinarySerializer::ScriptString(const char* name, JSString* string)
void CBinarySerializerScriptImpl::ScriptString(const char* name, JSString* string)
{
jschar* chars = JS_GetStringChars(string);
size_t length = JS_GetStringLength(string);
@ -282,11 +214,11 @@ void CBinarySerializer::ScriptString(const char* name, JSString* string)
#endif
// Serialize strings directly as UTF-16, to avoid expensive encoding conversions
NumberU32_Unbounded("string length", (uint32_t)length);
RawBytes(name, (const u8*)chars, length*2);
m_Serializer.NumberU32_Unbounded("string length", (uint32_t)length);
m_Serializer.RawBytes(name, (const u8*)chars, length*2);
}
u32 CBinarySerializer::GetScriptBackrefTag(JSObject* obj)
u32 CBinarySerializerScriptImpl::GetScriptBackrefTag(JSObject* obj)
{
// To support non-tree structures (e.g. "var x = []; var y = [x, x];"), we need a way
// to indicate multiple references to one object(/array). So every time we serialize a

View File

@ -22,35 +22,24 @@
#include "scriptinterface/AutoRooters.h"
#include "lib/byte_order.h"
#include <map>
/**
* Serialize to a binary stream. Subclasses should just need to implement
* the Put() method.
* PutScriptVal implementation details.
* (Split out from the main class because it's too big to be inlined.)
*/
class CBinarySerializer : public ISerializer
class CBinarySerializerScriptImpl
{
NONCOPYABLE(CBinarySerializer);
public:
CBinarySerializer(ScriptInterface& scriptInterface);
CBinarySerializerScriptImpl(ScriptInterface& scriptInterface, ISerializer& serializer);
protected:
virtual void PutNumber(const char* name, uint8_t value);
virtual void PutNumber(const char* name, int32_t value);
virtual void PutNumber(const char* name, uint32_t value);
virtual void PutNumber(const char* name, float value);
virtual void PutNumber(const char* name, double value);
virtual void PutNumber(const char* name, fixed value);
virtual void PutBool(const char* name, bool value);
virtual void PutString(const char* name, const std::string& value);
virtual void PutScriptVal(const char* name, jsval value);
private:
// PutScriptVal implementation details:
void ScriptString(const char* name, JSString* string);
void HandleScriptVal(jsval val);
private:
ScriptInterface& m_ScriptInterface;
ISerializer& m_Serializer;
typedef std::map<JSObject*, u32> backrefs_t;
@ -61,4 +50,99 @@ private:
AutoGCRooter m_Rooter;
};
/**
* Serialize to a binary stream. T must just implement the Put() method.
* (We use this templated approach to allow compiler inlining.)
*/
template <typename T>
class CBinarySerializer : public ISerializer
{
NONCOPYABLE(CBinarySerializer);
public:
CBinarySerializer(ScriptInterface& scriptInterface) :
m_ScriptImpl(new CBinarySerializerScriptImpl(scriptInterface, *this))
{
}
template <typename A>
CBinarySerializer(ScriptInterface& scriptInterface, A& a) :
m_ScriptImpl(new CBinarySerializerScriptImpl(scriptInterface, *this)),
m_Impl(a)
{
}
protected:
/*
The Put* implementations here are designed for subclasses
that want an efficient, portable, deserializable representation.
(Subclasses with different requirements should override these methods.)
Numbers are converted to little-endian byte strings, for portability
and efficiency.
Data is not aligned, for storage efficiency.
*/
virtual void PutNumber(const char* name, uint8_t value)
{
m_Impl.Put(name, (const u8*)&value, sizeof(uint8_t));
}
virtual void PutNumber(const char* name, int32_t value)
{
int32_t v = (i32)to_le32((u32)value);
m_Impl.Put(name, (const u8*)&v, sizeof(int32_t));
}
virtual void PutNumber(const char* name, uint32_t value)
{
uint32_t v = to_le32(value);
m_Impl.Put(name, (const u8*)&v, sizeof(uint32_t));
}
virtual void PutNumber(const char* name, float value)
{
m_Impl.Put(name, (const u8*)&value, sizeof(float));
}
virtual void PutNumber(const char* name, double value)
{
m_Impl.Put(name, (const u8*)&value, sizeof(double));
}
virtual void PutNumber(const char* name, fixed value)
{
int32_t v = (i32)to_le32((u32)value.GetInternalValue());
m_Impl.Put(name, (const u8*)&v, sizeof(int32_t));
}
virtual void PutBool(const char* name, bool value)
{
NumberU8(name, value ? 1 : 0, 0, 1);
}
virtual void PutString(const char* name, const std::string& value)
{
// TODO: maybe should intern strings, particularly to save space with script property names
PutNumber("string length", (uint32_t)value.length());
m_Impl.Put(name, (u8*)value.data(), value.length());
}
virtual void PutScriptVal(const char* UNUSED(name), jsval value)
{
m_ScriptImpl->HandleScriptVal(value);
}
virtual void PutRaw(const char* name, const u8* data, size_t len)
{
m_Impl.Put(name, data, len);
}
protected:
T m_Impl;
private:
std::auto_ptr<CBinarySerializerScriptImpl> m_ScriptImpl;
};
#endif // INCLUDED_BINARYSERIALIZER

View File

@ -140,7 +140,7 @@ void CDebugSerializer::PutScriptVal(const char* name, jsval value)
m_Stream << INDENT << name << ": " << source << "\n";
}
void CDebugSerializer::Put(const char* name, const u8* data, size_t len)
void CDebugSerializer::PutRaw(const char* name, const u8* data, size_t len)
{
m_Stream << INDENT << name << ": (" << len << " bytes)";

View File

@ -50,8 +50,7 @@ protected:
virtual void PutBool(const char* name, bool value);
virtual void PutString(const char* name, const std::string& value);
virtual void PutScriptVal(const char* name, jsval value);
virtual void Put(const char* name, const u8* data, size_t len);
virtual void PutRaw(const char* name, const u8* data, size_t len);
private:
ScriptInterface& m_ScriptInterface;

View File

@ -20,22 +20,27 @@
#include "HashSerializer.h"
CHashSerializer::CHashSerializer(ScriptInterface& scriptInterface) :
CBinarySerializer(scriptInterface)
CBinarySerializer<CHashSerializerImpl>(scriptInterface)
{
}
size_t CHashSerializer::GetHashLength()
{
return HashFunc::DIGESTSIZE;
return m_Impl.GetHashLength();
}
const u8* CHashSerializer::ComputeHash()
{
return m_Impl.ComputeHash();
}
size_t CHashSerializerImpl::GetHashLength()
{
return HashFunc::DIGESTSIZE;
}
const u8* CHashSerializerImpl::ComputeHash()
{
m_Hash.Final(m_HashData);
return m_HashData;
}
void CHashSerializer::Put(const char* UNUSED(name), const u8* data, size_t len)
{
m_Hash.Update(data, len);
}

View File

@ -22,22 +22,33 @@
#include "maths/MD5.h"
class CHashSerializer : public CBinarySerializer
class CHashSerializerImpl
{
// We don't care about cryptographic strength, just about detection of
// unintended changes and about performance, so MD5 is an adequate choice
typedef MD5 HashFunc;
public:
CHashSerializer(ScriptInterface& scriptInterface);
size_t GetHashLength();
const u8* ComputeHash();
protected:
virtual void Put(const char* name, const u8* data, size_t len);
void Put(const char* UNUSED(name), const u8* data, size_t len)
{
m_Hash.Update(data, len);
}
private:
HashFunc m_Hash;
u8 m_HashData[HashFunc::DIGESTSIZE];
};
class CHashSerializer : public CBinarySerializer<CHashSerializerImpl>
{
public:
CHashSerializer(ScriptInterface& scriptInterface);
size_t GetHashLength();
const u8* ComputeHash();
};
#endif // INCLUDED_HASHSERIALIZER

View File

@ -46,41 +46,6 @@ void ISerializer::NumberU32(const char* name, uint32_t value, uint32_t lower, ui
PutNumber(name, value);
}
void ISerializer::NumberU8_Unbounded(const char* name, uint8_t value)
{
PutNumber(name, value);
}
void ISerializer::NumberI32_Unbounded(const char* name, int32_t value)
{
PutNumber(name, value);
}
void ISerializer::NumberU32_Unbounded(const char* name, uint32_t value)
{
PutNumber(name, value);
}
void ISerializer::NumberFloat_Unbounded(const char* name, float value)
{
PutNumber(name, value);
}
void ISerializer::NumberDouble_Unbounded(const char* name, double value)
{
PutNumber(name, value);
}
void ISerializer::NumberFixed_Unbounded(const char* name, fixed value)
{
PutNumber(name, value);
}
void ISerializer::Bool(const char* name, bool value)
{
PutBool(name, value);
}
void ISerializer::StringASCII(const char* name, const std::string& value, uint32_t minlength, uint32_t maxlength)
{
if (!(minlength <= value.length() && value.length() <= maxlength))
@ -123,7 +88,7 @@ void ISerializer::ScriptVal(const char* name, CScriptValRooted value)
void ISerializer::RawBytes(const char* name, const u8* data, size_t len)
{
Put(name, data, len);
PutRaw(name, data, len);
}
bool ISerializer::IsDebug() const

View File

@ -144,17 +144,44 @@ public:
* @param name informative name for debug output
* @param value value to serialize
*/
void NumberU8_Unbounded(const char* name, uint8_t value);
void NumberI32_Unbounded(const char* name, int32_t value); ///< @copydoc NumberU8_Unbounded
void NumberU32_Unbounded(const char* name, uint32_t value); ///< @copydoc NumberU8_Unbounded
void NumberFloat_Unbounded(const char* name, float value); ///< @copydoc NumberU8_Unbounded
void NumberDouble_Unbounded(const char* name, double value); ///< @copydoc NumberU8_Unbounded
void NumberFixed_Unbounded(const char* name, fixed value); ///< @copydoc NumberU8_Unbounded
void NumberU8_Unbounded(const char* name, uint8_t value)
{
// (These functions are defined inline for efficiency)
PutNumber(name, value);
}
void NumberI32_Unbounded(const char* name, int32_t value) ///< @copydoc NumberU8_Unbounded
{
PutNumber(name, value);
}
void NumberU32_Unbounded(const char* name, uint32_t value) ///< @copydoc NumberU8_Unbounded
{
PutNumber(name, value);
}
void NumberFloat_Unbounded(const char* name, float value) ///< @copydoc NumberU8_Unbounded
{
PutNumber(name, value);
}
void NumberDouble_Unbounded(const char* name, double value) ///< @copydoc NumberU8_Unbounded
{
PutNumber(name, value);
}
void NumberFixed_Unbounded(const char* name, fixed value) ///< @copydoc NumberU8_Unbounded
{
PutNumber(name, value);
}
/**
* Serialize a boolean.
*/
void Bool(const char* name, bool value);
void Bool(const char* name, bool value)
{
PutBool(name, value);
}
/**
* Serialize an ASCII string.
@ -215,8 +242,7 @@ protected:
virtual void PutBool(const char* name, bool value) = 0;
virtual void PutString(const char* name, const std::string& value) = 0;
virtual void PutScriptVal(const char* name, jsval value) = 0;
virtual void Put(const char* name, const u8* data, size_t len) = 0;
virtual void PutRaw(const char* name, const u8* data, size_t len) = 0;
};
#endif // INCLUDED_ISERIALIZER

View File

@ -21,9 +21,6 @@
#include "SerializedScriptTypes.h"
#include "ps/CLogger.h"
#include "ps/CStr.h"
#include "scriptinterface/ScriptInterface.h"
#include "js/jsapi.h"

View File

@ -22,20 +22,12 @@
#include <ostream>
#include <cstring>
CStdSerializerImpl::CStdSerializerImpl(std::ostream& stream) :
m_Stream(stream)
{
}
CStdSerializer::CStdSerializer(ScriptInterface& scriptInterface, std::ostream& stream) :
CBinarySerializer(scriptInterface), m_Stream(stream)
CBinarySerializer<CStdSerializerImpl>(scriptInterface, stream)
{
}
void CStdSerializer::Put(const char* name, const u8* data, size_t len)
{
#if 0 // annotate the stream to help debugging if you're reading the output in a hex editor
m_Stream.put('[');
m_Stream.write(name, strlen(name));
m_Stream.put(']');
#else
UNUSED2(name);
#endif
m_Stream.write((const char*)data, len);
}

View File

@ -20,16 +20,31 @@
#include "BinarySerializer.h"
class CStdSerializer : public CBinarySerializer
class CStdSerializerImpl
{
public:
CStdSerializer(ScriptInterface& scriptInterface, std::ostream& stream);
CStdSerializerImpl(std::ostream& stream);
protected:
virtual void Put(const char* name, const u8* data, size_t len);
void Put(const char* name, const u8* data, size_t len)
{
#if 0 // annotate the stream to help debugging if you're reading the output in a hex editor
m_Stream.put('[');
m_Stream.write(name, strlen(name));
m_Stream.put(']');
#else
UNUSED2(name);
#endif
m_Stream.write((const char*)data, len);
}
private:
std::ostream& m_Stream;
};
class CStdSerializer : public CBinarySerializer<CStdSerializerImpl>
{
public:
CStdSerializer(ScriptInterface& scriptInterface, std::ostream& stream);
};
#endif // INCLUDED_STDSERIALIZER