// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; namespace Tools.DotNETCommon { /// /// 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 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 ReferenceComparer 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 /// Dictionary ObjectToUniqueId = new Dictionary(ReferenceComparerInstance); /// /// Constructor /// /// The output stream public BinaryArchiveWriter(Stream Stream) { this.Stream = Stream; this.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 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. 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. 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 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(Dictionary Dictionary, Action WriteKey, Action WriteValue) { 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 Object, Action WriteObject) where T : class { if(Object == null) { WriteBool(false); } else { WriteBool(true); WriteObject(); } } /// /// Writes an object to the output. If the specific instance has already been written, preserves the reference to that. /// /// Type of the object to serialize /// The object to serialize /// Delegate used to write the object public void WriteObjectReference(T Object, Action WriteObject) where T : class { if(Object == null) { WriteInt(-1); } else { int Index; if(ObjectToUniqueId.TryGetValue(Object, out Index)) { WriteInt(Index); } else { WriteInt(ObjectToUniqueId.Count); ObjectToUniqueId.Add(Object, ObjectToUniqueId.Count); WriteObject(); } } } } }