Skip to content

Commit 13f41c2

Browse files
committed
fixed determinism issues affecting USim runs
1 parent 5426d0b commit 13f41c2

File tree

9 files changed

+241
-76
lines changed

9 files changed

+241
-76
lines changed

com.unity.perception/CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
1313

1414
### Added
1515

16+
Added Register() and Unregister() methods to the RandomizerTag API so users can implement RandomizerTag compatible GameObject caching
17+
1618
### Changed
1719

20+
Made scenario MonoBehaviour lifecycle functions protected instead of private to enable users to define overrides
21+
22+
The GameObjectOneWayCache has been made public for users to cache GameObjects within their own custom Randomizers
23+
1824
### Deprecated
1925

2026
### Removed
@@ -23,6 +29,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
2329

2430
Fixed the math offsetting the iteration index of each Unity Simulation instance directly after they deserialize their app-params
2531

32+
The RandomizerTagManager now uses an OrderedSet data structure to register tags to preserve insertion order determinism in Unity Simulation
33+
34+
GameObjectOneWayCache now correctly registers and unregisters RandomizerTags on cached GameObjects
35+
2636
## [0.7.0-preview.1] - 2021-02-01
2737

2838
### Upgrade Notes
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
using System.Collections;
2+
using System.Collections.Generic;
3+
4+
namespace UnityEngine.Perception.Randomization.Randomizers
5+
{
6+
/// <summary>
7+
/// This collection has the properties of a HashSet that also preserves insertion order. As such, this data
8+
/// structure demonstrates the following time complexities:
9+
/// O(1) lookup, O(1) add, O(1) remove, and O(n) traversal
10+
/// </summary>
11+
/// <typeparam name="T">The item type to store in this collection</typeparam>
12+
class OrderedSet<T> : ICollection<T>
13+
{
14+
readonly IDictionary<T, LinkedListNode<T>> m_Dictionary;
15+
readonly LinkedList<T> m_LinkedList;
16+
17+
public OrderedSet() : this(EqualityComparer<T>.Default) { }
18+
19+
public OrderedSet(IEqualityComparer<T> comparer)
20+
{
21+
m_Dictionary = new Dictionary<T, LinkedListNode<T>>(comparer);
22+
m_LinkedList = new LinkedList<T>();
23+
}
24+
25+
public int Count => m_Dictionary.Count;
26+
27+
public virtual bool IsReadOnly => m_Dictionary.IsReadOnly;
28+
29+
void ICollection<T>.Add(T item)
30+
{
31+
Add(item);
32+
}
33+
34+
public bool Add(T item)
35+
{
36+
if (m_Dictionary.ContainsKey(item)) return false;
37+
var node = m_LinkedList.AddLast(item);
38+
m_Dictionary.Add(item, node);
39+
return true;
40+
}
41+
42+
public void Clear()
43+
{
44+
m_LinkedList.Clear();
45+
m_Dictionary.Clear();
46+
}
47+
48+
public bool Remove(T item)
49+
{
50+
var found = m_Dictionary.TryGetValue(item, out var node);
51+
if (!found) return false;
52+
m_Dictionary.Remove(item);
53+
m_LinkedList.Remove(node);
54+
return true;
55+
}
56+
57+
public IEnumerator<T> GetEnumerator()
58+
{
59+
return m_LinkedList.GetEnumerator();
60+
}
61+
62+
IEnumerator IEnumerable.GetEnumerator()
63+
{
64+
return GetEnumerator();
65+
}
66+
67+
public bool Contains(T item)
68+
{
69+
return m_Dictionary.ContainsKey(item);
70+
}
71+
72+
public void CopyTo(T[] array, int arrayIndex)
73+
{
74+
m_LinkedList.CopyTo(array, arrayIndex);
75+
}
76+
}
77+
}

com.unity.perception/Runtime/Randomization/Randomizers/OrderedSet.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

com.unity.perception/Runtime/Randomization/Randomizers/RandomizerExamples/Utilities/GameObjectOneWayCache.cs

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,26 @@ namespace UnityEngine.Perception.Randomization.Randomizers.Utilities
99
/// Facilitates object pooling for a pre-specified collection of prefabs with the caveat that objects can be fetched
1010
/// from the cache but not returned. Every frame, the cache needs to be reset, which will return all objects to the pool
1111
/// </summary>
12-
class GameObjectOneWayCache
12+
public class GameObjectOneWayCache
1313
{
1414
static ProfilerMarker s_ResetAllObjectsMarker = new ProfilerMarker("ResetAllObjects");
1515

16-
// Objects will reset to this origin when not being used
1716
Transform m_CacheParent;
1817
Dictionary<int, int> m_InstanceIdToIndex;
1918
List<GameObject>[] m_InstantiatedObjects;
2019
int[] m_NumObjectsActive;
2120
int NumObjectsInCache { get; set; }
21+
22+
/// <summary>
23+
/// The number of active cache objects in the scene
24+
/// </summary>
2225
public int NumObjectsActive { get; private set; }
2326

27+
/// <summary>
28+
/// Creates a new GameObjectOneWayCache
29+
/// </summary>
30+
/// <param name="parent">The parent object all cached instances will be parented under</param>
31+
/// <param name="prefabs">The prefabs to cache</param>
2432
public GameObjectOneWayCache(Transform parent, GameObject[] prefabs)
2533
{
2634
m_CacheParent = parent;
@@ -39,6 +47,13 @@ public GameObjectOneWayCache(Transform parent, GameObject[] prefabs)
3947
}
4048
}
4149

50+
/// <summary>
51+
/// Retrieves an existing instance of the given prefab from the cache if available.
52+
/// Otherwise, instantiate a new instance of the given prefab.
53+
/// </summary>
54+
/// <param name="prefab"></param>
55+
/// <returns></returns>
56+
/// <exception cref="ArgumentException"></exception>
4257
public GameObject GetOrInstantiate(GameObject prefab)
4358
{
4459
if (!m_InstanceIdToIndex.TryGetValue(prefab.GetInstanceID(), out var index))
@@ -47,22 +62,32 @@ public GameObject GetOrInstantiate(GameObject prefab)
4762
}
4863

4964
++NumObjectsActive;
65+
GameObject nextObject;
5066
if (m_NumObjectsActive[index] < m_InstantiatedObjects[index].Count)
5167
{
5268
var nextInCache = m_InstantiatedObjects[index][m_NumObjectsActive[index]];
5369
++m_NumObjectsActive[index];
54-
return nextInCache;
70+
nextObject = nextInCache;
5571
}
5672
else
5773
{
5874
++NumObjectsInCache;
5975
var newObject = Object.Instantiate(prefab, m_CacheParent);
6076
++m_NumObjectsActive[index];
6177
m_InstantiatedObjects[index].Add(newObject);
62-
return newObject;
78+
nextObject = newObject;
6379
}
80+
81+
var tags = nextObject.GetComponents<RandomizerTag>();
82+
foreach (var tag in tags)
83+
tag.Register();
84+
85+
return nextObject;
6486
}
6587

88+
/// <summary>
89+
/// Return all active cache objects back to an inactive state
90+
/// </summary>
6691
public void ResetAllObjects()
6792
{
6893
using (s_ResetAllObjectsMarker.Auto())
@@ -75,6 +100,9 @@ public void ResetAllObjects()
75100
{
76101
// Position outside the frame
77102
obj.transform.localPosition = new Vector3(10000, 0, 0);
103+
var tags = obj.GetComponents<RandomizerTag>();
104+
foreach (var tag in tags)
105+
tag.Unregister();
78106
}
79107
}
80108
}

com.unity.perception/Runtime/Randomization/Randomizers/RandomizerTag.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,26 @@ public abstract class RandomizerTag : MonoBehaviour
1313

1414
void Awake()
1515
{
16-
tagManager.AddTag(this);
16+
Register();
1717
}
1818

1919
void OnDestroy()
20+
{
21+
Unregister();
22+
}
23+
24+
/// <summary>
25+
/// Registers this tag with the tagManager
26+
/// </summary>
27+
public void Register()
28+
{
29+
tagManager.AddTag(this);
30+
}
31+
32+
/// <summary>
33+
/// Unregisters this tag with the tagManager
34+
/// </summary>
35+
public void Unregister()
2036
{
2137
tagManager.RemoveTag(this);
2238
}

com.unity.perception/Runtime/Randomization/Randomizers/RandomizerTagManager.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public class RandomizerTagManager
1414
public static RandomizerTagManager singleton { get; } = new RandomizerTagManager();
1515

1616
Dictionary<Type, HashSet<Type>> m_TypeTree = new Dictionary<Type, HashSet<Type>>();
17-
Dictionary<Type, HashSet<RandomizerTag>> m_TagMap = new Dictionary<Type, HashSet<RandomizerTag>>();
17+
Dictionary<Type, OrderedSet<RandomizerTag>> m_TagMap = new Dictionary<Type, OrderedSet<RandomizerTag>>();
1818

1919
/// <summary>
2020
/// Enumerates over all RandomizerTags of the given type present in the scene
@@ -60,15 +60,15 @@ void AddTagTypeToTypeHierarchy(Type tagType)
6060
if (m_TypeTree.ContainsKey(tagType))
6161
return;
6262

63-
m_TagMap.Add(tagType, new HashSet<RandomizerTag>());
63+
m_TagMap.Add(tagType, new OrderedSet<RandomizerTag>());
6464
m_TypeTree.Add(tagType, new HashSet<Type>());
6565

6666
var baseType = tagType.BaseType;
6767
while (baseType != null && baseType != typeof(RandomizerTag))
6868
{
6969
if (!m_TypeTree.ContainsKey(baseType))
7070
{
71-
m_TagMap.Add(baseType, new HashSet<RandomizerTag>());
71+
m_TagMap.Add(baseType, new OrderedSet<RandomizerTag>());
7272
m_TypeTree[baseType] = new HashSet<Type> { tagType };
7373
}
7474
else
@@ -84,7 +84,7 @@ void AddTagTypeToTypeHierarchy(Type tagType)
8484

8585
internal void RemoveTag<T>(T tag) where T : RandomizerTag
8686
{
87-
var tagType = typeof(T);
87+
var tagType = tag.GetType();
8888
if (m_TagMap.ContainsKey(tagType) && m_TagMap[tagType].Contains(tag))
8989
m_TagMap[tagType].Remove(tag);
9090
}

0 commit comments

Comments
 (0)