Files
sbox-public/engine/Sandbox.System/Utility/BytePack/BytePack.cs
s&box team 71f266059a Open source release
This commit imports the C# engine code and game files, excluding C++ source code.

[Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
2025-11-24 09:05:18 +00:00

195 lines
3.9 KiB
C#

namespace Sandbox;
/// <summary>
/// A class that can serialize and deserialize whole objects to and from byte streams,
/// without needing to know the type beforehand.
///
/// https://docs.facepunch.com/doc/bytepack-FdTR6ZUoOa
///
/// </summary>
internal partial class BytePack
{
readonly Dictionary<Type, Packer> types = new();
readonly Dictionary<Identifier, Packer> handlers = new();
readonly Dictionary<int, Packer> typeHandler = new();
internal Func<Type, Packer> OnCreatePackerFromType { get; set; }
internal Func<int, Packer> OnCreatePackerFromIdentifier { get; set; }
public BytePack()
{
InstallPodCommon();
Add( new ObjectPacker() );
Add( new StringPacker() );
Add( new ValueArrayPacker() );
Add( new ObjectArrayPacker() );
Add( new ListPacker() );
Add( new DictionaryPacker() );
}
public void Dispose()
{
types.Clear();
handlers.Clear();
typeHandler.Clear();
OnCreatePackerFromType = default;
OnCreatePackerFromIdentifier = default;
}
void Add( Packer ti )
{
ti.Init( this );
}
public byte[] Serialize<T>( T obj )
{
ByteStream bs = ByteStream.Create( 1024 );
try
{
Serialize( ref bs, obj );
return bs.ToArray();
}
finally
{
bs.Dispose();
}
}
public void SerializeTo<T>( ref ByteStream bs, T obj )
{
Serialize( ref bs, obj );
}
public object Deserialize( byte[] memory ) => Deserialize( new ReadOnlySpan<byte>( memory ) );
public object Deserialize( ReadOnlySpan<byte> data )
{
var bs = ByteStream.CreateReader( data );
try
{
return Deserialize( ref bs );
}
finally
{
bs.Dispose();
}
}
public object Deserialize( ref ByteStream data )
{
var h = data.Read<Identifier>();
if ( h == Identifier.Runtime )
{
int typeIdent = data.Read<int>();
var p = GetOrCreatePacker( typeIdent );
if ( p is not null )
{
return p.Read( ref data );
}
throw new System.Exception( $"Unhandled runtime ident {typeIdent}" );
}
if ( handlers.TryGetValue( h, out var typeInfo ) )
{
return typeInfo.Read( ref data );
}
if ( h == Identifier.Null ) return null;
throw new System.Exception( $"Unhandled header {h}" );
}
void Serialize<T>( ref ByteStream bs, T obj )
{
if ( obj is null )
{
bs.Write( Identifier.Null );
return;
}
if ( obj is Array array )
{
var element = array.GetType().GetElementType();
if ( SandboxedUnsafe.IsAcceptablePod( element ) )
{
bs.Write( Identifier.ArrayValue );
Serialize( Identifier.ArrayValue, ref bs, array );
return;
}
bs.Write( Identifier.Array );
Serialize( Identifier.Array, ref bs, array );
return;
}
var t = obj.GetType();
if ( t.IsBasedOnGenericType( typeof( List<> ) ) )
{
bs.Write( Identifier.List );
Serialize( Identifier.List, ref bs, obj );
return;
}
if ( t.IsBasedOnGenericType( typeof( Dictionary<,> ) ) )
{
bs.Write( Identifier.Dictionary );
Serialize( Identifier.Dictionary, ref bs, obj );
return;
}
if ( GetOrCreatePacker( t ) is Packer packer )
{
packer.WriteTypeIdentifier( ref bs, t );
packer.Write( ref bs, obj );
return;
}
throw new System.NotSupportedException( $"Unhandled type {t}" );
}
private Packer GetOrCreatePacker( Type type )
{
if ( types.TryGetValue( type, out var packer ) )
return packer;
if ( OnCreatePackerFromType is null )
return null;
packer = OnCreatePackerFromType( type );
if ( packer is null ) return null;
packer.Init( this );
return packer;
}
private Packer GetOrCreatePacker( int typeIdentifier )
{
if ( typeHandler.TryGetValue( typeIdentifier, out var packer ) )
return packer;
if ( OnCreatePackerFromIdentifier is null )
return null;
packer = OnCreatePackerFromIdentifier( typeIdentifier );
if ( packer is null )
return null;
packer.Init( this );
return packer;
}
void Serialize( Identifier ident, ref ByteStream bs, object obj )
{
handlers[ident].Write( ref bs, obj );
}
}