NetworkFields

Anonymous
csharp
06/09/2026 3:13 PM
9.3 KB
5
No Index
using System.Buffers.Binary;
using MemoryPack;
using Shared.Interfaces;

namespace Shared.Core
{
  public delegate int DeserializeDelegate<T>(ReadOnlySpan<byte> source, out T value);

  public class NetworkField<T>(T defaultValue, DeserializeDelegate<T>? deserializeFactory = null) : ISerializable, IDirtyable where T : notnull
  {
    public T Value
    {
      get => _value;
      set
      {
        if (EqualityComparer<T>.Default.Equals(_value, value))
          return;

        _value = value;
        IsDirty = true;
      }
    }
    private T _value = defaultValue;
    private readonly DeserializeDelegate<T>? _deserializeFactory = deserializeFactory;
    public bool IsDirty { get; set; } = false;

    public void CopyFrom(NetworkField<T> other)
    {
      if (other.IsDirty)
        _value = other._value;

      IsDirty = false;
    }

    public int Serialize(Span<byte> destination, bool ignoreDirty = true)
    {
      int offset = 1;
      if (IsDirty || !ignoreDirty)
      {
        destination[0] = 1;
        if (_value is ISerializable serializable)
          offset += serializable.Serialize(destination[offset..]);
        else
        {
          var bytes = MemoryPackSerializer.Serialize(_value);
          BinaryPrimitives.WriteInt32LittleEndian(destination[offset..], bytes.Length);
          offset += 4;
          bytes.CopyTo(destination[offset..]);
          offset += bytes.Length;
        }
      }
      else
        destination[0] = 0;

      return offset;
    }

    public int Deserialize(ReadOnlySpan<byte> source)
    {
      int offset = 1;
      if (source.Length == 0)
        return 0;
      if (source[0] == 1)
      {
        if (_deserializeFactory != null)
          offset += _deserializeFactory(source[offset..], out _value);
        else if (_value is ISerializable serializable)
          offset += serializable.Deserialize(source[offset..]);
        else
        {
          var length = BinaryPrimitives.ReadInt32LittleEndian(source[offset..]);
          offset += 4;
          var data = MemoryPackSerializer.Deserialize<T>(source[offset..]);
          offset += length;
          if (data != null)
            _value = data;
        }
        IsDirty = true;
      }
      return offset;
    }
  }

  public class NetworkArrayField<T>(int capacity, DeserializeDelegate<T>? deserializeFactory = null) : ISerializable, IDirtyable
  {
    public T? this[int index]
    {
      get => _value[index];
      set
      {
        if (EqualityComparer<T>.Default.Equals(_value[index], value))
          return;

        _value[index] = value;
        _isDirtyItems[index] = true;
      }
    }
    public int Capacity
    {
      get => _value.Length;
      set
      {
        if (value == _value.Length)
          return;

        Array.Resize(ref _value, value);
        Array.Resize(ref _isDirtyItems, value);
        IsDirty = true;
      }
    }
    private T?[] _value = new T[capacity];
    private bool[] _isDirtyItems = new bool[capacity];
    public bool IsDirty
    {
      get
      {
        foreach (var item in _isDirtyItems)
        {
          if (item)
            return true;
        }
        return _isDirty;
      }
      set
      {
        _isDirty = value;
        Array.Fill(_isDirtyItems, value);
      }
    }
    private bool _isDirty = false;
    private readonly DeserializeDelegate<T>? _deserializeFactory = deserializeFactory;

    public void CopyFrom(NetworkArrayField<T> other)
    {
      if (Capacity != other.Capacity)
        Capacity = other.Capacity;

      for (int i = 0; i < other.Capacity; i++)
      {
        if (other.IsDirty)
          _value[i] = other._value[i];
      }

      IsDirty = false;
    }

    public int Serialize(Span<byte> destination, bool ignoreDirty = true)
    {
      if (IsDirty == false && ignoreDirty)
      {
        destination[0] = 0;
        return 1;
      }

      int offset = 1;
      destination[0] = 1;

      // capacity
      BinaryPrimitives.WriteInt32LittleEndian(destination.Slice(offset, 4), Capacity);
      offset += 4;
      var sizeOffset = offset;
      offset += 4;

      for (int i = 0; i < Capacity; i++)
      {
        if (_isDirtyItems[i] == false && ignoreDirty) continue;

        // copy index
        BinaryPrimitives.WriteInt32LittleEndian(destination.Slice(offset, 4), i);
        offset += 4;

        if (_value[i] == null)
        {
          BinaryPrimitives.WriteInt32LittleEndian(destination.Slice(offset, 4), 0);
          offset += 4;
          continue;
        }
        else if (_value[i] is ISerializable existingItem)
        {
          var bytesLengthOffset = offset;
          offset += 4;
          var bytesLength = existingItem.Serialize(destination[offset..]);
          offset += bytesLength;
          BinaryPrimitives.WriteInt32LittleEndian(destination[bytesLengthOffset..], bytesLength);
        }
        else
        {
          var bytes = MemoryPackSerializer.Serialize(_value[i]);
          // copy bytes length
          BinaryPrimitives.WriteInt32LittleEndian(destination.Slice(offset, 4), bytes.Length);
          offset += 4;

          // copy bytes
          bytes.CopyTo(destination.Slice(offset, bytes.Length));
          offset += bytes.Length;
        }
      }

      BinaryPrimitives.WriteInt32LittleEndian(destination.Slice(sizeOffset, 4), offset);

      return offset;
    }

    public int Deserialize(ReadOnlySpan<byte> source)
    {
      if (source.Length == 0)
        return 0;
      var isDirty = source[0] == 1;
      if (isDirty == false) return 1;

      int offset = 1;
      Capacity = BinaryPrimitives.ReadInt32LittleEndian(source.Slice(offset, 4));
      offset += 4;

      int bytesSize = BinaryPrimitives.ReadInt32LittleEndian(source.Slice(offset, 4));
      offset += 4;

      while (offset < bytesSize)
      {
        int index = BinaryPrimitives.ReadInt32LittleEndian(source.Slice(offset, 4));
        offset += 4;

        var bytesLength = BinaryPrimitives.ReadInt32LittleEndian(source.Slice(offset, 4));
        offset += 4;

        if (bytesLength == 0)
        {
          _value[index] = default;
          continue;
        }

        if (_deserializeFactory != null)
          offset += _deserializeFactory(source.Slice(offset, bytesLength), out _value[index]);
        else
        {
          _value[index] = MemoryPackSerializer.Deserialize<T>(source.Slice(offset, bytesLength));
          offset += bytesLength;
        }
        _isDirtyItems[index] = true;
      }

      return offset;
    }

    public void Add(T item)
    {
      Capacity++;
      _value[Capacity - 1] = item;
      _isDirtyItems[Capacity - 1] = true;
    }

    public void Remove(T item)
    {
      int index = Array.IndexOf(_value, item);
      Remove(index);
    }

    public void Remove(int index)
    {
      var lastItemIndex = Capacity - 1;
      if (index == lastItemIndex)
      {
        Capacity--;
        return;
      }
      (_value[lastItemIndex], _value[index]) = (_value[index], _value[lastItemIndex]);
      _isDirtyItems[index] = true;
      Capacity--;
    }

    public void Clear()
    {
      Capacity = 0;
    }
  }
}
Editor is loading...