Skip to content

Commit 1e2cc66

Browse files
authored
fix: Handle initial collection items appropriately (#2702)
1 parent e7f7580 commit 1e2cc66

File tree

7 files changed

+101
-0
lines changed

7 files changed

+101
-0
lines changed

sources/assets/Stride.Core.Assets.Tests/Yaml/TestCollectionIdsSerialization.cs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,28 @@ public ContainerNonIdentifiableDictionary(string name)
6262
public Dictionary<string, ContainerCollection> NonIdentifiableObjects { get; set; } = [];
6363
}
6464

65+
public class ContainerCollectionWithInitialData
66+
{
67+
public List<string> Strings { get; set; } = ["InitialData"];
68+
}
69+
70+
public class ContainerDictionaryWithInitialData
71+
{
72+
public Dictionary<string, string> Strings { get; set; } = new() { { "InitialDataK", "InitialDataV" } };
73+
}
74+
75+
public class ContainerCollectionWithInitialDataNonId
76+
{
77+
[NonIdentifiableCollectionItems]
78+
public List<string> Strings { get; set; } = ["InitialDataNonId"];
79+
}
80+
81+
public class ContainerDictionaryWithInitialDataNonId
82+
{
83+
[NonIdentifiableCollectionItems]
84+
public Dictionary<string, string> Strings { get; set; } = new() { { "InitialDataNonIdK", "InitialDataNonIdV" } };
85+
}
86+
6587

6688
private const string YamlCollection =
6789
"""
@@ -671,4 +693,51 @@ public void TestIdsGeneration()
671693
hashSet.Add(ids["key4"]);
672694
Assert.Equal(5, hashSet.Count);
673695
}
696+
697+
[Fact]
698+
public void TestCollectionWithInitialDataSerialization()
699+
{
700+
var output = RoundTrip(new ContainerCollectionWithInitialData
701+
{
702+
Strings = new() { "ReplacedData" }
703+
});
704+
Assert.NotNull(output);
705+
Assert.Single(output.Strings);
706+
Assert.Equal("ReplacedData", output.Strings[0]);
707+
708+
var output2 = RoundTrip(new ContainerDictionaryWithInitialData
709+
{
710+
Strings = new() { { "ReplacedDataK", "ReplacedDataV" } }
711+
});
712+
Assert.NotNull(output2);
713+
Assert.Single(output2.Strings);
714+
Assert.Equal(new KeyValuePair<string, string>("ReplacedDataK", "ReplacedDataV"), output2.Strings.First());
715+
716+
var output3 = RoundTrip(new ContainerCollectionWithInitialDataNonId
717+
{
718+
Strings = new() { "ReplacedData" }
719+
});
720+
Assert.NotNull(output3);
721+
Assert.Single(output3.Strings);
722+
Assert.Equal("ReplacedData", output3.Strings[0]);
723+
724+
var output4 = RoundTrip(new ContainerDictionaryWithInitialDataNonId
725+
{
726+
Strings = new() { { "ReplacedDataK", "ReplacedDataV" } }
727+
});
728+
Assert.NotNull(output4);
729+
Assert.Single(output4.Strings);
730+
Assert.Equal(new KeyValuePair<string, string>("ReplacedDataK", "ReplacedDataV"), output4.Strings.First());
731+
732+
static T? RoundTrip<T>(T instance) where T : class
733+
{
734+
var yaml = SerializeAsString(instance);
735+
var stream = new MemoryStream();
736+
var writer = new StreamWriter(stream);
737+
writer.Write(yaml);
738+
writer.Flush();
739+
stream.Position = 0;
740+
return AssetYamlSerializer.Default.Deserialize(stream) as T;
741+
}
742+
}
674743
}

sources/assets/Stride.Core.Assets/Yaml/CollectionWithIdsSerializer.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,11 @@ protected override void TransformAfterDeserialization(IDictionary container, ITy
114114
throw new InvalidOperationException("The given container does not match the expected type.");
115115
var identifier = CollectionItemIdHelper.GetCollectionItemIds(targetCollection);
116116
identifier.Clear();
117+
118+
// The collection may contain some initial data from its containing instance's ctor,
119+
// let's replace the existing data with the data we have serialized
120+
collectionDescriptor.Clear(targetCollection);
121+
117122
var i = 0;
118123
var enumerator = container.GetEnumerator();
119124
while (enumerator.MoveNext())

sources/assets/Stride.Core.Assets/Yaml/CollectionWithIdsSerializerBase.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,10 @@ protected override void ReadDictionaryItems(ref ObjectContext objectContext)
133133
{
134134
var dictionaryDescriptor = (DictionaryDescriptor)objectContext.Descriptor;
135135

136+
// The collection may contain some initial data from its containing instance's ctor,
137+
// let's replace the existing data with the data we have serialized
138+
dictionaryDescriptor.Clear(objectContext.Instance);
139+
136140
var deletedItems = new HashSet<ItemId>();
137141

138142
var reader = objectContext.Reader;

sources/assets/Stride.Core.Assets/Yaml/DictionaryWithIdsSerializer.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@ protected override void TransformAfterDeserialization(IDictionary container, ITy
9797
throw new InvalidOperationException("The given container does not match the expected type.");
9898
var identifier = CollectionItemIdHelper.GetCollectionItemIds(targetCollection);
9999
identifier.Clear();
100+
101+
// The collection may contain some initial data from its containing instance's ctor,
102+
// let's replace the existing data with the data we have serialized
103+
dictionaryDescriptor.Clear(targetCollection);
104+
100105
var enumerator = container.GetEnumerator();
101106
while (enumerator.MoveNext())
102107
{

sources/core/Stride.Core.Reflection/TypeDescriptors/DictionaryDescriptor.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public class DictionaryDescriptor : ObjectDescriptor
2222
Func<object, ICollection> getValuesMethod;
2323
Func<object, object, object?> getValueMethod;
2424
Func<object, IEnumerable<KeyValuePair<object, object?>>> getEnumeratorMethod;
25+
Action<object> clearMethod;
2526

2627
#pragma warning disable CS8618
2728
// This warning is disabled because the necessary initialization will occur
@@ -56,6 +57,7 @@ void CreateDictionaryDelegates<TKey, TValue>()
5657
getValueMethod = (dictionary, key) => ((IDictionary<TKey, TValue?>)dictionary)[(TKey)key];
5758
setValueMethod = (dictionary, key, value) => ((IDictionary<TKey, TValue?>)dictionary)[(TKey)key] = (TValue?)value;
5859
getEnumeratorMethod = (dictionary) => GetGenericEnumerable((IDictionary<TKey, TValue>)dictionary);
60+
clearMethod = (dictionary) => ((IDictionary<TKey, TValue?>)dictionary).Clear();
5961
}
6062

6163
public override void Initialize(IComparer<object> keyComparer)
@@ -182,6 +184,16 @@ public ICollection GetValues(object dictionary)
182184
return getValuesMethod(dictionary);
183185
}
184186

187+
/// <summary>
188+
/// Calls clear on the dictionary
189+
/// </summary>
190+
/// <param name="dictionary">The dictionary</param>
191+
public void Clear(object dictionary)
192+
{
193+
ArgumentNullException.ThrowIfNull(dictionary);
194+
clearMethod(dictionary);
195+
}
196+
185197
/// <summary>
186198
/// Returns the value matching the given key in the dictionary, or null if the key is not found
187199
/// </summary>

sources/core/Stride.Core.Yaml/Serialization/Serializers/CollectionSerializer.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,10 @@ protected virtual void ReadCollectionItems(ref ObjectContext objectContext)
148148
throw new InvalidOperationException($"Cannot deserialize collection to readonly collection type [{thisObject.GetType()}].");
149149
}
150150

151+
// The collection may contain some initial data from its containing instance's ctor,
152+
// let's replace the existing data with the data we have serialized
153+
collectionDescriptor.Clear(thisObject);
154+
151155
var reader = objectContext.Reader;
152156

153157
var elementType = collectionDescriptor.ElementType;

sources/core/Stride.Core.Yaml/Serialization/Serializers/DictionarySerializer.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@ protected virtual void ReadDictionaryItems(ref ObjectContext objectContext)
141141
{
142142
var dictionaryDescriptor = (DictionaryDescriptor)objectContext.Descriptor;
143143

144+
dictionaryDescriptor.Clear(objectContext.Instance);
145+
144146
var reader = objectContext.Reader;
145147
while (!reader.Accept<MappingEnd>())
146148
{

0 commit comments

Comments
 (0)