Unity的序列化

源码位于Runtime>Serialize

Undo系统中的PropertyDiffUndoRecorder也依赖Unity的Serialize序列化系统。

每个类[ClassName]的序列化通过 [ClassName]::Transfer函数手动实现。比如Transform的序列化位于

//Transform.cpp

template<class TransferFunction> inline
void Transform::Transfer (TransferFunction& transfer) 
{
	Super::Transfer (transfer);
	TRANSFER_SIMPLE (m_LocalRotation);
	TRANSFER_SIMPLE (m_LocalPosition);
	TRANSFER_SIMPLE (m_LocalScale);

	//TRANSFER_EDITOR_ONLY_HIDDEN (m_LocalEulerAnglesHint);

	// This needs to be here since eg. Mesh collider queries the recalculate transform type.
	// and awakefromload might not have been called already.
	if (transfer.IsReading())
		RecalculateTransformType ();
	
	// When cloning objects for prefabs and instantiate, we don't use serialization to duplicate the hierarchy,
	// we duplicate the hierarchy directly
	if (SerializePrefabIgnoreProperties(transfer))
	{
		transfer.Transfer (m_Children, "m_Children", kHideInEditorMask | kStrongPPtrMask | kIgnoreWithInspectorUndoMask);	
		transfer.Transfer (m_Father, "m_Father", kHideInEditorMask | kIgnoreWithInspectorUndoMask);
	}

#if ENABLE_EDITOR_HIERARCHY_ORDERING
	TRANSFER_EDITOR_ONLY_HIDDEN(m_Order);
#endif
}

Vector3f的 Transfer 位于


// Runtime>Math>Vector3.h

template<class TransferFunction>
inline void Vector3f::Transfer (TransferFunction& t)
{
	t.AddMetaFlag (kTransferUsingFlowMappingStyle);
	t.Transfer (x, "x");
	t.Transfer (y, "y");
	t.Transfer (z, "z");
}

PropertyDiffUndoRecorder

PropertyDiffUndoRecorder::Flush会调用GenerateUndoDiffs

PropertyDiffUndoUtilities::GenerateUndoDiffs在最终调用GeneratePropertyDiff之前,会先调用序列化系统中的TransferUtilityWriteObjectToVector, 将object中的字段的值序列化到data数组中。



		// Get the type tree from the current state of the object
		// Serialize the current state to memory using WriteObjectToVector
		// Do a property diff between the two serialized data states

//PropertyDiffUndoUtilities.cpp
void GenerateUndoDiffs(const std::list<RecordedObject>& currentlyRecording, PropertyModifications& output)
{
	std::list<RecordedObject>::const_iterator i = currentlyRecording.begin();
	for (; i != currentlyRecording.end(); ++i)
	{
		const RecordedObject& recordItem = *i;
		if (recordItem.target.IsNull())
			continue;
		
		// Get the type tree from the current state of the object
		// Serialize the current state to memory using WriteObjectToVector
		// Do a property diff between the two serialized data states
		const TypeTree& typeTree = GenerateCachedTypeTree(*recordItem.target, kSerializeForPrefabSystem);

		if (recordItem.target->GetNeedsPerObjectTypeTree())
		{
			// Verify that the typetree in the current state matches the typetree when recording was started.
			// This should always be the case otherwise we missed the registration of an undo able action
			// which should be fixed
			bool isStreamCompatible = IsStreamedBinaryCompatbile(typeTree, recordItem.typeTree);

			if ( !isStreamCompatible)
			{
				// this should really not happen
				ErrorStringObject("Generating diff  of this object for undo because the type tree changed.\n"
					"This happens if you have used Undo.RecordObject when changing the script property.\n"
					"Please use Undo.RegisterCompleteObjectUndo", recordItem.target); 
				continue;
			}
		}

		dynamic_array<UInt8> currentState(kMemTempAlloc);
		WriteObjectToVector(*recordItem.target, &currentState, kSerializeForPrefabSystem);

		// And generate property diff, for any properties that are different from the prefab parent
		std::vector<PropertyModification> newProperties;
		GeneratePropertyDiff(typeTree, currentState, recordItem.preEditState, NULL, newProperties);
		// We have to set the target on each property modification or we will not be able to apply it later
		for (int i = 0; i < newProperties.size(); i++)
		{
			newProperties[i].target = recordItem.target;
		}

		if ( newProperties.size() != 0)
		{
			output.insert(output.end(), newProperties.begin(), newProperties.end());
		}
	}
}

TransferBase 和 SerializeTraits

下面介绍的多个Transfer都派生自TransfterBase,并且实现的函数中会用到SerializeTraitsSerializeTraits起到代理分发的作用。

SerializeTraitsBaseForBasicType 处理传输 基础数据 (int, double这种), 由传输类TransferBase 的派生类决定该如何处理,TypeTree相关的Transfter会记录数据的大小。Stream相关的Transfer则会读取或者写入该基础数据。

SerializeTraits 处理传输自定义数据, 这里由数据类决定如何处理数据,基本上会在该数据类型的Transfer函数实现上,手动挑选 字段 再调用Transfter。
比如参考Transform::Transfer

SerializeTraits会调用 自定义数据的Transfer函数,而Transfer函数中引擎会接着选择某些字段调用Transfer函数,实际上这个过程就起到了遍历整个TypeTree的效果。 到遍历到基础数据类型时, TransfterBase的派生类们 会接着决定 如何处理这些数据。

TransfterBase的Transfer函数可以选择传入数据Name,比如TypeTreem_Name是 手动传入的。。 比如Transform::Transfer的 第二行 TRANSFER_SIMPLE (m_LocalRotation); 会传入m_LocalRotation作为字符串。

#define TRANSFER(x) transfer.Transfer (x, #x)
#define TRANSFER_SIMPLE(x) transfer.Transfer (x, #x, kSimpleEditorMask)

//传输 基础数据  (int, double这种), 由`TransferBase `的派生类决定该如何处理
template<class T>
class SerializeTraitsBaseForBasicType : public SerializeTraitsBase<T>
{
public:
	typedef T value_type;

	template<class TransferFunction> inline
	static void Transfer (value_type& data, TransferFunction& transfer)
	{
		transfer.TransferBasicData (data);
	}
};


//传输自定义数据, 
template<class T>
class SerializeTraits : public SerializeTraitsBase<T>
{
public:

	typedef T value_type;

	inline static const char* GetTypeString (void* /*ptr*/) { return value_type::GetTypeString (); }
	inline static bool MightContainPPtr () { return value_type::MightContainPPtr (); }
	/// Returns whether or not a this type is to be treated as a seperate channel in the animation system
	static bool IsAnimationChannel () { return T::IsAnimationChannel (); }

	/// AllowTransferOptimization can be used for type that have the same memory format as serialized format.
	/// Eg. a float or a Vector3f.
	/// StreamedBinaryRead will collapse the read into a direct read when reading an array with values that have AllowTransferOptimization.
	static bool AllowTransferOptimization () { return T::AllowTransferOptimization (); }

	template<class TransferFunction> inline
	static void Transfer (value_type& data, TransferFunction& transfer)
	{
		data.Transfer (transfer);
	}

};

GenerateCachedTypeTree

会调用GenerateTypeTree, 接着又会调用ProxyTransferProxyTransfer就是专门用来为TypeTree记录 类型的父子关系,以及每个字段的大小。

template<class T>
inline void ProxyTransfer::TransferBasicData (T&)
{
	m_ActiveFather->m_ByteSize = SerializeTraits<T>::GetByteSize ();
	#if UNITY_EDITOR
	if (m_SimulatedByteOffset % m_ActiveFather->m_ByteSize != 0)
	{
		LogUnalignedTransfer();
	}
	m_SimulatedByteOffset += m_ActiveFather->m_ByteSize;
	#endif
}

WriteObjectToVector

WriteObjectToVector函数


//TransferUtility.cpp
template<bool swapEndian>
static inline void WriteObjectToVector (Object& object, dynamic_array<UInt8>* data, int options)
{
	Assert (data != NULL);
	data->clear ();

	MemoryCacheWriter memoryCache (*data);
	StreamedBinaryWrite<swapEndian> writeStream;
	CachedWriter& writeCache = writeStream.Init (options, BuildTargetSelection::NoTarget());

	writeCache.InitWrite (memoryCache);
	object.VirtualRedirectTransfer (writeStream);

	if (!writeCache.CompleteWriting () || writeCache.GetPosition() != data->size ())
		ErrorString ("Error while writing serialized data.");
}

void WriteObjectToVector (Object& object, dynamic_array<UInt8>* data, int options)
{
	WriteObjectToVector<false> (object, data, options);
}

其中object.VirtualRedirectTransfer 是实际序列化的函数, 会调用StreamedBinaryWrite的transfer

template<bool kSwapEndianess>
template<class T> inline
void StreamedBinaryWrite<kSwapEndianess>::TransferBasicData (T& data)
{
	if (kSwapEndianess)
	{
		T temp = data;
		SwapEndianBytes (temp);
		m_Cache.Write (temp);
	}
	else
		m_Cache.Write (data);
}

GeneratePropertyDiff

最后是将 之前的数据和现在的数据进行对比,并生成PropertyModification数组。


// 该函数会递归调用
//这里 propertyNameBuffer存储了字段的路径, 最后会赋值给PropertyModification
// addPropertyName 表示 当前的typeTree 要不要写进路径,基本是要的。。
// 
static void GeneratePropertyDiff (char* propertyNameBuffer, bool addPropertyName, const TypeTree& typeTree, const UInt8* src, int* srcBytePosition, const UInt8* dst, int* dstBytePosition, RemapPPtrCallback* clonedObjectToParentObjectRemap, vector<PropertyModification>& properties)
{
	if (typeTree.IsBasicDataType ())
	{
		if (!CompareData (src + *srcBytePosition, dst + *dstBytePosition, typeTree.m_ByteSize))
		{
			AddPropertyModificationValueFromBytes(propertyNameBuffer, addPropertyName, typeTree, dst + *dstBytePosition, properties);
		}
		*srcBytePosition += typeTree.m_ByteSize;
		*dstBytePosition += typeTree.m_ByteSize;
	}
	else if (IsTypeTreeString(typeTree))
	{
		SInt32 arraySize = ExtractArraySize(src + *srcBytePosition);
		bool isSame = false;
		if (arraySize == ExtractArraySize(dst + *dstBytePosition))
		{
			if (CompareData (src + *srcBytePosition + sizeof(SInt32), dst + *dstBytePosition + sizeof(SInt32), arraySize))
				isSame = true;
		}
		
		if (!isSame)
		{
			AddPropertyModificationValueFromBytes(propertyNameBuffer, addPropertyName, typeTree, dst + *dstBytePosition, properties);
		}
		
		WalkTypeTree(typeTree, src, srcBytePosition);
		WalkTypeTree(typeTree, dst, dstBytePosition);
		return;
	}
	else if (typeTree.m_IsArray)
	{
		SInt32 srcSize = ExtractArraySize(src + *srcBytePosition);
		SInt32 dstSize = ExtractArraySize(dst + *dstBytePosition);
		
		const TypeTree& sizeTypeTree = typeTree.m_Children.front();
		const TypeTree& elementTypeTree = typeTree.m_Children.back();
		
		// Create diff for array size
		AddPropertyNameToBuffer(propertyNameBuffer, typeTree);
		GeneratePropertyDiff (propertyNameBuffer, true, sizeTypeTree, src, srcBytePosition, dst, dstBytePosition, clonedObjectToParentObjectRemap, properties);
		
		// Iterate over both arrays
		for (int i=0;i<max(dstSize, srcSize);i++)
		{
			AddPropertyArrayIndexToBuffer(propertyNameBuffer, i);
			
			// Doesn't exist in dst size, no need for diffing
			if (i >= dstSize)
			{
				Assert(i < srcSize);
				Assert(i >= dstSize);
				WalkTypeTree(elementTypeTree, src, srcBytePosition);
			}
			// Array element doesn't exist in source -> Use empty array data
			else if (i >= srcSize)
			{
				dynamic_array<UInt8> emptyBuffer(kMemTempAlloc);
				int zeroElementSize = 0;
				CountZeroElementSize(elementTypeTree, &zeroElementSize);
				emptyBuffer.resize_initialized(zeroElementSize);
				int emptyBufferPosition = 0;
				GeneratePropertyDiff (propertyNameBuffer, false, elementTypeTree, &emptyBuffer[0], &emptyBufferPosition, dst, dstBytePosition, clonedObjectToParentObjectRemap, properties);
			}
			// Array element exists in source -> compare
			else
			{
				GeneratePropertyDiff (propertyNameBuffer, false, elementTypeTree, src, srcBytePosition, dst, dstBytePosition, clonedObjectToParentObjectRemap, properties);
			}
			
			ClearPropertyNameFromBuffer(propertyNameBuffer);
		}
		
		ClearPropertyNameFromBuffer(propertyNameBuffer);
	}
	else if (IsTypeTreePPtr(typeTree))
	{
		SInt32 srcInstanceID = ExtractPPtrInstanceID(src + *srcBytePosition);
		SInt32 dstInstanceID = ExtractPPtrInstanceID(dst + *dstBytePosition);
		SInt32 dstInstanceIDRemapped = dstInstanceID;
		
		// Remap the dst PPtr, this is used so that referenced to objects in the cloned island are compared to the same object in the clone.
		// For example the gameObject pptr of a component, should map to the cloned object not to the original prefab
		if (clonedObjectToParentObjectRemap != NULL)
			dstInstanceIDRemapped = clonedObjectToParentObjectRemap->Remap(dstInstanceID);
		
		if (srcInstanceID != dstInstanceIDRemapped)
		{
			if (addPropertyName)
				AddPropertyNameToBuffer(propertyNameBuffer, typeTree);
			
			PropertyModification modification;
			modification.propertyPath = propertyNameBuffer;
			modification.objectReference = PPtr<Object> (dstInstanceID);
			properties.push_back(modification);
			
			if (addPropertyName)
				ClearPropertyNameFromBuffer(propertyNameBuffer);
		}
		
		*srcBytePosition += typeTree.m_ByteSize;
		*dstBytePosition += typeTree.m_ByteSize;
	}
	else
	{
		// Recurse
		TypeTree::TypeTreeList::const_iterator i;
		for (i = typeTree.m_Children.begin (); i !=  typeTree.m_Children.end ();++i)
		{
			if (addPropertyName)
				AddPropertyNameToBuffer(propertyNameBuffer, typeTree);
			GeneratePropertyDiff (propertyNameBuffer, true, *i, src, srcBytePosition, dst, dstBytePosition, clonedObjectToParentObjectRemap, properties);
			
			if (addPropertyName)
				ClearPropertyNameFromBuffer(propertyNameBuffer);
		}
	}
	
	// Apply alignment on both arrays
	if (typeTree.m_MetaFlag & kAlignBytesFlag)
	{
		*srcBytePosition = Align4_Iterate (*srcBytePosition);
		*dstBytePosition = Align4_Iterate (*dstBytePosition);
	}
}


void AddPropertyNameToBuffer (char* propertyNameBuffer, const TypeTree& typeTree)
{
	const char* name = typeTree.m_Name.c_str();
	int size = strlen(propertyNameBuffer);
	if (size != 0)
	{
		propertyNameBuffer[size] = '.';
		size += 1;
	}
	memcpy(propertyNameBuffer + size, name, strlen(name) + 1);
	////@TODO: Fix out of bounds
}
posted @ 2025-03-27 22:11  dewxin  阅读(75)  评论(0)    收藏  举报