NetList/NetDictionary Callbacks (#3556)

This commit is contained in:
Conna Wiles
2025-12-12 11:07:55 +00:00
committed by GitHub
parent a28a2b7fac
commit c570971a1d
5 changed files with 368 additions and 101 deletions

View File

@@ -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 );
}
}
}

View File

@@ -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 );
}
}
}

View File

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

View File

@@ -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()
{

View File

@@ -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()
{