I'm trying to count items (int id) in a list. Doing it so:
int currentId = -1;
int count = 0;
foreach (var item in rawItemList) {
UnityEngine.Debug.LogFormat("Item {0}", item);
if (currentId != item) {
AddItem(currentId, count);
count = 0;
}
currentId = item;
count ;
UnityEngine.Debug.LogFormat("Count {0}", count);
}
And here's the AddItem
function:
void AddItem(int itemId, int count) {
UnityEngine.Debug.LogFormat("Add item {0} count {1}", itemId, count);
if (count == 0) return;
items.Add(itemId);
counts.Add(count);
}
rawItemList
, items
and counts
are all NativeList<int>
.
There are 7 elements in rawItemList
- {0, 0, 2, 2, 3, 4, 4}
. Printing those before and after above loop works fine (prints all the elements).
The issue I'm having is that after second AddItem
call, the foreach loop exits, not even printing the rest of elements in rawItemList
. Maybe worth mentioning, this code happens in a constructor. Is there a bug in here I'm not seeing?
Console log:
Item 0
Add item -1 count 0
Count 1
Item 0
Count 2
Item 2
Add item 0 count 2
Count 1
// Loop exits here
EDIT: Just checked with standard for loop. Works fine this way. What's wrong with foreach?
EDIT 2: Inserting full code. The last item is added outside of the loop.
// public struct CountedItemList ...
public NativeList<int> items;
public NativeList<int> counts;
public CountedItemList(NativeList<int> rawItemList) {
items = new NativeList<int>(Allocator.Temp);
counts = new NativeList<int>(Allocator.Temp);
if (rawItemList.Length == 0) return;
rawItemList.Sort();
int currentId = -1;
int count = 0;
foreach (var item in rawItemList) {
UnityEngine.Debug.LogFormat("Item {0}", item);
if (currentId != item) {
AddItem(currentId, count);
count = 0;
}
currentId = item;
count ;
UnityEngine.Debug.LogFormat("Count {0}", count);
}
AddItem(currentId, count);
}
It's called like this from another script:
NativeList<int> rawItemList = new NativeList<int>(Allocator.Temp);
// Populate items
var cil = new CountedItemList(rawItemList);
rawItemList.Dispose();
EDIT 3: In EDIT 1 I've replaced
foreach(var item in rawItemList) {
With
for(int i = 0; i < rawItemList.Length; i {
int item = rawItemList[i];
The rest of the code stayed the same, yet the behavior changed.
CodePudding user response:
As said pure logic wise I can not reproduce this.
However, I implemented more or less exactly what you have in Unity like this
public class Test : MonoBehaviour
{
private CountedItemList cil;
private void Start()
{
NativeList<int> rawItemList = new NativeList<int>(Allocator.Temp);
// Populate items
rawItemList.Add(0);
rawItemList.Add(0);
rawItemList.Add(2);
rawItemList.Add(2);
rawItemList.Add(2);
rawItemList.Add(3);
rawItemList.Add(3);
rawItemList.Add(4);
cil = new CountedItemList(rawItemList);
rawItemList.Dispose();
}
private void OnDestroy()
{
cil?.Dispose();
}
}
public class CountedItemList : IDisposable
{
public NativeList<int> items;
public NativeList<int> counts;
public CountedItemList(NativeList<int> rawItemList)
{
items = new NativeList<int>(Allocator.Temp);
counts = new NativeList<int>(Allocator.Temp);
if (rawItemList.Length == 0) return;
rawItemList.Sort();
var currentId = -1;
var count = 0;
//for(var i = 0; i < rawItemList.Length; i )
//{
// var item = rawItemList[i];
foreach (var item in rawItemList)
{
UnityEngine.Debug.LogFormat("Item {0}", item);
if (currentId != item)
{
AddItem(currentId, count);
count = 0;
}
currentId = item;
count ;
UnityEngine.Debug.LogFormat("Count {0}", count);
}
AddItem(currentId, count);
}
void AddItem(int itemId, int count)
{
UnityEngine.Debug.LogFormat("Add item {0} count {1}", itemId, count);
if (count == 0) return;
items.Add(itemId);
counts.Add(count);
}
public void Dispose()
{
items.Dispose();
counts.Dispose();
}
}
Which results in a pretty not ignorable exception
ObjectDisposedException: The Unity.Collections.NativeList`1[System.Int32] has been deallocated, it is not allowed to access it
Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle.CheckReadAndThrowNoEarlyOut (Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle handle) (at <86acb61e0d2b4b36bc20af11093be9a5>:0)
Unity.Collections.NativeArray`1[T].CheckElementReadAccess (System.Int32 index) (at <86acb61e0d2b4b36bc20af11093be9a5>:0)
Unity.Collections.NativeArray`1[T].get_Item (System.Int32 index) (at <86acb61e0d2b4b36bc20af11093be9a5>:0)
Unity.Collections.NativeArray`1 Enumerator[T].get_Current () (at <86acb61e0d2b4b36bc20af11093be9a5>:0)
CountedItemList..ctor (Unity.Collections.NativeList`1[T] rawItemList) (at Assets/Scripts/ExamplePart.cs:24)
Test.Start () (at Assets/Scripts/Test.cs:24)
pointing to the line
foreach (var item in rawItemList)
I think the explanation is something like
The NativeList<int>.GetEnumerator
used by foreach
seems to be iterating asynchronously. You immediately do
var cil = new CountedItemList(rawItemList);
rawItemList.Dispose();
so the rawItemList.Dispose();
seems to get called before the iteration via foreach
is finished.
On the other hand for
uses no special enumerator but synchronous index accesses so here it is ensured hat the constructor is finished before the list is diposed.