// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.IO; using System.Runtime.CompilerServices; using System.Text; namespace EpicGames.Core { /// /// Writes data to a binary output stream. Similar to the NET Framework BinaryWriter class, but supports fast serialization of object graphs and container types, and supports nullable objects. /// public sealed class BinaryArchiveWriter : IDisposable { /// /// Comparer which tests for reference equality between two objects /// class ReferenceComparer : IEqualityComparer { bool IEqualityComparer.Equals(object? a, object? b) { return a == b; } int IEqualityComparer.GetHashCode(object x) { return RuntimeHelpers.GetHashCode(x); } } /// /// Instance of the ReferenceComparer class which can be shared by all archive writers /// static readonly ReferenceComparer s_referenceComparerInstance = new ReferenceComparer(); /// /// The output stream being written to /// Stream? _stream; /// /// Buffer for data to be written to the stream /// byte[] _buffer; /// /// Current position within the output buffer /// int _bufferPos; /// /// Map of object instance to unique id /// readonly Dictionary _objectToUniqueId = new Dictionary(s_referenceComparerInstance); /// /// Constructor /// /// The output stream public BinaryArchiveWriter(Stream stream) { _stream = stream; _buffer = new byte[4096]; } /// /// Constructor /// /// File to write to public BinaryArchiveWriter(FileReference fileName) : this(File.Open(fileName.FullName, FileMode.Create, FileAccess.Write, FileShare.Read)) { } /// /// Flushes this stream, and disposes the stream /// public void Dispose() { Flush(); if (_stream != null) { _stream.Dispose(); _stream = null; } } /// /// Writes all buffered data to disk /// public void Flush() { if (_bufferPos > 0) { _stream!.Write(_buffer, 0, _bufferPos); _bufferPos = 0; } } /// /// Ensures there is a minimum amount of space in the output buffer /// /// Minimum amount of space required in the output buffer private void EnsureSpace(int numBytes) { if (_bufferPos + numBytes > _buffer.Length) { Flush(); if (numBytes > _buffer.Length) { _buffer = new byte[numBytes]; } } } /// /// Writes a bool to the output /// /// Value to write public void WriteBool(bool value) { WriteByte(value ? (byte)1 : (byte)0); } /// /// Writes a single byte to the output /// /// Value to write public void WriteByte(byte value) { EnsureSpace(1); _buffer[_bufferPos] = value; _bufferPos++; } /// /// Writes a single signed byte to the output /// /// Value to write public void WriteSignedByte(sbyte value) { WriteByte((byte)value); } /// /// Writes a single short to the output /// /// Value to write public void WriteShort(short value) { WriteUnsignedShort((ushort)value); } /// /// Writes a single unsigned short to the output /// /// Value to write public void WriteUnsignedShort(ushort value) { EnsureSpace(2); _buffer[_bufferPos + 0] = (byte)value; _buffer[_bufferPos + 1] = (byte)(value >> 8); _bufferPos += 2; } /// /// Writes a single int to the output /// /// Value to write public void WriteInt(int value) { WriteUnsignedInt((uint)value); } /// /// Writes a single unsigned int to the output /// /// Value to write public void WriteUnsignedInt(uint value) { EnsureSpace(4); _buffer[_bufferPos + 0] = (byte)value; _buffer[_bufferPos + 1] = (byte)(value >> 8); _buffer[_bufferPos + 2] = (byte)(value >> 16); _buffer[_bufferPos + 3] = (byte)(value >> 24); _bufferPos += 4; } /// /// Writes a single long to the output /// /// Value to write public void WriteLong(long value) { WriteUnsignedLong((ulong)value); } /// /// Writes a single unsigned long to the output /// /// Value to write public void WriteUnsignedLong(ulong value) { EnsureSpace(8); _buffer[_bufferPos + 0] = (byte)value; _buffer[_bufferPos + 1] = (byte)(value >> 8); _buffer[_bufferPos + 2] = (byte)(value >> 16); _buffer[_bufferPos + 3] = (byte)(value >> 24); _buffer[_bufferPos + 4] = (byte)(value >> 32); _buffer[_bufferPos + 5] = (byte)(value >> 40); _buffer[_bufferPos + 6] = (byte)(value >> 48); _buffer[_bufferPos + 7] = (byte)(value >> 56); _bufferPos += 8; } /// /// Writes a double (64 bit floating point value) to the stream /// /// Value to write public void WriteDouble(double value) { WriteLong(BitConverter.DoubleToInt64Bits(value)); } /// /// Writes a string to the output /// /// Value to write public void WriteString(string? value) { byte[]? bytes; if (value == null) { bytes = null; } else { bytes = Encoding.UTF8.GetBytes(value); } WriteByteArray(bytes); } /// /// Writes an array of bytes to the output /// /// Data to write. May be null. public void WriteByteArray(byte[]? data) { WritePrimitiveArray(data, sizeof(byte)); } /// /// Writes an array of shorts to the output /// /// Data to write. May be null. public void WriteShortArray(short[]? data) { WritePrimitiveArray(data, sizeof(short)); } /// /// Writes an array of ints to the output /// /// Data to write. May be null. public void WriteIntArray(int[]? data) { WritePrimitiveArray(data, sizeof(int)); } /// /// Writes an array of primitive types to the output. /// /// Data to write. May be null. /// Size of each element private void WritePrimitiveArray(T[]? data, int elementSize) where T : struct { if (data == null) { WriteInt(-1); } else { WriteInt(data.Length); WriteBulkData(data, data.Length * elementSize); } } /// /// Writes an array of bytes to the output /// /// Data to write. May be null. public void WriteFixedSizeByteArray(byte[] data) { WriteFixedSizePrimitiveArray(data, sizeof(byte)); } /// /// Writes an array of shorts to the output /// /// Data to write. May be null. public void WriteFixedSizeShortArray(short[] data) { WriteFixedSizePrimitiveArray(data, sizeof(short)); } /// /// Writes an array of ints to the output /// /// Data to write. May be null. public void WriteFixedSizeIntArray(int[] data) { WriteFixedSizePrimitiveArray(data, sizeof(int)); } /// /// Writes an array of primitive types to the output. /// /// Data to write. May be null. /// Size of each element private void WriteFixedSizePrimitiveArray(T[] data, int elementSize) where T : struct { WriteBulkData(data, data.Length * elementSize); } /// /// Writes primitive data from the given array to the output buffer. /// /// Data to write. /// Size of the data, in bytes private void WriteBulkData(Array data, int size) { if (size > 0) { for (int pos = 0; ;) { int copySize = Math.Min(size - pos, _buffer.Length - _bufferPos); System.Buffer.BlockCopy(data, pos, _buffer, _bufferPos, copySize); _bufferPos += copySize; pos += copySize; if (pos == size) { break; } Flush(); } } } /// /// Write an array of items to the archive /// /// Type of the element /// Array of items /// Writes an individual element to the archive public void WriteArray(T[]? items, Action writeElement) { if (items == null) { WriteInt(-1); } else { WriteInt(items.Length); for (int idx = 0; idx < items.Length; idx++) { writeElement(items[idx]); } } } /// /// Write a list of items to the archive /// /// Type of the element /// List of items /// Writes an individual element to the archive public void WriteList(IReadOnlyList? items, Action writeElement) { if (items == null) { WriteInt(-1); } else { WriteInt(items.Count); for (int idx = 0; idx < items.Count; idx++) { writeElement(items[idx]); } } } /// /// Writes a hashset of items /// /// The element type for the set /// The set to write /// Delegate used to read a single element public void WriteHashSet(HashSet set, Action writeElement) { if (set == null) { WriteInt(-1); } else { WriteInt(set.Count); foreach (T element in set) { writeElement(element); } } } /// /// Writes a sortedset of items /// /// The element type for the sortedset /// The set to write /// Delegate used to read a single element public void WriteSortedSet(SortedSet set, Action writeElement) { if (set == null) { WriteInt(-1); } else { WriteInt(set.Count); foreach (T element in set) { writeElement(element); } } } /// /// Writes a dictionary of items /// /// Type of the dictionary key /// Type of the dictionary value /// The dictionary to write /// Delegate used to read a single key /// Delegate used to read a single value public void WriteDictionary(IReadOnlyDictionary dictionary, Action writeKey, Action writeValue) where TK : notnull { if (dictionary == null) { WriteInt(-1); } else { WriteInt(dictionary.Count); foreach (KeyValuePair pair in dictionary) { writeKey(pair.Key); writeValue(pair.Value); } } } /// /// Writes a nullable object to the archive /// /// The nullable type /// Item to write /// Delegate used to write a value public void WriteNullable(Nullable item, Action writeValue) where T : struct { if (item.HasValue) { WriteBool(true); writeValue(item.Value); } else { WriteBool(false); } } /// /// Writes an object to the output, checking whether it is null or not. Does not preserve object references; each object written is duplicated. /// /// Type of the object to serialize /// Reference to check for null before serializing /// Delegate used to write the object public void WriteOptionalObject(T obj, Action writeObject) where T : class { if (obj == null) { WriteBool(false); } else { WriteBool(true); writeObject(); } } /// /// Writes a null object reference to the output. /// public void WriteNullObjectReference() { WriteInt(-1); } /// /// Writes an object to the output. If the specific instance has already been written, preserves the reference to that. /// /// The object to serialize /// Delegate used to write the object public void WriteObjectReference(object? obj, Action writeObject) { if (WriteObjectReferenceUniqueId(obj)) { writeObject(); } } /// /// Writes an object to the output. If the specific instance has already been written, preserves the reference to that. /// /// The object to serialize /// Delegate used to write the object public void WriteObjectReference(T obj, Action writeObject) where T : class? { WriteObjectReference((object?)obj, writeObject); } /// /// Writes an object to the output. If the specific instance has already been written, preserves the reference to that. /// /// The object to serialize /// Delegate used to write the object public void WriteObjectReference(T obj, Action writeObject) where T : class? { if (WriteObjectReferenceUniqueId(obj)) { writeObject(this, obj); } } /// /// Writes a unique id for the given object. /// /// The object to serialize /// False if the object has already been written. True if this is the first time the object has been referenced. private bool WriteObjectReferenceUniqueId(object? obj) { if (obj == null) { WriteInt(-1); return false; } else { if (_objectToUniqueId.TryGetValue(obj, out int index)) { WriteInt(index); return false; } else { WriteInt(_objectToUniqueId.Count); _objectToUniqueId.Add(obj, _objectToUniqueId.Count); return true; } } } } }