mirror of
https://github.com/Facepunch/sbox-public.git
synced 2025-12-23 22:48:07 -05:00
NetList/NetDictionary Callbacks (#3556)
This commit is contained in:
@@ -4,6 +4,18 @@ using System.Collections.Specialized;
|
||||
|
||||
namespace Sandbox;
|
||||
|
||||
/// <summary>
|
||||
/// Describes a change to a <see cref="NetDictionary{TKey,TValue}"/> which is passed to
|
||||
/// <see cref="NetDictionary{TKey,TValue}.OnChanged"/> whenever its contents change.
|
||||
/// </summary>
|
||||
public struct NetDictionaryChangeEvent<TKey, TValue>
|
||||
{
|
||||
public NotifyCollectionChangedAction Type { get; set; }
|
||||
public TKey Key { get; set; }
|
||||
public TValue NewValue { get; set; }
|
||||
public TValue OldValue { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A networkable dictionary for use with the <see cref="SyncAttribute"/> and <see cref="HostSyncAttribute"/>. Only changes will be
|
||||
/// networked instead of sending the whole dictionary every time, so it's more efficient.
|
||||
@@ -36,34 +48,39 @@ public sealed class NetDictionary<TKey, TValue> : INetworkSerializer, INetworkRe
|
||||
public TValue Value { get; set; }
|
||||
}
|
||||
|
||||
private readonly ObservableDictionary<TKey, TValue> dictionary = new();
|
||||
private readonly List<Change> changes = new();
|
||||
/// <summary>
|
||||
/// Get notified when the dictionary is changed.
|
||||
/// </summary>
|
||||
public Action<NetDictionaryChangeEvent<TKey, TValue>> OnChanged;
|
||||
|
||||
private readonly ObservableDictionary<TKey, TValue> _dictionary = new();
|
||||
private readonly List<Change> _changes = new();
|
||||
|
||||
bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly => false;
|
||||
bool IDictionary.IsReadOnly => false;
|
||||
bool IDictionary.IsFixedSize => false;
|
||||
bool ICollection.IsSynchronized => false;
|
||||
object ICollection.SyncRoot => this;
|
||||
ICollection IDictionary.Values => (ICollection)dictionary.Values;
|
||||
ICollection IDictionary.Keys => (ICollection)dictionary.Keys;
|
||||
ICollection IDictionary.Values => (ICollection)_dictionary.Values;
|
||||
ICollection IDictionary.Keys => (ICollection)_dictionary.Keys;
|
||||
|
||||
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => dictionary.Values;
|
||||
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => dictionary.Keys;
|
||||
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => _dictionary.Values;
|
||||
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => _dictionary.Keys;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="ObservableDictionary{TKey,TValue}.Values"/>
|
||||
/// </summary>
|
||||
public ICollection<TValue> Values => dictionary.Values;
|
||||
public ICollection<TValue> Values => _dictionary.Values;
|
||||
|
||||
public NetDictionary()
|
||||
{
|
||||
dictionary.CollectionChanged += OnCollectionChanged;
|
||||
_dictionary.CollectionChanged += OnCollectionChanged;
|
||||
AddResetChange();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
changes.Clear();
|
||||
_changes.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -71,7 +88,7 @@ public sealed class NetDictionary<TKey, TValue> : INetworkSerializer, INetworkRe
|
||||
/// </summary>
|
||||
void ICollection.CopyTo( Array array, int index )
|
||||
{
|
||||
(dictionary as ICollection).CopyTo( array, index );
|
||||
(_dictionary as ICollection).CopyTo( array, index );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -106,7 +123,7 @@ public sealed class NetDictionary<TKey, TValue> : INetworkSerializer, INetworkRe
|
||||
if ( !CanWriteChanges() )
|
||||
return;
|
||||
|
||||
dictionary.Add( key, value );
|
||||
_dictionary.Add( key, value );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -117,7 +134,7 @@ public sealed class NetDictionary<TKey, TValue> : INetworkSerializer, INetworkRe
|
||||
if ( !CanWriteChanges() )
|
||||
return;
|
||||
|
||||
dictionary.Add( item );
|
||||
_dictionary.Add( item );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -128,7 +145,7 @@ public sealed class NetDictionary<TKey, TValue> : INetworkSerializer, INetworkRe
|
||||
if ( !CanWriteChanges() )
|
||||
return;
|
||||
|
||||
dictionary.Clear();
|
||||
_dictionary.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -136,7 +153,7 @@ public sealed class NetDictionary<TKey, TValue> : INetworkSerializer, INetworkRe
|
||||
/// </summary>
|
||||
public bool ContainsKey( TKey key )
|
||||
{
|
||||
return dictionary.ContainsKey( key );
|
||||
return _dictionary.ContainsKey( key );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -144,7 +161,7 @@ public sealed class NetDictionary<TKey, TValue> : INetworkSerializer, INetworkRe
|
||||
/// </summary>
|
||||
public bool Contains( KeyValuePair<TKey, TValue> item )
|
||||
{
|
||||
return dictionary.Contains( item );
|
||||
return _dictionary.Contains( item );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -152,12 +169,12 @@ public sealed class NetDictionary<TKey, TValue> : INetworkSerializer, INetworkRe
|
||||
/// </summary>
|
||||
public void CopyTo( KeyValuePair<TKey, TValue>[] array, int arrayIndex )
|
||||
{
|
||||
dictionary.CopyTo( array, arrayIndex );
|
||||
_dictionary.CopyTo( array, arrayIndex );
|
||||
}
|
||||
|
||||
public bool Remove( KeyValuePair<TKey, TValue> item )
|
||||
{
|
||||
return CanWriteChanges() && dictionary.Remove( item );
|
||||
return CanWriteChanges() && _dictionary.Remove( item );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -165,7 +182,7 @@ public sealed class NetDictionary<TKey, TValue> : INetworkSerializer, INetworkRe
|
||||
/// </summary>
|
||||
public ICollection<TKey> Keys
|
||||
{
|
||||
get { return dictionary.Keys; }
|
||||
get { return _dictionary.Keys; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -176,31 +193,31 @@ public sealed class NetDictionary<TKey, TValue> : INetworkSerializer, INetworkRe
|
||||
if ( !CanWriteChanges() )
|
||||
return false;
|
||||
|
||||
return dictionary.Remove( key );
|
||||
return _dictionary.Remove( key );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="ObservableDictionary{TKey,TValue}.TryGetValue"/>
|
||||
/// </summary>
|
||||
public bool TryGetValue( TKey key, out TValue value ) => dictionary.TryGetValue( key, out value );
|
||||
public bool TryGetValue( TKey key, out TValue value ) => _dictionary.TryGetValue( key, out value );
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="ObservableDictionary{TKey,TValue}.Count"/>
|
||||
/// </summary>
|
||||
public int Count => dictionary.Count;
|
||||
public int Count => _dictionary.Count;
|
||||
|
||||
public TValue this[TKey key]
|
||||
{
|
||||
get
|
||||
{
|
||||
return dictionary[key];
|
||||
return _dictionary[key];
|
||||
}
|
||||
set
|
||||
{
|
||||
if ( !CanWriteChanges() )
|
||||
return;
|
||||
|
||||
dictionary[key] = value;
|
||||
_dictionary[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,7 +232,7 @@ public sealed class NetDictionary<TKey, TValue> : INetworkSerializer, INetworkRe
|
||||
/// </summary>
|
||||
IDictionaryEnumerator IDictionary.GetEnumerator()
|
||||
{
|
||||
return ((IDictionary)dictionary).GetEnumerator();
|
||||
return ((IDictionary)_dictionary).GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -223,7 +240,7 @@ public sealed class NetDictionary<TKey, TValue> : INetworkSerializer, INetworkRe
|
||||
/// </summary>
|
||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
|
||||
{
|
||||
return dictionary.GetEnumerator();
|
||||
return _dictionary.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -231,7 +248,7 @@ public sealed class NetDictionary<TKey, TValue> : INetworkSerializer, INetworkRe
|
||||
/// </summary>
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable)dictionary).GetEnumerator();
|
||||
return ((IEnumerable)_dictionary).GetEnumerator();
|
||||
}
|
||||
|
||||
private INetworkProxy Parent { get; set; }
|
||||
@@ -244,7 +261,7 @@ public sealed class NetDictionary<TKey, TValue> : INetworkSerializer, INetworkRe
|
||||
/// <summary>
|
||||
/// Do we have any pending changes?
|
||||
/// </summary>
|
||||
bool INetworkSerializer.HasChanges => changes.Count > 0;
|
||||
bool INetworkSerializer.HasChanges => _changes.Count > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Write any changed items to a <see cref="ByteStream"/>.
|
||||
@@ -255,9 +272,9 @@ public sealed class NetDictionary<TKey, TValue> : INetworkSerializer, INetworkRe
|
||||
{
|
||||
// We are sending changes, not a full update. This flag indicates that.
|
||||
data.Write( false );
|
||||
data.Write( changes.Count );
|
||||
data.Write( _changes.Count );
|
||||
|
||||
foreach ( var change in changes )
|
||||
foreach ( var change in _changes )
|
||||
{
|
||||
data.Write( change.Type );
|
||||
WriteValue( change.Key, ref data );
|
||||
@@ -269,7 +286,7 @@ public sealed class NetDictionary<TKey, TValue> : INetworkSerializer, INetworkRe
|
||||
Log.Warning( e, $"Error when writing NetDictionary changes - {e.Message}" );
|
||||
}
|
||||
|
||||
changes.Clear();
|
||||
_changes.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -292,7 +309,7 @@ public sealed class NetDictionary<TKey, TValue> : INetworkSerializer, INetworkRe
|
||||
}
|
||||
|
||||
// Clear changes whenever we read data. We don't want to keep local changes.
|
||||
changes.Clear();
|
||||
_changes.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -304,9 +321,9 @@ public sealed class NetDictionary<TKey, TValue> : INetworkSerializer, INetworkRe
|
||||
{
|
||||
// We are sending a full update. This flag indicates that.
|
||||
data.Write( true );
|
||||
data.Write( dictionary.Count );
|
||||
data.Write( _dictionary.Count );
|
||||
|
||||
foreach ( var (k, v) in dictionary )
|
||||
foreach ( var (k, v) in _dictionary )
|
||||
{
|
||||
WriteValue( k, ref data );
|
||||
WriteValue( v, ref data );
|
||||
@@ -323,7 +340,7 @@ public sealed class NetDictionary<TKey, TValue> : INetworkSerializer, INetworkRe
|
||||
/// </summary>
|
||||
private void ReadAll( ref ByteStream data )
|
||||
{
|
||||
dictionary.Clear();
|
||||
_dictionary.Clear();
|
||||
|
||||
var count = data.Read<int>();
|
||||
|
||||
@@ -334,7 +351,7 @@ public sealed class NetDictionary<TKey, TValue> : INetworkSerializer, INetworkRe
|
||||
|
||||
if ( key is null ) continue;
|
||||
|
||||
dictionary[key] = value;
|
||||
_dictionary[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,7 +370,7 @@ public sealed class NetDictionary<TKey, TValue> : INetworkSerializer, INetworkRe
|
||||
|
||||
if ( type == NotifyCollectionChangedAction.Reset )
|
||||
{
|
||||
dictionary.Clear();
|
||||
_dictionary.Clear();
|
||||
}
|
||||
else if ( key is null )
|
||||
{
|
||||
@@ -361,21 +378,42 @@ public sealed class NetDictionary<TKey, TValue> : INetworkSerializer, INetworkRe
|
||||
}
|
||||
else if ( type == NotifyCollectionChangedAction.Add )
|
||||
{
|
||||
dictionary.Add( key, value );
|
||||
_dictionary.Add( key, value );
|
||||
}
|
||||
else if ( type == NotifyCollectionChangedAction.Remove )
|
||||
{
|
||||
dictionary.Remove( key );
|
||||
_dictionary.Remove( key );
|
||||
}
|
||||
else if ( type == NotifyCollectionChangedAction.Replace )
|
||||
{
|
||||
dictionary[key] = value;
|
||||
_dictionary[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
|
||||
{
|
||||
var changeEvent = new NetDictionaryChangeEvent<TKey, TValue>
|
||||
{
|
||||
Type = e.Action
|
||||
};
|
||||
|
||||
if ( e.OldItems is not null && e.OldItems.Count > 0 )
|
||||
{
|
||||
var (k, oldValue) = (KeyValuePair<TKey, TValue>)e.OldItems[0];
|
||||
changeEvent.OldValue = oldValue;
|
||||
changeEvent.Key = k;
|
||||
}
|
||||
|
||||
if ( e.NewItems is not null && e.NewItems.Count > 0 )
|
||||
{
|
||||
var (k, newValue) = (KeyValuePair<TKey, TValue>)e.NewItems[0];
|
||||
changeEvent.NewValue = newValue;
|
||||
changeEvent.Key = k;
|
||||
}
|
||||
|
||||
OnChanged?.InvokeWithWarning( changeEvent );
|
||||
|
||||
if ( !CanWriteChanges() )
|
||||
return;
|
||||
|
||||
@@ -383,13 +421,13 @@ public sealed class NetDictionary<TKey, TValue> : INetworkSerializer, INetworkRe
|
||||
{
|
||||
var (k, v) = (KeyValuePair<TKey, TValue>)e.NewItems[0];
|
||||
var change = new Change { Key = k, Value = v, Type = e.Action };
|
||||
changes.Add( change );
|
||||
_changes.Add( change );
|
||||
}
|
||||
else if ( e.Action == NotifyCollectionChangedAction.Remove )
|
||||
{
|
||||
var (k, v) = (KeyValuePair<TKey, TValue>)e.NewItems[0];
|
||||
var (k, v) = (KeyValuePair<TKey, TValue>)e.OldItems[0];
|
||||
var change = new Change { Key = k, Type = e.Action };
|
||||
changes.Add( change );
|
||||
_changes.Add( change );
|
||||
}
|
||||
else if ( e.Action == NotifyCollectionChangedAction.Reset )
|
||||
{
|
||||
@@ -397,19 +435,19 @@ public sealed class NetDictionary<TKey, TValue> : INetworkSerializer, INetworkRe
|
||||
}
|
||||
else if ( e.Action == NotifyCollectionChangedAction.Replace )
|
||||
{
|
||||
var (k, v) = (KeyValuePair<TKey, TValue>)e.NewItems[0];
|
||||
var change = new Change { Key = k, Type = e.Action, Value = v };
|
||||
changes.Add( change );
|
||||
var (k, newValue) = (KeyValuePair<TKey, TValue>)e.NewItems[0];
|
||||
var change = new Change { Key = k, Type = e.Action, Value = newValue };
|
||||
_changes.Add( change );
|
||||
}
|
||||
}
|
||||
|
||||
private T ReadValue<T>( ref ByteStream data )
|
||||
private static T ReadValue<T>( ref ByteStream data )
|
||||
{
|
||||
var value = Game.TypeLibrary.FromBytes<object>( ref data );
|
||||
return (T)value;
|
||||
}
|
||||
|
||||
private void WriteValue( object value, ref ByteStream data )
|
||||
private static void WriteValue( object value, ref ByteStream data )
|
||||
{
|
||||
Game.TypeLibrary.ToBytes( value, ref data );
|
||||
}
|
||||
@@ -419,16 +457,16 @@ public sealed class NetDictionary<TKey, TValue> : INetworkSerializer, INetworkRe
|
||||
private void AddResetChange()
|
||||
{
|
||||
var change = new Change { Type = NotifyCollectionChangedAction.Reset };
|
||||
changes.Add( change );
|
||||
_changes.Add( change );
|
||||
|
||||
foreach ( var (k, v) in dictionary )
|
||||
foreach ( var (k, v) in _dictionary )
|
||||
{
|
||||
// If a key is no longer valid, don't send it as a change, it'll be a null key on read.
|
||||
if ( k is IValid valid && !valid.IsValid() )
|
||||
continue;
|
||||
|
||||
change = new Change { Key = k, Value = v, Type = NotifyCollectionChangedAction.Add };
|
||||
changes.Add( change );
|
||||
_changes.Add( change );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,19 @@ using System.Collections.Specialized;
|
||||
|
||||
namespace Sandbox;
|
||||
|
||||
/// <summary>
|
||||
/// Describes a change to a <see cref="NetListChangeEvent{T}"/> which is passed to
|
||||
/// <see cref="NetList{T}.OnChanged"/> whenever its contents change.
|
||||
/// </summary>
|
||||
public struct NetListChangeEvent<T>
|
||||
{
|
||||
public NotifyCollectionChangedAction Type { get; set; }
|
||||
public int Index { get; set; }
|
||||
public int MovedIndex { get; set; }
|
||||
public T NewValue { get; set; }
|
||||
public T OldValue { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A networkable list for use with the <see cref="SyncAttribute"/> and <see cref="HostSyncAttribute"/>. Only changes will be
|
||||
/// networked instead of sending the whole list every time, so it's more efficient.
|
||||
@@ -37,18 +50,23 @@ public sealed class NetList<T> : INetworkSerializer, INetworkReliable, INetworkP
|
||||
public T Value { get; set; }
|
||||
}
|
||||
|
||||
private readonly ObservableCollection<T> list = new();
|
||||
private readonly List<Change> changes = new();
|
||||
/// <summary>
|
||||
/// Get notified when the list has changed.
|
||||
/// </summary>
|
||||
public Action<NetListChangeEvent<T>> OnChanged;
|
||||
|
||||
private readonly ObservableCollection<T> _list = new();
|
||||
private readonly List<Change> _changes = new();
|
||||
|
||||
public NetList()
|
||||
{
|
||||
list.CollectionChanged += OnCollectionChanged;
|
||||
_list.CollectionChanged += OnCollectionChanged;
|
||||
AddResetChange();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
changes.Clear();
|
||||
_changes.Clear();
|
||||
}
|
||||
|
||||
bool ICollection<T>.IsReadOnly => false;
|
||||
@@ -85,7 +103,7 @@ public sealed class NetList<T> : INetworkSerializer, INetworkReliable, INetworkP
|
||||
/// </summary>
|
||||
void ICollection.CopyTo( Array array, int index )
|
||||
{
|
||||
(list as ICollection).CopyTo( array, index );
|
||||
(_list as ICollection).CopyTo( array, index );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -93,7 +111,7 @@ public sealed class NetList<T> : INetworkSerializer, INetworkReliable, INetworkP
|
||||
/// </summary>
|
||||
bool IList.Contains( object value )
|
||||
{
|
||||
return list.Contains( (T)value );
|
||||
return _list.Contains( (T)value );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -101,7 +119,7 @@ public sealed class NetList<T> : INetworkSerializer, INetworkReliable, INetworkP
|
||||
/// </summary>
|
||||
int IList.IndexOf( object value )
|
||||
{
|
||||
return list.IndexOf( (T)value );
|
||||
return _list.IndexOf( (T)value );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -128,7 +146,7 @@ public sealed class NetList<T> : INetworkSerializer, INetworkReliable, INetworkP
|
||||
if ( !CanWriteChanges() )
|
||||
return;
|
||||
|
||||
list.Clear();
|
||||
_list.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -136,7 +154,7 @@ public sealed class NetList<T> : INetworkSerializer, INetworkReliable, INetworkP
|
||||
/// </summary>
|
||||
public bool Contains( T item )
|
||||
{
|
||||
return list.Contains( item );
|
||||
return _list.Contains( item );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -144,7 +162,7 @@ public sealed class NetList<T> : INetworkSerializer, INetworkReliable, INetworkP
|
||||
/// </summary>
|
||||
public void CopyTo( T[] array, int arrayIndex )
|
||||
{
|
||||
list.CopyTo( array, arrayIndex );
|
||||
_list.CopyTo( array, arrayIndex );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -155,7 +173,7 @@ public sealed class NetList<T> : INetworkSerializer, INetworkReliable, INetworkP
|
||||
if ( !CanWriteChanges() )
|
||||
return;
|
||||
|
||||
list.Add( value );
|
||||
_list.Add( value );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -168,7 +186,7 @@ public sealed class NetList<T> : INetworkSerializer, INetworkReliable, INetworkP
|
||||
|
||||
foreach ( var value in collection )
|
||||
{
|
||||
list.Add( value );
|
||||
_list.Add( value );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,7 +195,7 @@ public sealed class NetList<T> : INetworkSerializer, INetworkReliable, INetworkP
|
||||
/// </summary>
|
||||
public bool Remove( T value )
|
||||
{
|
||||
return CanWriteChanges() && list.Remove( value );
|
||||
return CanWriteChanges() && _list.Remove( value );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -185,7 +203,7 @@ public sealed class NetList<T> : INetworkSerializer, INetworkReliable, INetworkP
|
||||
/// </summary>
|
||||
public int IndexOf( T item )
|
||||
{
|
||||
return list.IndexOf( item );
|
||||
return _list.IndexOf( item );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -196,7 +214,7 @@ public sealed class NetList<T> : INetworkSerializer, INetworkReliable, INetworkP
|
||||
if ( !CanWriteChanges() )
|
||||
return;
|
||||
|
||||
list.Insert( index, value );
|
||||
_list.Insert( index, value );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -207,26 +225,26 @@ public sealed class NetList<T> : INetworkSerializer, INetworkReliable, INetworkP
|
||||
if ( !CanWriteChanges() )
|
||||
return;
|
||||
|
||||
list.RemoveAt( index );
|
||||
_list.RemoveAt( index );
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="List{T}.Count"/>
|
||||
/// </summary>
|
||||
public int Count => list.Count;
|
||||
public int Count => _list.Count;
|
||||
|
||||
public T this[int key]
|
||||
{
|
||||
get
|
||||
{
|
||||
return list[key];
|
||||
return _list[key];
|
||||
}
|
||||
set
|
||||
{
|
||||
if ( !CanWriteChanges() )
|
||||
return;
|
||||
|
||||
list[key] = value;
|
||||
_list[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,7 +253,7 @@ public sealed class NetList<T> : INetworkSerializer, INetworkReliable, INetworkP
|
||||
/// </summary>
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return list.GetEnumerator();
|
||||
return _list.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -243,7 +261,7 @@ public sealed class NetList<T> : INetworkSerializer, INetworkReliable, INetworkP
|
||||
/// </summary>
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable)list).GetEnumerator();
|
||||
return ((IEnumerable)_list).GetEnumerator();
|
||||
}
|
||||
|
||||
private INetworkProxy Parent { get; set; }
|
||||
@@ -256,7 +274,7 @@ public sealed class NetList<T> : INetworkSerializer, INetworkReliable, INetworkP
|
||||
/// <summary>
|
||||
/// Do we have any pending changes?
|
||||
/// </summary>
|
||||
bool INetworkSerializer.HasChanges => changes.Count > 0;
|
||||
bool INetworkSerializer.HasChanges => _changes.Count > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Write any changed items to a <see cref="ByteStream"/>.
|
||||
@@ -267,9 +285,9 @@ public sealed class NetList<T> : INetworkSerializer, INetworkReliable, INetworkP
|
||||
{
|
||||
// We are sending changes, not a full update. This flag indicates that.
|
||||
data.Write( false );
|
||||
data.Write( changes.Count );
|
||||
data.Write( _changes.Count );
|
||||
|
||||
foreach ( var change in changes )
|
||||
foreach ( var change in _changes )
|
||||
{
|
||||
data.Write( change.Type );
|
||||
data.Write( change.Index );
|
||||
@@ -282,7 +300,7 @@ public sealed class NetList<T> : INetworkSerializer, INetworkReliable, INetworkP
|
||||
Log.Warning( e, $"Error when writing NetList changes - {e.Message}" );
|
||||
}
|
||||
|
||||
changes.Clear();
|
||||
_changes.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -305,7 +323,7 @@ public sealed class NetList<T> : INetworkSerializer, INetworkReliable, INetworkP
|
||||
}
|
||||
|
||||
// Clear changes whenever we read data. We don't want to keep local changes.
|
||||
changes.Clear();
|
||||
_changes.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -317,9 +335,9 @@ public sealed class NetList<T> : INetworkSerializer, INetworkReliable, INetworkP
|
||||
{
|
||||
// We are sending a full update. This flag indicates that.
|
||||
data.Write( true );
|
||||
data.Write( list.Count );
|
||||
data.Write( _list.Count );
|
||||
|
||||
foreach ( var item in list )
|
||||
foreach ( var item in _list )
|
||||
{
|
||||
WriteValue( item, ref data );
|
||||
}
|
||||
@@ -336,14 +354,14 @@ public sealed class NetList<T> : INetworkSerializer, INetworkReliable, INetworkP
|
||||
/// <param name="data"></param>
|
||||
private void ReadAll( ref ByteStream data )
|
||||
{
|
||||
list.Clear();
|
||||
_list.Clear();
|
||||
|
||||
var count = data.Read<int>();
|
||||
|
||||
for ( var i = 0; i < count; i++ )
|
||||
{
|
||||
var value = ReadValue( ref data );
|
||||
list.Add( value );
|
||||
_list.Add( value );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -363,45 +381,64 @@ public sealed class NetList<T> : INetworkSerializer, INetworkReliable, INetworkP
|
||||
|
||||
if ( type == NotifyCollectionChangedAction.Add )
|
||||
{
|
||||
if ( index >= 0 && index <= list.Count )
|
||||
list.Insert( index, value );
|
||||
if ( index >= 0 && index <= _list.Count )
|
||||
_list.Insert( index, value );
|
||||
else
|
||||
list.Add( value );
|
||||
_list.Add( value );
|
||||
}
|
||||
else if ( type == NotifyCollectionChangedAction.Remove )
|
||||
{
|
||||
if ( index >= 0 && index < list.Count )
|
||||
list.RemoveAt( index );
|
||||
if ( index >= 0 && index < _list.Count )
|
||||
{
|
||||
var element = _list.ElementAt( index );
|
||||
_list.RemoveAt( index );
|
||||
}
|
||||
}
|
||||
else if ( type == NotifyCollectionChangedAction.Reset )
|
||||
{
|
||||
list.Clear();
|
||||
_list.Clear();
|
||||
}
|
||||
else if ( type == NotifyCollectionChangedAction.Replace )
|
||||
{
|
||||
list[index] = value;
|
||||
if ( index >= 0 && index < _list.Count )
|
||||
{
|
||||
var element = _list.ElementAt( index );
|
||||
}
|
||||
|
||||
_list[index] = value;
|
||||
}
|
||||
else if ( type == NotifyCollectionChangedAction.Move )
|
||||
{
|
||||
list.Move( index, movedIndex );
|
||||
_list.Move( index, movedIndex );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
|
||||
{
|
||||
var changeEvent = new NetListChangeEvent<T>
|
||||
{
|
||||
Type = e.Action,
|
||||
Index = e.Action == NotifyCollectionChangedAction.Add ? e.NewStartingIndex : e.OldStartingIndex,
|
||||
MovedIndex = e.NewStartingIndex,
|
||||
OldValue = (e.OldItems is not null && e.OldItems.Count > 0) ? (T)e.OldItems[0] : default,
|
||||
NewValue = (e.NewItems is not null && e.NewItems.Count > 0) ? (T)e.NewItems[0] : default
|
||||
};
|
||||
|
||||
OnChanged?.InvokeWithWarning( changeEvent );
|
||||
|
||||
if ( !CanWriteChanges() )
|
||||
return;
|
||||
|
||||
if ( e.Action == NotifyCollectionChangedAction.Add )
|
||||
{
|
||||
var change = new Change { Index = e.NewStartingIndex, Value = (T)e.NewItems[0], Type = e.Action };
|
||||
changes.Add( change );
|
||||
_changes.Add( change );
|
||||
}
|
||||
else if ( e.Action == NotifyCollectionChangedAction.Remove )
|
||||
{
|
||||
var change = new Change { Index = e.OldStartingIndex, Type = e.Action };
|
||||
changes.Add( change );
|
||||
_changes.Add( change );
|
||||
}
|
||||
else if ( e.Action == NotifyCollectionChangedAction.Reset )
|
||||
{
|
||||
@@ -410,22 +447,22 @@ public sealed class NetList<T> : INetworkSerializer, INetworkReliable, INetworkP
|
||||
else if ( e.Action == NotifyCollectionChangedAction.Replace )
|
||||
{
|
||||
var change = new Change { Index = e.OldStartingIndex, Type = e.Action, Value = (T)e.NewItems[0] };
|
||||
changes.Add( change );
|
||||
_changes.Add( change );
|
||||
}
|
||||
else if ( e.Action == NotifyCollectionChangedAction.Move )
|
||||
{
|
||||
var change = new Change { Index = e.OldStartingIndex, MovedIndex = e.NewStartingIndex, Type = e.Action };
|
||||
changes.Add( change );
|
||||
_changes.Add( change );
|
||||
}
|
||||
}
|
||||
|
||||
private T ReadValue( ref ByteStream data )
|
||||
private static T ReadValue( ref ByteStream data )
|
||||
{
|
||||
var value = Game.TypeLibrary.FromBytes<object>( ref data );
|
||||
return (T)value;
|
||||
}
|
||||
|
||||
private void WriteValue( T value, ref ByteStream data )
|
||||
private static void WriteValue( T value, ref ByteStream data )
|
||||
{
|
||||
Game.TypeLibrary.ToBytes( value, ref data );
|
||||
}
|
||||
@@ -435,13 +472,13 @@ public sealed class NetList<T> : INetworkSerializer, INetworkReliable, INetworkP
|
||||
private void AddResetChange()
|
||||
{
|
||||
var change = new Change { Type = NotifyCollectionChangedAction.Reset };
|
||||
changes.Add( change );
|
||||
_changes.Add( change );
|
||||
|
||||
for ( var i = 0; i < list.Count; i++ )
|
||||
for ( var i = 0; i < _list.Count; i++ )
|
||||
{
|
||||
var item = list[i];
|
||||
change = new() { Index = -1, Value = item, Type = NotifyCollectionChangedAction.Add };
|
||||
changes.Add( change );
|
||||
var item = _list[i];
|
||||
change = new Change { Index = -1, Value = item, Type = NotifyCollectionChangedAction.Add };
|
||||
_changes.Add( change );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,8 +76,7 @@ public class ObservableDictionary<TKey, TValue> : IDictionary<TKey, TValue>, INo
|
||||
Dictionary.TryGetValue( key, out value );
|
||||
var removed = Dictionary.Remove( key );
|
||||
if ( removed )
|
||||
//OnCollectionChanged(NotifyCollectionChangedAction.Remove, new KeyValuePair<TKey, TValue>(key, value));
|
||||
OnCollectionChanged();
|
||||
OnCollectionChanged( NotifyCollectionChangedAction.Remove, new KeyValuePair<TKey, TValue>( key, value ) );
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace Networking;
|
||||
|
||||
@@ -54,6 +55,107 @@ public class NetDictionary
|
||||
Assert.AreEqual( 3, dictionary["c"] );
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void OnChangedIsInvokedWhenItemIsAdded()
|
||||
{
|
||||
var dict = new NetDictionary<string, int>();
|
||||
|
||||
var callCount = 0;
|
||||
NetDictionaryChangeEvent<string, int> receivedEvent = default;
|
||||
|
||||
dict.OnChanged = ev =>
|
||||
{
|
||||
callCount++;
|
||||
receivedEvent = ev;
|
||||
};
|
||||
|
||||
dict.Add( "foo", 42 );
|
||||
|
||||
Assert.AreEqual( 1, callCount );
|
||||
Assert.AreEqual( NotifyCollectionChangedAction.Add, receivedEvent.Type );
|
||||
Assert.AreEqual( "foo", receivedEvent.Key );
|
||||
Assert.AreEqual( 42, receivedEvent.NewValue );
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void OnChangedIsInvokedWhenItemIsRemoved()
|
||||
{
|
||||
var dict = new NetDictionary<string, int>();
|
||||
|
||||
dict.Add( "foo", 10 );
|
||||
dict.Add( "bar", 20 );
|
||||
|
||||
var callCount = 0;
|
||||
NetDictionaryChangeEvent<string, int> receivedEvent = default;
|
||||
|
||||
dict.OnChanged = ev =>
|
||||
{
|
||||
callCount++;
|
||||
receivedEvent = ev;
|
||||
};
|
||||
|
||||
dict.Remove( "foo" );
|
||||
|
||||
Assert.AreEqual( 1, callCount );
|
||||
Assert.AreEqual( NotifyCollectionChangedAction.Remove, receivedEvent.Type );
|
||||
Assert.AreEqual( "foo", receivedEvent.Key );
|
||||
Assert.AreEqual( 10, receivedEvent.OldValue );
|
||||
Assert.IsFalse( dict.ContainsKey( "foo" ) );
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void OnChangedIsInvokedWhenDictionaryIsCleared()
|
||||
{
|
||||
var dict = new NetDictionary<string, int>();
|
||||
|
||||
dict.Add( "foo", 1 );
|
||||
dict.Add( "bar", 2 );
|
||||
|
||||
var callCount = 0;
|
||||
NetDictionaryChangeEvent<string, int> receivedEvent = default;
|
||||
|
||||
dict.OnChanged = ev =>
|
||||
{
|
||||
callCount++;
|
||||
receivedEvent = ev;
|
||||
};
|
||||
|
||||
dict.Clear();
|
||||
|
||||
Assert.AreEqual( 1, callCount );
|
||||
Assert.AreEqual( NotifyCollectionChangedAction.Reset, receivedEvent.Type );
|
||||
Assert.AreEqual( 0, dict.Count );
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ReplaceInvokesWithCorrectValues()
|
||||
{
|
||||
var dict = new NetDictionary<string, int>();
|
||||
|
||||
dict.Add( "foo", 10 );
|
||||
|
||||
var callCount = 0;
|
||||
NetDictionaryChangeEvent<string, int> receivedEvent = default;
|
||||
|
||||
dict.OnChanged = ev =>
|
||||
{
|
||||
callCount++;
|
||||
receivedEvent = ev;
|
||||
};
|
||||
|
||||
// This should represent a Replace: old 10 -> new 99
|
||||
dict["foo"] = 99;
|
||||
|
||||
Assert.AreEqual( 1, callCount );
|
||||
|
||||
Assert.AreEqual( NotifyCollectionChangedAction.Replace, receivedEvent.Type );
|
||||
Assert.AreEqual( "foo", receivedEvent.Key );
|
||||
Assert.AreEqual( 10, receivedEvent.OldValue );
|
||||
Assert.AreEqual( 99, receivedEvent.NewValue );
|
||||
|
||||
Assert.AreEqual( 99, dict["foo"] );
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ValidAccess()
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace Networking;
|
||||
|
||||
@@ -40,6 +41,96 @@ public class NetList
|
||||
Assert.AreEqual( 3, list[2] );
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void OnChangedIsInvokedWhenItemIsAdded()
|
||||
{
|
||||
var list = new NetList<int>();
|
||||
|
||||
var callCount = 0;
|
||||
NetListChangeEvent<int> receivedEvent = default;
|
||||
|
||||
list.OnChanged = change =>
|
||||
{
|
||||
callCount++;
|
||||
receivedEvent = change;
|
||||
};
|
||||
|
||||
list.Add( 42 );
|
||||
|
||||
Assert.AreEqual( 1, callCount );
|
||||
Assert.AreEqual( NotifyCollectionChangedAction.Add, receivedEvent.Type );
|
||||
Assert.AreEqual( 0, receivedEvent.Index );
|
||||
Assert.AreEqual( 42, receivedEvent.NewValue );
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void OnChangedIsInvokedWhenItemIsRemoved()
|
||||
{
|
||||
var list = new NetList<int>();
|
||||
|
||||
list.Add( 10 );
|
||||
list.Add( 20 );
|
||||
|
||||
var callCount = 0;
|
||||
NetListChangeEvent<int> receivedEvent = default;
|
||||
|
||||
list.OnChanged = change =>
|
||||
{
|
||||
callCount++;
|
||||
receivedEvent = change;
|
||||
};
|
||||
|
||||
list.Remove( 10 );
|
||||
|
||||
Assert.AreEqual( 1, callCount );
|
||||
Assert.AreEqual( NotifyCollectionChangedAction.Remove, receivedEvent.Type );
|
||||
Assert.AreEqual( 0, receivedEvent.Index ); // 10 was at index 0
|
||||
Assert.AreEqual( 10, receivedEvent.OldValue ); // removed value
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void OnChangedIsInvokedWhenListIsCleared()
|
||||
{
|
||||
var list = new NetList<int>();
|
||||
|
||||
list.Add( 1 );
|
||||
list.Add( 2 );
|
||||
|
||||
var callCount = 0;
|
||||
NetListChangeEvent<int> receivedEvent = default;
|
||||
|
||||
list.OnChanged = change =>
|
||||
{
|
||||
callCount++;
|
||||
receivedEvent = change;
|
||||
};
|
||||
|
||||
list.Clear();
|
||||
|
||||
Assert.AreEqual( 1, callCount );
|
||||
Assert.AreEqual( NotifyCollectionChangedAction.Reset, receivedEvent.Type );
|
||||
Assert.AreEqual( 0, list.Count );
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void OnChangedIsNotInvokedWhenNoChangeOccurs()
|
||||
{
|
||||
var list = new NetList<int>();
|
||||
var callCount = 0;
|
||||
|
||||
list.OnChanged = _ =>
|
||||
{
|
||||
callCount++;
|
||||
};
|
||||
|
||||
list.Add( 5 );
|
||||
|
||||
// Removing an item that does not exist should not trigger a change
|
||||
list.Remove( 999 );
|
||||
|
||||
Assert.AreEqual( 1, callCount );
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ValidAccess()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user