Array iteration ensure fail

Hello and thanks for looking at my problem. I am working on a stackable inventory using a struct. In my struct, FInventoryItem I specify a Quantity and MaxStackSize to denote how many to add and the maximum one entry can hold. My Itteration works, I can add quantity, and as I specify, if the quantity reaches the MaxStackSize creates a new entry. My first issue is that even though the code is working, I am recieving an ensure error on the third stack. My second issue is that when the third stack is created, it creates a 4th as well. Here is the code:



if (GameMode)
    {
        UDataTable* ItemTable = GameMode->GetItemDB();
        if (ItemTable)
        {
            UE_LOG(LogTemp, Warning, TEXT("Item Table Set"));
            FInventoryItem* ItemToAdd = ItemTable->FindRow<FInventoryItem>(ID, "");

            if (ItemToAdd)
            {
                UE_LOG(LogTemp, Warning, TEXT("Item to Add"));
                if (ItemToAdd->bIsStackable)
                {
                    UE_LOG(LogTemp, Warning, TEXT("bool called"));
                    if (InventorytoAdd->Inventory.Contains(*ItemToAdd))
                    {
                        UE_LOG(LogTemp, Warning, TEXT("Item is in inventory"));
                        for (auto& Item : InventorytoAdd->Inventory)
                        {
                            UE_LOG(LogTemp, Warning, TEXT("for loop called"));
                            if (Item.Quantity >= Item.MaxStackSize)
                            {
                                UE_LOG(LogTemp, Warning, TEXT("quantity too large"));
                                InventorytoAdd->Inventory.Add(*ItemToAdd);
                            }
                            else
                            {
                                UE_LOG(LogTemp, Warning, TEXT("Quantity: %d"), &Item.Quantity);
                                Item.Quantity = Item.Quantity + ItemToAdd->Quantity;
                            }
                        }
                    }
                    else if (!InventorytoAdd->Inventory.Contains(*ItemToAdd))
                    {
                        UE_LOG(LogTemp, Warning, TEXT("Item not in Inventory"));
                        InventorytoAdd->Inventory.Add(*ItemToAdd);
                    }

                }
                else
                {
                    UE_LOG(LogTemp, Warning, TEXT("Bool not working"));
                    InventorytoAdd->Inventory.Add(*ItemToAdd);
                }    
            }
            else return;
        }
        else return;
    }
    else return;

and here is the ensure faile:

LogOutputDevice: Error: === Handled ensure: ===
LogOutputDevice: Error: Ensure condition failed: Lhs.CurrentNum == Lhs.InitialNum [File:C:\Program Files\Epic Games\UE_4.19\Engine\Source\Runtime\Core\Public\Containers/Array.h] [Line: 206]
LogOutputDevice: Error: Array has changed during ranged-for iteration!
LogOutputDevice: Error: Stack:
LogOutputDevice: Error: [Callstack] 0x000000005146B6F6 UE4Editor-Core.dll!UnknownFunction ]
LogOutputDevice: Error: [Callstack] 0x00000000511CAE2A UE4Editor-Core.dll!UnknownFunction ]
LogOutputDevice: Error: [Callstack] 0x00000000511DECC6 UE4Editor-Core.dll!UnknownFunction ]
LogOutputDevice: Error: [Callstack] 0x00000000572B7173 UE4Editor-RPGFix-191.dll!UInventory_Component::AddItemtoInventoryByID() [c:\ue4\rpgfix\source\rpgfix\character components\inventory_component.cpp:76]
LogOutputDevice: Error: [Callstack] 0x00000000572BB1DD UE4Editor-RPGFix-191.dll!APickup::Interact() [c:\ue4\rpgfix\source\rpgfix\interactables\pickup.cpp:45]
LogOutputDevice: Error: [Callstack] 0x00000000572B96DF UE4Editor-RPGFix-191.dll!UInventory_Component::execInteract() [c:\ue4\rpgfix\source\rpgfix\character components\inventory_component.h:114]
LogOutputDevice: Error: [Callstack] 0x0000000051A9E094 UE4Editor-CoreUObject.dll!UnknownFunction ]
LogOutputDevice: Error: [Callstack] 0x0000000051C70771 UE4Editor-CoreUObject.dll!UnknownFunction ]
LogOutputDevice: Error: [Callstack] 0x0000000051C887A1 UE4Editor-CoreUObject.dll!UnknownFunction ]
LogOutputDevice: Error: [Callstack] 0x0000000051C8AA72 UE4Editor-CoreUObject.dll!UnknownFunction ]
LogOutputDevice: Error: [Callstack] 0x0000000051C711DE UE4Editor-CoreUObject.dll!UnknownFunction ]
LogOutputDevice: Error: [Callstack] 0x0000000051C8AA72 UE4Editor-CoreUObject.dll!UnknownFunction ]
LogOutputDevice: Error: [Callstack] 0x0000000051A9E094 UE4Editor-CoreUObject.dll!UnknownFunction ]
LogOutputDevice: Error: [Callstack] 0x0000000051C89D63 UE4Editor-CoreUObject.dll!UnknownFunction ]
LogOutputDevice: Error: [Callstack] 0x000000004E5FCCFB UE4Editor-Engine.dll!UnknownFunction ]
LogOutputDevice: Error: [Callstack] 0x000000004F80090E UE4Editor-Engine.dll!UnknownFunction ]
LogOutputDevice: Error: [Callstack] 0x000000004F800A7A UE4Editor-Engine.dll!UnknownFunction ]
LogOutputDevice: Error: [Callstack] 0x000000004F83292B UE4Editor-Engine.dll!UnknownFunction ]
LogOutputDevice: Error: [Callstack] 0x000000004F369EF9 UE4Editor-Engine.dll!UnknownFunction ]
LogOutputDevice: Error: [Callstack] 0x000000004F37E755 UE4Editor-Engine.dll!UnknownFunction ]
LogOutputDevice: Error: [Callstack] 0x000000004F3679BB UE4Editor-Engine.dll!UnknownFunction ]
LogOutputDevice: Error: [Callstack] 0x000000004F37D8FC UE4Editor-Engine.dll!UnknownFunction ]
LogOutputDevice: Error: [Callstack] 0x000000004E5D00E4 UE4Editor-Engine.dll!UnknownFunction ]
LogOutputDevice: Error: [Callstack] 0x000000004F6D690B UE4Editor-Engine.dll!UnknownFunction ]
LogOutputDevice: Error: [Callstack] 0x000000004F6DBFE3 UE4Editor-Engine.dll!UnknownFunction ]
LogOutputDevice: Error: [Callstack] 0x000000005109B713 UE4Editor-Core.dll!UnknownFunction ]
LogOutputDevice: Error: [Callstack] 0x000000005109BD3A UE4Editor-Core.dll!UnknownFunction ]
LogOutputDevice: Error: [Callstack] 0x00000000510BECFE UE4Editor-Core.dll!UnknownFunction ]
LogOutputDevice: Error: [Callstack] 0x000000004F6FCFC1 UE4Editor-Engine.dll!UnknownFunction ]
LogOutputDevice: Error: [Callstack] 0x000000004F702AE2 UE4Editor-Engine.dll!UnknownFunction ]
LogOutputDevice: Error: [Callstack] 0x000000004EF5BF04 UE4Editor-Engine.dll!UnknownFunction ]
LogOutputDevice: Error: [Callstack] 0x000000004EF6716B UE4Editor-Engine.dll!UnknownFunction ]
LogOutputDevice: Error: [Callstack] 0x000000004211D781 UE4Editor-UnrealEd.dll!UnknownFunction ]
LogOutputDevice: Error: [Callstack] 0x00000000429B9516 UE4Editor-UnrealEd.dll!UnknownFunction ]
LogOutputDevice: Error: [Callstack] 0x000000000BAC5EAC UE4Editor.exe!UnknownFunction ]
LogOutputDevice: Error: [Callstack] 0x000000000BAD6BD0 UE4Editor.exe!UnknownFunction ]
LogOutputDevice: Error: [Callstack] 0x000000000BAD6C4A UE4Editor.exe!UnknownFunction ]
LogOutputDevice: Error: [Callstack] 0x000000000BAE4149 UE4Editor.exe!UnknownFunction ]
LogOutputDevice: Error: [Callstack] 0x000000000BAE5B57 UE4Editor.exe!UnknownFunction ]
LogOutputDevice: Error: [Callstack] 0x000000009E403034 KERNEL32.DLL!UnknownFunction ]
LogOutputDevice: Error: [Callstack] 0x00000000A0B83691 ntdll.dll!UnknownFunction ]

I would appreciate any help

1 Like

LogOutputDevice: Error: Array has changed during ranged-for iteration!

If you want to modify the container during iteration (which is usually bad practice), use standard for-loop or iterators explicitly and manually update current index/iterator after add/remove element.

2 Likes

Thank you for your response and sorry I am still pretty new at programming, only been at this for a year now. I did manage to solve the issue by adding returns after adding the item and after updating the quantity. I would, however, like to not be implementing bad practices. How else would I modify the struct value if not during iteration?

1 Like

Your main problem is that you are trying to traverse an array and while doing this your are adding more items to it. You can traverse your array and keep a separate list of items to be added and once you finish traverse that array you get the separate list of items and add it to the original one.

1 Like

Thank you for helping, however, I am unsure how to go about what you are saying. I tried to store Item in a new FInventoryItem (the Struct) variable and modify the Quantity after the for loop, however, that didn’t work, I was unable to update the quantity of the item in the array.

Here is my current code, which works and can even make a new stack of the item if the Quantity is over the maximum:



 for (auto& Item : InventorytoAdd->Inventory)
                        {
                            if (Item.ItemID == ItemToAdd->ItemID) // If the item's ID in inventory matches the Added Item's ID 
                            {
                                if (Item.Quantity < Item.MaxStackSize)
                                {
                                    if (Item.Quantity + ItemToAdd->Quantity > Item.MaxStackSize) //If the sum of the two Item's Quantities is greater than the max stack size
                                    {
                                        int32 AddedItemInitialQuantity = ItemToAdd->Quantity;
                                        int32 QuantitytoAdd = Item.Quantity + ItemToAdd->Quantity - Item.MaxStackSize;
                                        Item.Quantity = Item.MaxStackSize; // Set the Item in inventory's quantity to the maximum
                                        ItemToAdd->Quantity = QuantitytoAdd; // Give the added Item the rest of the quantity that is over the maximum
                                        InventorytoAdd->Inventory.Add(*ItemToAdd); //Add the Item with the updated quantity
                                        ItemToAdd->Quantity = AddedItemInitialQuantity;
                                        return;
                                    }
                                    else if (Item.Quantity < Item.MaxStackSize)
                                    {
                                        Item.Quantity = Item.Quantity + ItemToAdd->Quantity;
                                        return;
                                    }
                                }
                            }
                        }


Even though this works, as I said I am still pretty new to programming and definitely don’t want to be using bad practices. How would I keep a separate list of the items in the inventory?

Thanks again.

This is one of reasons why “C# fanboys” want C++ to die lol

To make your life easier you can iterate an index backwards while changing the array.
Instead of doing “for &item…” you can do

for (int32 i = inventory.Num()-1; i > INDEX_NONE; --i)
inventory*… // do things

First question is do you really need a loop? Because your last snippet can be easily implemented with something like FindByPredicate, without any loop. But…
Your code allow to have more than one item with given ID. So, breaking loop after first occurrence of desired item isnt good idea. Why?
If first founded item already reach MaxStackSize then you create new item, and finally you got three items: “full-item”, created non-full item and another non-full item.
This is what you want?

To elaborate on the cause and how to fix it; when using a Ranged-For Loop:



for (FInventoryItem& Item : InventoryToAdd->Inventory)
{
  // ... Do stuff
}


You are not allowed to modify whatever container you are looping through (in this case, “InventoryToAdd->Inventory” ).

What Bruno is suggestion is to keep a separate list of your changes and just append them at the end:



TArray<FInventoryItem> NewItems;
for (FInventoryItem& Item : InventoryToAdd->Inventory)
{
     if (Item.Quantity >= Item.MaxStackSize)
     {
         UE_LOG(LogTemp, Warning, TEXT("quantity too large"));
         NewItems.Append(*ItemToAdd); // Rather than modify our array, just queue up this new item to be applied later.
     }
     else
     {
          UE_LOG(LogTemp, Warning, TEXT("Quantity: %d"), &Item.Quantity);
          Item.Quantity = Item.Quantity + ItemToAdd->Quantity;
      }
}

// We're done with our iteration, now we can safely append items to our array.
InventoryToAdd->Inventory.Append(NewItems);


Hope that helps.

Sorry Its been a few weeks thanks for the responses! I was trying to get the Append idea mentioned to work, however was unable. I did finally get everything working by finding the first index that matches the ItemID. and then just modifying [Index]


InventorytoAdd->Inventory[Index]

as Emaer originally suggested (just took me time to figure out how lol). The loop now looks like:



                   int32 Index = 0;

                    if (InventorytoAdd->Inventory.Contains(*ItemToAdd))
                    {
                        for (int32 i = 0; i < InventorytoAdd->Inventory.Num(); i++)
                        {
                            if ((InventorytoAdd->Inventory*.ItemID == ItemToAdd->ItemID) && (InventorytoAdd->Inventory*.Quantity < InventorytoAdd->Inventory*.MaxStackSize))
                            {
                                Index = i;
                                break;
                            }
                        }
                        if (InventorytoAdd->Inventory[Index].ItemID == ItemToAdd->ItemID)
                        {
                            IncreaseQuantityAtIndex(*ItemToAdd, InventorytoAdd, Index); // I extracted the code to a new function but its basically what was shown earlier
                        }
                    }


As far as using something like FindbyPredicate, I don’t know what that is sorry, like I said I haven’t been programming that long.

Thanks again for all of the help and ideas!

Now if I could figure out how to mark this as answered lol