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