// 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();
}
}
}
}
}