Skip to content

Release 0.6.2 #9

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ All notable changes to this package will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [0.6.2] - 2024-11-02

- Added the *ObservableUpdateFlag* to help performance when updating subscribers to the *ObservableDictionary*. By default is set *ObservableUpdateFlag.KeyUpdateOnly*

**Fix**:
- Fixed an issue that would no setup Remove update action to Subscribers when calling *Clear* on the *ObservableDictionary*

## [0.6.1] - 2024-11-01

**Fix**:
Expand Down
78 changes: 65 additions & 13 deletions Runtime/ObservableDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@

namespace GameLovers
{
public enum ObservableUpdateFlag
{
// Updates all subsribers that didn't specify the key index
UpdateOnly,
// Updates only for subscripers that added their key index
Comment on lines +14 to +16
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Correct typos in enum member comments

There are spelling errors in the comments for the ObservableUpdateFlag enum members:

  • Line 14: "subsribers" should be "subscribers"
  • Line 16: "subscripers" should be "subscribers"

KeyUpdateOnly,
// Updates all types of subscribers [This has a high performance cost]
Both
}

/// <summary>
/// A simple dictionary with the possibility to observe changes to it's elements defined <see cref="ObservableUpdateType"/> rules
/// </summary>
Expand All @@ -18,6 +28,11 @@ public interface IObservableDictionary : IEnumerable
/// Requests the element count of this dictionary
/// </summary>
int Count { get; }

/// <summary>
/// Defines the configuration for the observable update done when updating elements in this dictionary
/// </summary>
ObservableUpdateFlag ObservableUpdateFlag { get; set; }
}

/// <inheritdoc cref="IObservableDictionary"/>
Expand Down Expand Up @@ -45,23 +60,35 @@ public interface IObservableDictionaryReader<TKey, TValue> : IObservableDictiona
/// <summary>
/// Observes to this dictionary changes with the given <paramref name="onUpdate"/>
/// </summary>
/// <remarks>
/// It needs the <see cref="this.ObservableUpdateFlag"/> to NOT be set as <see cref="ObservableUpdateFlag.KeyUpdateOnly"/>
/// </remarks>
void Observe(Action<TKey, TValue, TValue, ObservableUpdateType> onUpdate);

/// <summary>
/// Observes to this dictionary changes with the given <paramref name="onUpdate"/> when the given <paramref name="key"/>
/// data changes
/// </summary>
/// <remarks>
/// It needs the <see cref="this.ObservableUpdateFlag"/> to NOT be set as <see cref="ObservableUpdateFlag.UpdateOnly"/>
/// </remarks>
void Observe(TKey key, Action<TKey, TValue, TValue, ObservableUpdateType> onUpdate);

/// <inheritdoc cref="Observe(TKey,System.Action{TKey,TValue,TValue,FirstLight.ObservableUpdateType})" />
/// <remarks>
/// It invokes the given <paramref name="onUpdate"/> method before starting to observe to this dictionary
/// </remarks>
/// <remarks>
/// It needs the <see cref="this.ObservableUpdateFlag"/> to NOT be set as <see cref="ObservableUpdateFlag.UpdateOnly"/>
/// </remarks>
void InvokeObserve(TKey key, Action<TKey, TValue, TValue, ObservableUpdateType> onUpdate);

/// <summary>
/// Stops observing this dictionary with the given <paramref name="onUpdate"/> of any data changes
/// </summary>
/// <remarks>
/// It needs the <see cref="this.ObservableUpdateFlag"/> to NOT be set as <see cref="ObservableUpdateFlag.KeyUpdateOnly"/>
/// </remarks>
void StopObserving(Action<TKey, TValue, TValue, ObservableUpdateType> onUpdate);

/// <summary>
Expand Down Expand Up @@ -173,15 +200,18 @@ public class ObservableDictionary<TKey, TValue> : IObservableDictionary<TKey, TV
/// <inheritdoc />
public int Count => Dictionary.Count;
/// <inheritdoc />
public ObservableUpdateFlag ObservableUpdateFlag { get; set; }
/// <inheritdoc />
public ReadOnlyDictionary<TKey, TValue> ReadOnlyDictionary => new ReadOnlyDictionary<TKey, TValue>(Dictionary);

protected virtual IDictionary<TKey, TValue> Dictionary { get; }

protected ObservableDictionary() { }
private ObservableDictionary() { }

public ObservableDictionary(IDictionary<TKey, TValue> dictionary)
{
Dictionary = dictionary;
ObservableUpdateFlag = ObservableUpdateFlag.KeyUpdateOnly;
}

/// <inheritdoc cref="Dictionary{TKey,TValue}.this" />
Expand Down Expand Up @@ -227,17 +257,20 @@ public virtual void Add(TKey key, TValue value)
{
Dictionary.Add(key, value);

if (_keyUpdateActions.TryGetValue(key, out var actions))
if (ObservableUpdateFlag != ObservableUpdateFlag.UpdateOnly && _keyUpdateActions.TryGetValue(key, out var actions))
{
for (var i = 0; i < actions.Count; i++)
{
actions[i](key, default, value, ObservableUpdateType.Added);
}
}

for (var i = 0; i < _updateActions.Count; i++)
if (ObservableUpdateFlag != ObservableUpdateFlag.KeyUpdateOnly)
{
_updateActions[i](key, default, value, ObservableUpdateType.Added);
for (var i = 0; i < _updateActions.Count; i++)
{
_updateActions[i](key, default, value, ObservableUpdateType.Added);
}
}
}

Expand All @@ -251,17 +284,19 @@ public virtual bool Remove(TKey key)

Dictionary.Remove(key);

if (_keyUpdateActions.TryGetValue(key, out var actions))
if (ObservableUpdateFlag != ObservableUpdateFlag.UpdateOnly && _keyUpdateActions.TryGetValue(key, out var actions))
{
for (var i = 0; i < actions.Count; i++)
{
actions[i](key, value, default, ObservableUpdateType.Removed);
}
}

for (var i = 0; i < _updateActions.Count; i++)
if (ObservableUpdateFlag != ObservableUpdateFlag.KeyUpdateOnly)
{
_updateActions[i](key, value, default, ObservableUpdateType.Removed);
for (var i = 0; i < _updateActions.Count; i++)
{
_updateActions[i](key, value, default, ObservableUpdateType.Removed);
}
}

return true;
Expand All @@ -274,11 +309,25 @@ public virtual void Clear()

Dictionary.Clear();

for (var i = 0; i < _updateActions.Count; i++)
if (ObservableUpdateFlag != ObservableUpdateFlag.UpdateOnly)
{
foreach (var data in _keyUpdateActions)
{
for (var i = 0; i < data.Value.Count; i++)
{
data.Value[i](data.Key, dictionary[data.Key], default, ObservableUpdateType.Removed);
}
}
}
Comment on lines +314 to +321
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Prevent possible KeyNotFoundException in Clear method

When invoking key-specific update actions during Clear, if a key in _keyUpdateActions is not present in the dictionary snapshot, accessing dictionary[data.Key] may throw a KeyNotFoundException. This can occur if observers are registered for keys not present in the dictionary.

Apply this diff to fix the issue:

-						data.Value[i](data.Key, dictionary[data.Key], default, ObservableUpdateType.Removed);
+						if (dictionary.TryGetValue(data.Key, out var previousValue))
+						{
+							data.Value[i](data.Key, previousValue, default, ObservableUpdateType.Removed);
+						}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
foreach (var data in _keyUpdateActions)
{
for (var i = 0; i < data.Value.Count; i++)
{
data.Value[i](data.Key, dictionary[data.Key], default, ObservableUpdateType.Removed);
}
}
}
foreach (var data in _keyUpdateActions)
{
for (var i = 0; i < data.Value.Count; i++)
{
if (dictionary.TryGetValue(data.Key, out var previousValue))
{
data.Value[i](data.Key, previousValue, default, ObservableUpdateType.Removed);
}
}
}
}


if (ObservableUpdateFlag != ObservableUpdateFlag.KeyUpdateOnly)
{
foreach (var data in dictionary)
{
_updateActions[i](data.Key, data.Value, default, ObservableUpdateType.Removed);
for (var i = 0; i < _updateActions.Count; i++)
{
_updateActions[i](data.Key, data.Value, default, ObservableUpdateType.Removed);
}
}
}
}
Expand Down Expand Up @@ -380,17 +429,20 @@ protected void InvokeUpdate(TKey key, TValue previousValue)
{
var value = Dictionary[key];

if (_keyUpdateActions.TryGetValue(key, out var actions))
if (ObservableUpdateFlag != ObservableUpdateFlag.UpdateOnly && _keyUpdateActions.TryGetValue(key, out var actions))
{
for (var i = 0; i < actions.Count; i++)
{
actions[i](key, previousValue, value, ObservableUpdateType.Updated);
}
}

for (var i = 0; i < _updateActions.Count; i++)
if (ObservableUpdateFlag != ObservableUpdateFlag.KeyUpdateOnly)
{
_updateActions[i](key, previousValue, value, ObservableUpdateType.Updated);
for (var i = 0; i < _updateActions.Count; i++)
{
_updateActions[i](key, previousValue, value, ObservableUpdateType.Updated);
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "com.gamelovers.dataextensions",
"displayName": "Unity Data Type Extensions",
"author": "Miguel Tomas",
"version": "0.6.1",
"version": "0.6.2",
"unity": "2022.3",
"license": "MIT",
"description": "This package extends various sets of data types to be used in any type of data containers or persistent serializable data",
Expand Down
Loading