| 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 | |