Skip to content

Release 0.6.0 #7

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
Oct 10, 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
29 changes: 27 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,38 @@ 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.0] - 2023-08-05

- Improved the *ObservableResolverList* and *ObservableResolverDictionary* data types to properly resolve lists and dictionaries with different data types from the original collection.

## [0.5.1] - 2023-09-04
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Inconsistent version date.

The date for version 0.5.1 (2023-09-04) is later than the date for version 0.6.0 (2023-08-05). This appears to be an inconsistency in the changelog. Please verify and adjust the dates to maintain chronological order.


- Added StructPair data type to support both object and struct type containers, improving memory usage performance.

**Fix**:
- Fixed the dispose extension methods for GameObject and Object, removing pragma directives and adding null reference check in GetValid method to avoid unwanted exceptions

## [0.5.0] - 2023-08-05

- Added **floatP**, a deterministic floating-point number type, enhancing precision and predictability in mathematical operations. Including arithmetic and comparison operators for floatP to support complex calculations and conversion methods between floatP and float types.

## [0.4.0] - 2023-07-30

- Added utility methods and extensions for Unity's Object and GameObject types, enhancing the codebase's functionality.
- Introduced a SerializableType struct for viewing, modifying, and saving types from the inspector, with serialization support and compatibility with filter attributes.
Comment on lines +24 to +25

Choose a reason for hiding this comment

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

Image description CodeRabbit

The description states that the assertions in the floatPTests class have been adjusted to cast float values explicitly to floatP. However, the code changes in this hunk are missing, so it's difficult to provide a thorough review. Please include the actual code changes for a more accurate review.

Changelog:


## [0.3.0] - 2023-07-28

- Added support for observing field updates with previous and current values in the ObservableField class.
- Introduced a UnitySerializedDictionary class that allows serialization of dictionaries in Unity.

## [0.2.0] - 2020-09-28

- Removed *ObservableIdList* because it's behaviour was too confusing and the same result can be obtained with *ObservableList* or *ObservableDictionary*
- Added new *ObservableResolverList*, *ObservableResolverDictionary* & *ObservableResolverField* to allow to create observable types without referencing the collection directly
- Added Unit tests to all types
- Added Unit tests to all data types in the project
Comment on lines 34 to +35

Choose a reason for hiding this comment

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

Image description CodeRabbit

The changelog entry for version 0.6.0 mentions improvements to the ObservableResolverList and ObservableResolverDictionary data types. However, the actual changes made to these data types are not mentioned in the changelog. It would be helpful to provide more details about the specific improvements made to these data types.

Overall, please provide the missing code changes for a more accurate review of the logic, performance, and other aspects of the code.


**Changed**:
- Removed *ObservableIdList* because it's behaviour was too confusing and the same result can be obtained with *ObservableList* or *ObservableDictionary*
- Removed all Pair Data and moved them to new *Pair<Key,Value>* serialized type that can now be serializable on Unity 2020.1
- Moved all Vector2, Vector3 & Vector4 extensions to the ValueData file

Expand Down
188 changes: 179 additions & 9 deletions Runtime/ObservableDictionary.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using NUnit.Framework;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using UnityEngine.UIElements;

// ReSharper disable once CheckNamespace

Expand All @@ -19,6 +21,9 @@ public interface IObservableDictionary : IEnumerable
}

/// <inheritdoc cref="IObservableDictionary"/>
/// <remarks>
/// This dictionary only allows to read the elements in it and not to modify it
/// </remarks>
public interface IObservableDictionaryReader<TKey, TValue> : IObservableDictionary, IEnumerable<KeyValuePair<TKey, TValue>>
{
/// <summary>
Expand Down Expand Up @@ -71,7 +76,7 @@ public interface IObservableDictionaryReader<TKey, TValue> : IObservableDictiona
void StopObservingAll(object subscriber = null);
}

/// <inheritdoc />
/// <inheritdoc cref="IObservableDictionary"/>
public interface IObservableDictionary<TKey, TValue> : IObservableDictionaryReader<TKey, TValue>
{
/// <summary>
Expand All @@ -86,12 +91,77 @@ public interface IObservableDictionary<TKey, TValue> : IObservableDictionaryRead
/// <inheritdoc cref="Dictionary{TKey,TValue}.Remove" />
bool Remove(TKey key);

/// <inheritdoc cref="Dictionary{TKey,TValue}.Clear"/>
void Clear();

/// <remarks>
/// It invokes any update method that is observing to the given <paramref name="key"/> on this dictionary
/// </remarks>
void InvokeUpdate(TKey key);
}

/// <inheritdoc />
/// <remarks>
/// This interface resolves between 2 dictionaries with different types of keys and values
/// </remarks>
public interface IObservableResolverDictionaryReader<TKey, TValue, TKeyOrigin, TValueOrigin> :
IObservableDictionaryReader<TKey, TValue>
{
/// <summary>
/// The Original Dictionary that is being resolved across the entire interface
/// </summary>
ReadOnlyDictionary<TKeyOrigin, TValueOrigin> OriginDictionary { get; }

/// <summary>
/// Gets the value from the origin dictionary corresponding to the specified key.
/// </summary>
/// <param name="key">The key to locate in the origin dictionary.</param>
/// <returns>The value from the origin dictionary corresponding to the specified key.</returns>
TValueOrigin GetOriginValue(TKey key);

/// <summary>
/// Attempts to get the value from the origin dictionary corresponding to the specified key.
/// </summary>
/// <param name="key">The key to locate in the origin dictionary.</param>
/// <param name="value">When this method returns, contains the value from the origin dictionary corresponding to the specified key, if the key is found; otherwise, the default value for the type of the value parameter. This parameter is passed uninitialized.</param>
/// <returns>true if the origin dictionary contains an element with the specified key; otherwise, false.</returns>
bool TryGetOriginValue(TKey key, out TValueOrigin value);
}

/// <inheritdoc cref="IObservableDictionary{TKey,TValue}"/>
/// <remarks>
/// This interface resolves between 2 dictionaries with different types of keys and values
/// </remarks>
public interface IObservableResolverDictionary<TKey, TValue, TKeyOrigin, TValueOrigin> :
IObservableResolverDictionaryReader<TKey, TValue, TKeyOrigin, TValueOrigin>,
IObservableDictionary<TKey, TValue>
{
/// <summary>
/// Updates the value in the origin dictionary corresponding to the specified origin key.
/// </summary>
/// <param name="key">The key of the value to update in the origin dictionary.</param>
/// <param name="value">The new value to set in the origin dictionary.</param>
void UpdateOrigin(TKeyOrigin key, TValueOrigin value);

/// <inheritdoc cref="Dictionary{TKey,TValue}.Add" />
/// <remarks>
/// Add's to the origin dictionary
/// </remarks>
void AddOrigin(TKeyOrigin key, TValueOrigin value);

/// <inheritdoc cref="Dictionary{TKey,TValue}.Remove" />
/// <remarks>
/// Remove's to the origin dictionary
/// </remarks>
bool RemoveOrigin(TKeyOrigin key);

/// <inheritdoc cref="Dictionary{TKey,TValue}.Clear" />
/// <remarks>
/// Clear's to the origin dictionary
/// </remarks>
void ClearOrigin();
}

/// <inheritdoc />
public class ObservableDictionary<TKey, TValue> : IObservableDictionary<TKey, TValue>
{
Expand Down Expand Up @@ -153,7 +223,7 @@ public bool ContainsKey(TKey key)
}

/// <inheritdoc />
public void Add(TKey key, TValue value)
public virtual void Add(TKey key, TValue value)
{
Dictionary.Add(key, value);

Expand All @@ -172,7 +242,7 @@ public void Add(TKey key, TValue value)
}

/// <inheritdoc />
public bool Remove(TKey key)
public virtual bool Remove(TKey key)
{
if (!Dictionary.TryGetValue(key, out var value))
{
Expand All @@ -197,6 +267,22 @@ public bool Remove(TKey key)
return true;
}

/// <inheritdoc />
public virtual void Clear()
{
var dictionary = new Dictionary<TKey, TValue>(Dictionary);

Dictionary.Clear();

for (var i = 0; i < _updateActions.Count; i++)
{
foreach (var data in dictionary)
{
_updateActions[i](data.Key, data.Value, default, ObservableUpdateType.Removed);
}
}
}

/// <inheritdoc />
public void InvokeUpdate(TKey key)
{
Expand Down Expand Up @@ -310,16 +396,100 @@ protected void InvokeUpdate(TKey key, TValue previousValue)
}

/// <inheritdoc />
public class ObservableResolverDictionary<TKey, TValue> : ObservableDictionary<TKey, TValue>
where TValue : struct
public class ObservableResolverDictionary<TKey, TValue, TKeyOrigin, TValueOrigin> :
ObservableDictionary<TKey, TValue>,
IObservableResolverDictionary<TKey, TValue, TKeyOrigin, TValueOrigin>
{
private readonly Func<IDictionary<TKey, TValue>> _dictionaryResolver;
private readonly IDictionary<TKeyOrigin, TValueOrigin> _dictionary;
private readonly Func<TKey, TValue, KeyValuePair<TKeyOrigin, TValueOrigin>> _toOrignResolver;
private readonly Func<KeyValuePair<TKeyOrigin, TValueOrigin>, KeyValuePair<TKey, TValue>> _fromOrignResolver;

protected override IDictionary<TKey, TValue> Dictionary => _dictionaryResolver();
/// <inheritdoc />
public ReadOnlyDictionary<TKeyOrigin, TValueOrigin> OriginDictionary => new ReadOnlyDictionary<TKeyOrigin, TValueOrigin>(_dictionary);

public ObservableResolverDictionary(Func<IDictionary<TKey, TValue>> dictionaryResolver)
public ObservableResolverDictionary(IDictionary<TKeyOrigin, TValueOrigin> dictionary,
Func<KeyValuePair<TKeyOrigin, TValueOrigin>, KeyValuePair<TKey, TValue>> fromOrignResolver,
Func<TKey, TValue, KeyValuePair<TKeyOrigin, TValueOrigin>> toOrignResolver)
: base(new Dictionary<TKey, TValue>(dictionary.Count))
{
_dictionary = dictionary;
_toOrignResolver = toOrignResolver;
_fromOrignResolver = fromOrignResolver;

Comment on lines +403 to +418
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 spelling of field names _toOrignResolver and _fromOrignResolver to _toOriginResolver and _fromOriginResolver

The field names _toOrignResolver and _fromOrignResolver contain a misspelling of the word "Origin".

Rename the fields to correct the spelling and update all references:

-	private readonly Func<TKey, TValue, KeyValuePair<TKeyOrigin, TValueOrigin>> _toOrignResolver;
-	private readonly Func<KeyValuePair<TKeyOrigin, TValueOrigin>, KeyValuePair<TKey, TValue>> _fromOrignResolver;
+	private readonly Func<TKey, TValue, KeyValuePair<TKeyOrigin, TValueOrigin>> _toOriginResolver;
+	private readonly Func<KeyValuePair<TKeyOrigin, TValueOrigin>, KeyValuePair<TKey, TValue>> _fromOriginResolver;

Ensure that you also update these field names throughout the class.

📝 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
private readonly IDictionary<TKeyOrigin, TValueOrigin> _dictionary;
private readonly Func<TKey, TValue, KeyValuePair<TKeyOrigin, TValueOrigin>> _toOrignResolver;
private readonly Func<KeyValuePair<TKeyOrigin, TValueOrigin>, KeyValuePair<TKey, TValue>> _fromOrignResolver;
protected override IDictionary<TKey, TValue> Dictionary => _dictionaryResolver();
/// <inheritdoc />
public ReadOnlyDictionary<TKeyOrigin, TValueOrigin> OriginDictionary => new ReadOnlyDictionary<TKeyOrigin, TValueOrigin>(_dictionary);
public ObservableResolverDictionary(Func<IDictionary<TKey, TValue>> dictionaryResolver)
public ObservableResolverDictionary(IDictionary<TKeyOrigin, TValueOrigin> dictionary,
Func<KeyValuePair<TKeyOrigin, TValueOrigin>, KeyValuePair<TKey, TValue>> fromOrignResolver,
Func<TKey, TValue, KeyValuePair<TKeyOrigin, TValueOrigin>> toOrignResolver)
: base(new Dictionary<TKey, TValue>(dictionary.Count))
{
_dictionary = dictionary;
_toOrignResolver = toOrignResolver;
_fromOrignResolver = fromOrignResolver;
private readonly IDictionary<TKeyOrigin, TValueOrigin> _dictionary;
private readonly Func<TKey, TValue, KeyValuePair<TKeyOrigin, TValueOrigin>> _toOriginResolver;
private readonly Func<KeyValuePair<TKeyOrigin, TValueOrigin>, KeyValuePair<TKey, TValue>> _fromOriginResolver;
/// <inheritdoc />
public ReadOnlyDictionary<TKeyOrigin, TValueOrigin> OriginDictionary => new ReadOnlyDictionary<TKeyOrigin, TValueOrigin>(_dictionary);
public ObservableResolverDictionary(IDictionary<TKeyOrigin, TValueOrigin> dictionary,
Func<KeyValuePair<TKeyOrigin, TValueOrigin>, KeyValuePair<TKey, TValue>> fromOrignResolver,
Func<TKey, TValue, KeyValuePair<TKeyOrigin, TValueOrigin>> toOrignResolver)
: base(new Dictionary<TKey, TValue>(dictionary.Count))
{
_dictionary = dictionary;
_toOriginResolver = toOrignResolver;
_fromOriginResolver = fromOrignResolver;

foreach (var pair in dictionary)
{
Dictionary.Add(fromOrignResolver(pair));
}
}

/// <inheritdoc />
public TValueOrigin GetOriginValue(TKey key)
{
return _dictionary[_toOrignResolver(key, default).Key];
}

/// <inheritdoc />
public bool TryGetOriginValue(TKey key, out TValueOrigin value)
{
return _dictionary.TryGetValue(_toOrignResolver(key, default).Key, out value);
}

/// <inheritdoc />
public void UpdateOrigin(TKeyOrigin key, TValueOrigin value)
{
var convertPair = _fromOrignResolver(new KeyValuePair<TKeyOrigin, TValueOrigin>(key, value));

_dictionary[key] = value;
this[convertPair.Key] = convertPair.Value;
}
Comment on lines +438 to +444
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Check for existence of origin key before updating in UpdateOrigin method

In the UpdateOrigin method, you update _dictionary[key] without verifying if the key exists. Assigning to a non-existent key will add a new entry, which might not be intended.

Consider adding a check to ensure the key exists before updating:

	public void UpdateOrigin(TKeyOrigin key, TValueOrigin value)
	{
		var convertPair = _fromOrignResolver(new KeyValuePair<TKeyOrigin, TValueOrigin>(key, value));

+		if (!_dictionary.ContainsKey(key))
+		{
+			throw new KeyNotFoundException($"The key '{key}' does not exist in the origin dictionary.");
+		}
		_dictionary[key] = value;
		this[convertPair.Key] = convertPair.Value;
	}
📝 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
public void UpdateOrigin(TKeyOrigin key, TValueOrigin value)
{
var convertPair = _fromOrignResolver(new KeyValuePair<TKeyOrigin, TValueOrigin>(key, value));
_dictionary[key] = value;
this[convertPair.Key] = convertPair.Value;
}
public void UpdateOrigin(TKeyOrigin key, TValueOrigin value)
{
var convertPair = _fromOrignResolver(new KeyValuePair<TKeyOrigin, TValueOrigin>(key, value));
if (!_dictionary.ContainsKey(key))
{
throw new KeyNotFoundException($"The key '{key}' does not exist in the origin dictionary.");
}
_dictionary[key] = value;
this[convertPair.Key] = convertPair.Value;
}


/// <inheritdoc />
public override void Add(TKey key, TValue value)
{
_dictionary.Add(_toOrignResolver(key, value));
base.Add(key, value);
}

/// <inheritdoc />
public override bool Remove(TKey key)
{
var pair = _toOrignResolver(key, Dictionary[key]);

_dictionary.Remove(pair.Key);

return base.Remove(key);
}
Comment on lines +454 to +461
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure key exists before accessing Dictionary[key] in Remove method

In the Remove method of ObservableResolverDictionary, you access Dictionary[key] without checking if the key exists. This can lead to a KeyNotFoundException if the key is not present in the dictionary.

Consider verifying that the key exists before accessing it:

	public override bool Remove(TKey key)
	{
+		if (!Dictionary.TryGetValue(key, out var value))
+		{
+			return false;
+		}
-		var pair = _toOrignResolver(key, Dictionary[key]);
+		var pair = _toOrignResolver(key, value);

		_dictionary.Remove(pair.Key);

		return base.Remove(key);
	}
📝 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
public override bool Remove(TKey key)
{
var pair = _toOrignResolver(key, Dictionary[key]);
_dictionary.Remove(pair.Key);
return base.Remove(key);
}
public override bool Remove(TKey key)
{
if (!Dictionary.TryGetValue(key, out var value))
{
return false;
}
var pair = _toOrignResolver(key, value);
_dictionary.Remove(pair.Key);
return base.Remove(key);
}


/// <inheritdoc />
public override void Clear()
{
_dictionary.Clear();
base.Clear(); ;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove duplicate semicolon to fix syntax error

There's a duplicate semicolon on this line:

	base.Clear(); ;

This will cause a syntax error. Remove the extra semicolon.

Apply this diff to fix the syntax error:

-    base.Clear(); ;
+    base.Clear();

}

/// <inheritdoc />
public void AddOrigin(TKeyOrigin key, TValueOrigin value)
{
var convertPair = _fromOrignResolver(new KeyValuePair<TKeyOrigin, TValueOrigin>(key, value));

_dictionary.Add(key, value);
base.Add(convertPair.Key, convertPair.Value);
}

/// <inheritdoc />
public bool RemoveOrigin(TKeyOrigin key)
{
var convertPair = _fromOrignResolver(new KeyValuePair<TKeyOrigin, TValueOrigin>(key, OriginDictionary[key]));

_dictionary.Remove(key);
return base.Remove(convertPair.Key);
}

/// <inheritdoc />
public void ClearOrigin()
{
_dictionaryResolver = dictionaryResolver;
_dictionary.Clear();
base.Clear();
}
}
}
Loading
Loading