1 | //************************************ bs::framework - Copyright 2018 Marko Pintera **************************************// |
2 | //*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********// |
3 | #pragma once |
4 | |
5 | #include "Prerequisites/BsPrerequisitesUtil.h" |
6 | #include "Serialization/BsSerializedObject.h" |
7 | #include "Reflection/BsRTTIField.h" |
8 | |
9 | namespace bs |
10 | { |
11 | /** @addtogroup Serialization |
12 | * @{ |
13 | */ |
14 | |
15 | class IReflectable; |
16 | struct RTTIReflectableFieldBase; |
17 | struct RTTIReflectablePtrFieldBase; |
18 | struct SerializationContext; |
19 | |
20 | /** |
21 | * Encodes/decodes all the fields of the provided object into/from a binary format. Fields are encoded using their |
22 | * unique IDs. Encoded data will remain compatible for decoding even if you modify the encoded class, as long as you |
23 | * assign new unique field IDs to added/modified fields. |
24 | * |
25 | * Like for any serializable class, fields are defined in RTTIType that each IReflectable class must be able to return. |
26 | * |
27 | * Any data the object or its children are pointing to will also be serialized (unless the pointer isn't registered in |
28 | * RTTIType). Upon decoding the pointer addresses will be set to proper values. |
29 | */ |
30 | class BS_UTILITY_EXPORT BinarySerializer |
31 | { |
32 | public: |
33 | BinarySerializer(); |
34 | |
35 | /** |
36 | * Encodes all serializable fields provided by @p object into a binary format. Data is written in chunks. Whenever a |
37 | * chunk is filled a callback is triggered that gives the user opportunity to expand or empty the buffer (for |
38 | * example write the chunk to disk) |
39 | * |
40 | * @param[in] object Object to encode into binary format. |
41 | * @param[out] buffer Preallocated buffer where the data will be stored. |
42 | * @param[in] bufferLength Length of the buffer, in bytes. |
43 | * @param[out] bytesWritten Length of the data that was actually written to the buffer, in bytes. |
44 | * @param[in] flushBufferCallback This callback will get called whenever the buffer gets full (Be careful to |
45 | * check the provided @p bytesRead variable, as buffer might not be full |
46 | * completely). User must then either create a new buffer or empty the existing |
47 | * one, and then return it by the callback. If the returned buffer address is |
48 | * NULL, encoding is aborted. |
49 | * @param[in] shallow Determines how to handle referenced objects. If true then references will |
50 | * not be encoded and will be set to null. If false then references will be |
51 | * encoded as well and restored upon decoding. |
52 | * @param[in] context Optional object that will be passed along to all serialized objects through |
53 | * their serialization callbacks. Can be used for controlling serialization, |
54 | * maintaining state or sharing information between objects during |
55 | * serialization. |
56 | */ |
57 | void encode(IReflectable* object, UINT8* buffer, UINT32 bufferLength, UINT32* bytesWritten, |
58 | std::function<UINT8*(UINT8* buffer, UINT32 bytesWritten, UINT32& newBufferSize)> flushBufferCallback, |
59 | bool shallow = false, SerializationContext* context = nullptr); |
60 | |
61 | /** |
62 | * Decodes an object from binary data. |
63 | * |
64 | * @param[in] data Binary data to decode. |
65 | * @param[in] dataLength Length of the data in bytes. |
66 | * @param[in] context Optional object that will be passed along to all serialized objects through |
67 | * their deserialization callbacks. Can be used for controlling deserialization, |
68 | * maintaining state or sharing information between objects during deserialization. |
69 | * @param[in] progress Optional callback that will occassionally trigger, reporting the current progress |
70 | * of the operation. The reported value is in range [0, 1]. |
71 | * |
72 | * @note |
73 | * Child elements are guaranteed to be fully deserialized before their parents, except for fields marked with WeakRef flag. |
74 | */ |
75 | SPtr<IReflectable> decode(const SPtr<DataStream>& data, UINT32 dataLength, SerializationContext* context = nullptr, |
76 | std::function<void(float)> progress = nullptr); |
77 | private: |
78 | /** Determines how many bytes need to be read before the progress report callback is triggered. */ |
79 | static constexpr UINT32 REPORT_AFTER_BYTES = 32768; |
80 | |
81 | struct ObjectMetaData |
82 | { |
83 | UINT32 objectMeta; |
84 | UINT32 typeId; |
85 | }; |
86 | |
87 | struct ObjectToEncode |
88 | { |
89 | ObjectToEncode(UINT32 _objectId, const SPtr<IReflectable>& _object) |
90 | :objectId(_objectId), object(_object) |
91 | { } |
92 | |
93 | UINT32 objectId; |
94 | SPtr<IReflectable> object; |
95 | }; |
96 | |
97 | struct ObjectToDecode |
98 | { |
99 | ObjectToDecode(const SPtr<IReflectable>& _object, size_t offset = 0) |
100 | :object(_object), offset(offset) |
101 | { } |
102 | |
103 | SPtr<IReflectable> object; |
104 | bool isDecoded = false; |
105 | bool decodeInProgress = false; // Used for error reporting circular references |
106 | size_t offset; |
107 | }; |
108 | |
109 | /** Encodes a single IReflectable object. */ |
110 | UINT8* encodeEntry(IReflectable* object, UINT32 objectId, UINT8* buffer, UINT32& bufferLength, UINT32* bytesWritten, |
111 | std::function<UINT8*(UINT8* buffer, UINT32 bytesWritten, UINT32& newBufferSize)> flushBufferCallback, bool shallow); |
112 | |
113 | /** Decodes a single IReflectable object. */ |
114 | bool decodeEntry(const SPtr<DataStream>& data, size_t dataLength, const SPtr<IReflectable>& output); |
115 | |
116 | /** Helper method for encoding a complex object and copying its data to a buffer. */ |
117 | UINT8* complexTypeToBuffer(IReflectable* object, UINT8* buffer, UINT32& bufferLength, UINT32* bytesWritten, |
118 | std::function<UINT8*(UINT8* buffer, UINT32 bytesWritten, UINT32& newBufferSize)> flushBufferCallback, bool shallow); |
119 | |
120 | /** Helper method for encoding a data block to a buffer. */ |
121 | UINT8* dataBlockToBuffer(UINT8* data, UINT32 size, UINT8* buffer, UINT32& bufferLength, UINT32* bytesWritten, |
122 | std::function<UINT8*(UINT8* buffer, UINT32 bytesWritten, UINT32& newBufferSize)> flushBufferCallback); |
123 | |
124 | /** Finds an existing, or creates a unique unique identifier for the specified object. */ |
125 | UINT32 findOrCreatePersistentId(IReflectable* object); |
126 | |
127 | /** |
128 | * Finds or creates an id for the provided object and returns it. And it adds the object to a list of objects that |
129 | * need to be encoded, if it's not already there. |
130 | */ |
131 | UINT32 registerObjectPtr(SPtr<IReflectable> object); |
132 | |
133 | /** Encodes data required for representing a serialized field, into 4 bytes. */ |
134 | static UINT32 encodeFieldMetaData(UINT16 id, UINT8 size, bool array, |
135 | SerializableFieldType type, bool hasDynamicSize, bool terminator); |
136 | |
137 | /** Decode meta field that was encoded using encodeFieldMetaData().*/ |
138 | static void decodeFieldMetaData(UINT32 encodedData, UINT16& id, UINT8& size, bool& array, |
139 | SerializableFieldType& type, bool& hasDynamicSize, bool& terminator); |
140 | |
141 | /** |
142 | * Encodes data required for representing an object identifier, into 8 bytes. |
143 | * |
144 | * @param[in] objId Unique ID of the object instance. |
145 | * @param[in] objTypeId Unique ID of the object type. |
146 | * @param[in] isBaseClass true if this object is base class (that is, just a part of a larger object). |
147 | * |
148 | * @note Id can be a maximum of 30 bits, as two bits are reserved. |
149 | */ |
150 | static ObjectMetaData encodeObjectMetaData(UINT32 objId, UINT32 objTypeId, bool isBaseClass); |
151 | |
152 | /** Decode meta field that was encoded using encodeObjectMetaData. */ |
153 | static void decodeObjectMetaData(ObjectMetaData encodedData, UINT32& objId, UINT32& objTypeId, bool& isBaseClass); |
154 | |
155 | /** Returns true if the provided encoded meta data represents object meta data. */ |
156 | static bool isObjectMetaData(UINT32 encodedData); |
157 | |
158 | Map<UINT32, ObjectToDecode> mDecodeObjectMap; |
159 | Vector<ObjectToEncode> mObjectsToEncode; |
160 | UnorderedMap<void*, UINT32> mObjectAddrToId; |
161 | UINT32 mLastUsedObjectId = 1; |
162 | UINT32 mTotalBytesWritten; |
163 | UINT32 mTotalBytesRead = 0; |
164 | UINT32 mTotalBytesToRead = 0; |
165 | UINT32 mNextProgressReport = REPORT_AFTER_BYTES; |
166 | FrameAlloc* mAlloc = nullptr; |
167 | |
168 | SerializationContext* mContext = nullptr; |
169 | std::function<void(float)> mReportProgress = nullptr; |
170 | |
171 | static constexpr const int META_SIZE = 4; // Meta field size |
172 | static constexpr const int NUM_ELEM_FIELD_SIZE = 4; // Size of the field storing number of array elements |
173 | static constexpr const int COMPLEX_TYPE_FIELD_SIZE = 4; // Size of the field storing the size of a child complex type |
174 | static constexpr const int DATA_BLOCK_TYPE_FIELD_SIZE = 4; |
175 | }; |
176 | |
177 | // TODO - Potential improvements: |
178 | // - I will probably want to extract a generalized Serializer class so we can re-use the code in text or other serializers |
179 | // - Encode does a chunk-based encode so that we don't need to know the buffer size in advance, and don't have to use |
180 | // a lot of memory for the buffer. Consider doing something similar for decode. |
181 | // - Add a simple encode method that doesn't require a callback, instead it calls the callback internally and creates |
182 | // the buffer internally. |
183 | |
184 | /** @} */ |
185 | } |
186 | |