Files
UnrealEngineUWP/Engine/Source/Programs/DotNETCommon/DotNETUtilities/BinaryArchiveWriter.cs
Ben Marsh a22b952aa9 Copying //UE4/Dev-Build to Dev-Main (//UE4/Dev-Main)
#rb none
#rnx

[CL 4718806 by Ben Marsh in Main branch]
2019-01-14 12:11:24 -05:00

475 lines
11 KiB
C#

// 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
{
/// <summary>
/// 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.
/// </summary>
public class BinaryArchiveWriter : IDisposable
{
/// <summary>
/// Comparer which tests for reference equality between two objects
/// </summary>
class ReferenceComparer : IEqualityComparer<object>
{
bool IEqualityComparer<object>.Equals(object A, object B)
{
return A == B;
}
int IEqualityComparer<object>.GetHashCode(object X)
{
return RuntimeHelpers.GetHashCode(X);
}
}
/// <summary>
/// Instance of the ReferenceComparer class which can be shared by all archive writers
/// </summary>
static ReferenceComparer ReferenceComparerInstance = new ReferenceComparer();
/// <summary>
/// The output stream being written to
/// </summary>
Stream Stream;
/// <summary>
/// Buffer for data to be written to the stream
/// </summary>
byte[] Buffer;
/// <summary>
/// Current position within the output buffer
/// </summary>
int BufferPos;
/// <summary>
/// Map of object instance to unique id
/// </summary>
Dictionary<object, int> ObjectToUniqueId = new Dictionary<object, int>(ReferenceComparerInstance);
/// <summary>
/// Constructor
/// </summary>
/// <param name="Stream">The output stream</param>
public BinaryArchiveWriter(Stream Stream)
{
this.Stream = Stream;
this.Buffer = new byte[4096];
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="FileName">File to write to</param>
public BinaryArchiveWriter(FileReference FileName)
: this(File.Open(FileName.FullName, FileMode.Create, FileAccess.Write, FileShare.Read))
{
}
/// <summary>
/// Flushes this stream, and disposes the stream
/// </summary>
public void Dispose()
{
Flush();
if(Stream != null)
{
Stream.Dispose();
Stream = null;
}
}
/// <summary>
/// Writes all buffered data to disk
/// </summary>
public void Flush()
{
if(BufferPos > 0)
{
Stream.Write(Buffer, 0, BufferPos);
BufferPos = 0;
}
}
/// <summary>
/// Ensures there is a minimum amount of space in the output buffer
/// </summary>
/// <param name="NumBytes">Minimum amount of space required in the output buffer</param>
private void EnsureSpace(int NumBytes)
{
if(BufferPos + NumBytes > Buffer.Length)
{
Flush();
if(NumBytes > Buffer.Length)
{
Buffer = new byte[NumBytes];
}
}
}
/// <summary>
/// Writes a bool to the output
/// </summary>
/// <param name="Value">Value to write</param>
public void WriteBool(bool Value)
{
WriteByte(Value? (byte)1 : (byte)0);
}
/// <summary>
/// Writes a single byte to the output
/// </summary>
/// <param name="Value">Value to write</param>
public void WriteByte(byte Value)
{
EnsureSpace(1);
Buffer[BufferPos] = Value;
BufferPos++;
}
/// <summary>
/// Writes a single signed byte to the output
/// </summary>
/// <param name="Value">Value to write</param>
public void WriteSignedByte(sbyte Value)
{
WriteByte((byte)Value);
}
/// <summary>
/// Writes a single short to the output
/// </summary>
/// <param name="Value">Value to write</param>
public void WriteShort(short Value)
{
WriteUnsignedShort((ushort)Value);
}
/// <summary>
/// Writes a single unsigned short to the output
/// </summary>
/// <param name="Value">Value to write</param>
public void WriteUnsignedShort(ushort Value)
{
EnsureSpace(2);
Buffer[BufferPos + 0] = (byte)Value;
Buffer[BufferPos + 1] = (byte)(Value >> 8);
BufferPos += 2;
}
/// <summary>
/// Writes a single int to the output
/// </summary>
/// <param name="Value">Value to write</param>
public void WriteInt(int Value)
{
WriteUnsignedInt((uint)Value);
}
/// <summary>
/// Writes a single unsigned int to the output
/// </summary>
/// <param name="Value">Value to write</param>
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;
}
/// <summary>
/// Writes a single long to the output
/// </summary>
/// <param name="Value">Value to write</param>
public void WriteLong(long Value)
{
WriteUnsignedLong((ulong)Value);
}
/// <summary>
/// Writes a single unsigned long to the output
/// </summary>
/// <param name="Value">Value to write</param>
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;
}
/// <summary>
/// Writes a string to the output
/// </summary>
/// <param name="Value">Value to write</param>
public void WriteString(string Value)
{
byte[] Bytes;
if(Value == null)
{
Bytes = null;
}
else
{
Bytes = Encoding.UTF8.GetBytes(Value);
}
WriteByteArray(Bytes);
}
/// <summary>
/// Writes an array of bytes to the output
/// </summary>
/// <param name="Data">Data to write. May be null.</param>
public void WriteByteArray(byte[] Data)
{
WritePrimitiveArray(Data, sizeof(byte));
}
/// <summary>
/// Writes an array of shorts to the output
/// </summary>
/// <param name="Data">Data to write. May be null.</param>
public void WriteShortArray(short[] Data)
{
WritePrimitiveArray(Data, sizeof(short));
}
/// <summary>
/// Writes an array of ints to the output
/// </summary>
/// <param name="Data">Data to write. May be null.</param>
public void WriteIntArray(int[] Data)
{
WritePrimitiveArray(Data, sizeof(int));
}
/// <summary>
/// Writes an array of primitive types to the output.
/// </summary>
/// <param name="Data">Data to write. May be null.</param>
private void WritePrimitiveArray<T>(T[] Data, int ElementSize) where T : struct
{
if(Data == null)
{
WriteInt(-1);
}
else
{
WriteInt(Data.Length);
WriteBulkData(Data, Data.Length * ElementSize);
}
}
/// <summary>
/// Writes primitive data from the given array to the output buffer.
/// </summary>
/// <param name="Data">Data to write.</param>
/// <param name="Size">Size of the data, in bytes</param>
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();
}
}
}
/// <summary>
/// Write an array of items to the archive
/// </summary>
/// <typeparam name="T">Type of the element</typeparam>
/// <param name="Items">Array of items</param>
/// <param name="WriteElement">Writes an individual element to the archive</param>
public void WriteArray<T>(T[] Items, Action<T> WriteElement)
{
if(Items == null)
{
WriteInt(-1);
}
else
{
WriteInt(Items.Length);
for(int Idx = 0; Idx < Items.Length; Idx++)
{
WriteElement(Items[Idx]);
}
}
}
/// <summary>
/// Write a list of items to the archive
/// </summary>
/// <typeparam name="T">Type of the element</typeparam>
/// <param name="Items">List of items</param>
/// <param name="WriteElement">Writes an individual element to the archive</param>
public void WriteList<T>(List<T> Items, Action<T> WriteElement)
{
if(Items == null)
{
WriteInt(-1);
}
else
{
WriteInt(Items.Count);
for(int Idx = 0; Idx < Items.Count; Idx++)
{
WriteElement(Items[Idx]);
}
}
}
/// <summary>
/// Writes a hashset of items
/// </summary>
/// <typeparam name="T">The element type for the set</typeparam>
/// <param name="Set">The set to write</param>
/// <param name="WriteElement">Delegate used to read a single element</param>
public void WriteHashSet<T>(HashSet<T> Set, Action<T> WriteElement)
{
if(Set == null)
{
WriteInt(-1);
}
else
{
WriteInt(Set.Count);
foreach(T Element in Set)
{
WriteElement(Element);
}
}
}
/// <summary>
/// Writes a dictionary of items
/// </summary>
/// <typeparam name="K">Type of the dictionary key</typeparam>
/// <typeparam name="V">Type of the dictionary value</typeparam>
/// <param name="Dictionary">The dictionary to write</param>
/// <param name="WriteKey">Delegate used to read a single key</param>
/// <param name="WriteValue">Delegate used to read a single value</param>
public void WriteDictionary<K, V>(Dictionary<K, V> Dictionary, Action<K> WriteKey, Action<V> WriteValue)
{
if(Dictionary == null)
{
WriteInt(-1);
}
else
{
WriteInt(Dictionary.Count);
foreach(KeyValuePair<K, V> Pair in Dictionary)
{
WriteKey(Pair.Key);
WriteValue(Pair.Value);
}
}
}
/// <summary>
/// Writes a nullable object to the archive
/// </summary>
/// <typeparam name="T">The nullable type</typeparam>
/// <param name="Item">Item to write</param>
/// <param name="WriteValue">Delegate used to write a value</param>
public void WriteNullable<T>(Nullable<T> Item, Action<T> WriteValue) where T : struct
{
if(Item.HasValue)
{
WriteBool(true);
WriteValue(Item.Value);
}
else
{
WriteBool(false);
}
}
/// <summary>
/// Writes an object to the output, checking whether it is null or not. Does not preserve object references; each object written is duplicated.
/// </summary>
/// <typeparam name="T">Type of the object to serialize</typeparam>
/// <param name="Object">Reference to check for null before serializing</param>
/// <param name="WriteObject">Delegate used to write the object</param>
public void WriteOptionalObject<T>(T Object, Action WriteObject) where T : class
{
if(Object == null)
{
WriteBool(false);
}
else
{
WriteBool(true);
WriteObject();
}
}
/// <summary>
/// Writes an object to the output. If the specific instance has already been written, preserves the reference to that.
/// </summary>
/// <typeparam name="T">Type of the object to serialize</typeparam>
/// <param name="Object">The object to serialize</param>
/// <param name="WriteObject">Delegate used to write the object</param>
public void WriteObjectReference<T>(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();
}
}
}
}
}