I've written enumerators based on the existing NativeHashMap.Enumerator and UnsafeHashMap.Enumerator structs that work in burst, but I am running into trouble when I try to combine enumerators for nested unmanaged structs. Specifically I am running into a problem where the first level works, but the second level only returns the first element of the set because the enumerator it depends on is getting corrupted or reset.
In my example I have three structs: Parent, Child, and GrandChild. Parent contains a NativeHashMap of Child, and Child contains an UnsafeHashMap of GrandChild. I can iterate over a Parent's Chilren using TestEnumerator.NativeMap. I can also iterate over a Child's GrandChildren using TestEnumerator.UnsafeMap. My problem is that when I try to combine these two enumerators into TestEnumerator.AllGrandChildren, the UnsafeMap Enumerator gets corrupted each time that AllGrandChildren.MoveNext() is called, and only the first GrandChild from every Child is returned.
using UnityEngine;
using Unity.Collections;
using System.Collections.Generic;
using System;
using Unity.Collections.LowLevel.Unsafe;
using System.Collections;
public class NestedEnumeratorTest : MonoBehaviour
{
void Start()
{
{
TestChild child = new TestChild(Allocator.Temp);
for (int i = 0; i < 100; i++)
{
child.Add(new TestGrandChild(-1, i));
}
int count = 0;
foreach (TestGrandChild grandchild in child.GrandChildren)
{
count++;
}
Debug.Log("Number of grandchildren in child: " + count); // Logs 100
}
{
TestParent parent = new TestParent(Allocator.Temp);
for (int i = 0; i < 10; i++)
{
TestChild child = new TestChild(Allocator.Temp);
for (int j = 0; j < 10; j++)
{
child.Add(new TestGrandChild(i, j));
}
parent.Add(child);
}
int count = 0;
foreach (TestGrandChild grandchild in parent.AllGrandChildren)
{
count++;
}
Debug.Log("Number of grandchildren in parent: " + count); // Logs 10
}
}
}
public struct TestEmumerable
{
public struct AllGrandChildren : IEnumerable<TestGrandChild>
{
private NativeHashMap<int, TestChild>.Enumerator enumerator;
public readonly TestEmumerator.AllGrandChildren Enumerator => new(enumerator);
public AllGrandChildren(NativeHashMap<int, TestChild>.Enumerator enumerator)
{
this.enumerator = enumerator;
}
public IEnumerator<TestGrandChild> GetEnumerator()
{
return Enumerator;
}
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
}
public struct NativeMap<Key, Value> : IEnumerable<Value>
where Key : unmanaged, IEquatable<Key>
where Value : unmanaged
{
private NativeHashMap<Key, Value>.Enumerator enumerator;
public readonly TestEmumerator.NativeMap<Key, Value> Enumerator => new(enumerator);
public NativeMap(NativeHashMap<Key, Value>.Enumerator enumerator)
{
this.enumerator = enumerator;
}
public IEnumerator<Value> GetEnumerator()
{
return Enumerator;
}
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
}
public struct UnsafeMap<Key, Value> : IEnumerable<Value>
where Key : unmanaged, IEquatable<Key>
where Value : unmanaged
{
private UnsafeHashMap<Key, Value>.Enumerator enumerator;
public readonly TestEmumerator.UnsafeMap<Key, Value> Enumerator => new(enumerator);
public UnsafeMap(UnsafeHashMap<Key, Value>.Enumerator enumerator)
{
this.enumerator = enumerator;
}
public IEnumerator<Value> GetEnumerator()
{
return Enumerator;
}
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
}
}
public struct TestEmumerator
{
public struct AllGrandChildren : IEnumerator<TestGrandChild>
{
private NativeMap<int, TestChild> children; // Why can't I use a custom type here?
private UnsafeMap<int, TestGrandChild> grandchildren;
private NativeReference<bool> started;
public AllGrandChildren(NativeHashMap<int, TestChild>.Enumerator children)
{
this.children = new(children);
grandchildren = new();
started = new(false, Allocator.Temp);
}
public TestGrandChild Current => grandchildren.Current;
object IEnumerator.Current => Current;
public bool MoveNext()
{
bool moveNext;
if (started.Value)
{
moveNext = grandchildren.MoveNext();
}
else
{
started.Value = true;
moveNext = false;
}
while (!moveNext && children.MoveNext())
{
grandchildren = children.Current.GrandChildren.Enumerator;
moveNext = grandchildren.MoveNext();
}
return moveNext;
}
public void Reset()
{
if (started.Value)
{
started.Value = false;
}
children.Reset();
}
public void Dispose()
{
}
}
public struct NativeMap<Key, Value> : IEnumerator<Value>, IDisposable
where Key : unmanaged, IEquatable<Key>
where Value : unmanaged
{
private NativeHashMap<Key, Value>.Enumerator enumerator;
public NativeMap(NativeHashMap<Key, Value>.Enumerator enumerator)
{
this.enumerator = enumerator;
}
public Value Current => enumerator.Current.Value;
object IEnumerator.Current => Current;
public void Dispose()
{
}
public bool MoveNext()
{
return enumerator.MoveNext();
}
public void Reset()
{
enumerator.Reset();
}
}
public struct UnsafeMap<Key, Value> : IEnumerator<Value>, IDisposable
where Key : unmanaged, IEquatable<Key>
where Value : unmanaged
{
private UnsafeHashMap<Key, Value>.Enumerator enumerator;
public UnsafeMap(UnsafeHashMap<Key, Value>.Enumerator enumerator)
{
this.enumerator = enumerator;
}
public unsafe Value Current => enumerator.Current.Value;
object IEnumerator.Current => Current;
public void Dispose()
{
}
public bool MoveNext()
{
return enumerator.MoveNext();
}
public void Reset()
{
enumerator.Reset();
}
}
}
public struct TestParent
{
NativeHashMap<int, TestChild> children;
public TestEmumerable.NativeMap<int, TestChild> Children => new(children.GetEnumerator());
public TestEmumerable.AllGrandChildren AllGrandChildren => new(children.GetEnumerator());
public TestParent(Allocator allocator)
{
children = new(10, allocator);
}
public void Add(TestChild child)
{
children[children.Count] = child;
}
}
public struct TestChild
{
private UnsafeHashMap<int, TestGrandChild> grandchildren;
public TestEmumerable.UnsafeMap<int, TestGrandChild> GrandChildren => new(grandchildren.GetEnumerator());
public TestChild(Allocator allocator)
{
grandchildren = new(10, allocator);
}
public void Add(TestGrandChild grandchild)
{
grandchildren[grandchildren.Count] = grandchild;
}
}
public struct TestGrandChild
{
int parent;
int child;
public TestGrandChild(int parent, int child)
{
this.parent = parent;
this.child = child;
}
}
Edit:
I solved my problem, but I don't know why my solution works. In TestEmumerator.AllGrandChildren, If I use NativeHashMap.Enumerator instead of my custom TestEnumerator.NativeMap type for the children property, everything starts working. Can someone help explain why this is?