ArgumentException: An item with the same key has already been added

I’m getting an exception that only happens in a build. This is using astarpathfindingproject_master_pro_dev_4_3_28_fa68888e.unitypackage

I am not using IL2CPP
Unity 2019.3.15f1

ArgumentException: An item with the same key has already been added. Key: PM_Stool_1A (NavmeshCut2)
  at System.Collections.Generic.Dictionary`2[TKey,TValue].TryInsert (TKey key, TValue value, System.Collections.Generic.InsertionBehavior behavior) [0x000c1] in <c79628fadf574d3a8feae0871fad28ef>:0 
  at System.Collections.Generic.Dictionary`2[TKey,TValue].Add (TKey key, TValue value) [0x00000] in <c79628fadf574d3a8feae0871fad28ef>:0 
  at Pathfinding.Util.GridLookup`1[T].Add (T item, Pathfinding.IntRect bounds) [0x0004a] in <3ded99942e1c4854b0d9c7c6b5f07db9>:0 
  at Pathfinding.NavmeshUpdates+NavmeshUpdateSettings.AddClipper (Pathfinding.NavmeshClipper obj) [0x00039] in <3ded99942e1c4854b0d9c7c6b5f07db9>:0 
  at Pathfinding.NavmeshUpdates.HandleOnEnableCallback (Pathfinding.NavmeshClipper obj) [0x0001f] in <3ded99942e1c4854b0d9c7c6b5f07db9>:0 
  at (wrapper delegate-invoke) System.Action`1[Pathfinding.NavmeshClipper].invoke_void_T(Pathfinding.NavmeshClipper)
  at Pathfinding.NavmeshClipper.OnEnable () [0x00017] in <3ded99942e1c4854b0d9c7c6b5f07db9>:0 
UnityEngine.GameObject:Internal_AddComponentWithType(Type)
UnityEngine.GameObject:AddComponent(Type)
UnityEngine.GameObject:AddComponent()
ModV1.Source.Utility_Common:CreateOrGetComponent(GameObject, Boolean&)
NavmeshCutUpdater:ProcessPhase3Complete()
NavmeshCutUpdater:OnPhase3Complete(Object, InterruptableEventArgs)
ModV1.Source.InterruptableEvent`1:Invoke(Object, TriggerableVariableChange)
ModV1.Source.TriggerableVariable`1:TriggerVariableChanged()
ModV1.Source.TriggerableVariable`1:Set(Boolean)
<>c__DisplayClass72_0:<OnAvatarSceneLoadFinished3>b__0(List`1)
ModV1.Source.AssetManager2.AM2Loader:ExecuteAsync(PrehashedString[], UnityAction`1)
GameSystem:ExecuteAsync(PrehashedString[], UnityAction`1)
AnyModeState:OnAvatarSceneLoadFinished3(List`1, List`1, String, Boolean, Boolean)
<SetSceneReadyForGameplay>d__68:MoveNext()
UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)
 
(Filename: <c79628fadf574d3a8feae0871fad28ef> Line: 0)

A bunch of these, followed by


PointOnEdgeException, perturbating vertices slightly.
This is usually fine. It happens sometimes because of rounding errors. Cutting will be retried a few more times. 
(Filename: C:\buildslave\unity\build\Runtime/Export/Debug/Debug.bindings.h Line: 35)

PointOnEdgeException, perturbating vertices slightly.
This is usually fine. It happens sometimes because of rounding errors. Cutting will be retried a few more times. 
(Filename: C:\buildslave\unity\build\Runtime/Export/Debug/Debug.bindings.h Line: 35)

PointOnEdgeException, perturbating vertices slightly.
This is usually fine. It happens sometimes because of rounding errors. Cutting will be retried a few more times. 
(Filename: C:\buildslave\unity\build\Runtime/Export/Debug/Debug.bindings.h Line: 35)

NullReferenceException
  at (wrapper managed-to-native) UnityEngine.Transform.get_position_Injected(UnityEngine.Transform,UnityEngine.Vector3&)
  at UnityEngine.Transform.get_position () [0x00000] in <97fdf0a75ab94da9a7e5188c186e574d>:0 
  at NavmeshCut2.RequiresUpdate (Pathfinding.Util.GridLookup`1+Root[T] previousState) [0x00000] in <0dbc5c4161324de1b71c937551a2259b>:0 
  at Pathfinding.NavmeshUpdates.ForceUpdate () [0x000db] in <3ded99942e1c4854b0d9c7c6b5f07db9>:0 
  at Pathfinding.NavmeshUpdates.Update () [0x0007f] in <3ded99942e1c4854b0d9c7c6b5f07db9>:0 
  at AstarPath.Update () [0x00008] in <3ded99942e1c4854b0d9c7c6b5f07db9>:0 
 
(Filename: <97fdf0a75ab94da9a7e5188c186e574d> Line: 0)

NullReferenceException
  at (wrapper managed-to-native) UnityEngine.Transform.get_position_Injected(UnityEngine.Transform,UnityEngine.Vector3&)
  at UnityEngine.Transform.get_position () [0x00000] in <97fdf0a75ab94da9a7e5188c186e574d>:0 
  at NavmeshCut2.RequiresUpdate (Pathfinding.Util.GridLookup`1+Root[T] previousState) [0x00000] in <0dbc5c4161324de1b71c937551a2259b>:0 
  at Pathfinding.NavmeshUpdates.ForceUpdate () [0x0007a] in <3ded99942e1c4854b0d9c7c6b5f07db9>:0 
  at Pathfinding.NavmeshUpdates.Update () [0x0007f] in <3ded99942e1c4854b0d9c7c6b5f07db9>:0 
  at AstarPath.Update () [0x00008] in <3ded99942e1c4854b0d9c7c6b5f07db9>:0 
 
(Filename: <97fdf0a75ab94da9a7e5188c186e574d> Line: 0)

This is NavmeshCut2


public class NavmeshCut2 : Pathfinding.NavmeshCut
{
    public override bool RequiresUpdate(Pathfinding.Util.GridLookup<NavmeshClipper>.Root previousState)
    {
        // https://forum.arongranberg.com/t/navmeshcut-reduces-fps-to-8/8859/3
        return tr.position.y > -10.0f && base.RequiresUpdate(previousState);
    }
}

I made an earlier change to NavMeshCut.OnDisable that was probably related

protected virtual void OnDisable () {
            // KevinJ: Fix crash
            if (all.Count > 0)
            {
                // Efficient removal (the list doesn't need to be ordered).
                // Move the last item in the list to the slot occupied by this item
                // and then remove the last slot.
                all[listIndex] = all[all.Count - 1];
                all[listIndex].listIndex = listIndex;
                all.RemoveAt(all.Count - 1);
                listIndex = -1;
            }
			if (OnDisableCallback != null) OnDisableCallback(this);
		}

I noticed in MeshMeshCut.cs you have this static:
static readonly List all = new List();

However you are not resetting this as follows:

static List<NavmeshClipper> all = new List<NavmeshClipper>();
		int listIndex = -1;

        // KevinJ:
        [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
        static public void SubsystemRegistrationInit()
        {
            all = new List<NavmeshClipper>();
        }

This is most likely why it crashed in OnDisable

I think this is is wrong:

protected virtual void OnEnable () {
			listIndex = all.Count;
			if (OnEnableCallback != null) OnEnableCallback(this);
			all.Add(this);
		}

If OnEnableCallback itself adds to the list, then listIndex could be wrong. It should be

protected virtual void OnEnable () {
			if (OnEnableCallback != null) OnEnableCallback(this);
			listIndex = all.Count;
			all.Add(this);
		}

After making the above changes it no longer crashes

Hi

Interesting. The change you are suggesting does seem reasonable, and I can see that the previous code could cause a problem if you are disabling and enabling navmesh cutting for a complete graph during runtime (not individual NavmeshCut components). However there is a lot of code so it is possible I have missed something that could cause bugs even if you do not do that.

I will make that change in my codebase too. Let me know if the bug resurfaces in the future.

I’m getting another error, possibly navmeshUpdates is not managed properly

ArgumentException: An item with the same key has already been added. Key: Box (NavmeshCut2)
  at System.Collections.Generic.Dictionary`2[TKey,TValue].TryInsert (TKey key, TValue value, System.Collections.Generic.InsertionBehavior behavior) [0x000c1] in <c79628fadf574d3a8feae0871fad28ef>:0 
  at System.Collections.Generic.Dictionary`2[TKey,TValue].Add (TKey key, TValue value) [0x00000] in <c79628fadf574d3a8feae0871fad28ef>:0 
  at Pathfinding.Util.GridLookup`1[T].Add (T item, Pathfinding.IntRect bounds) [0x0004a] in <95dce218b3734529a94b06c1704c5d56>:0 
  at Pathfinding.NavmeshUpdates+NavmeshUpdateSettings.AddClipper (Pathfinding.NavmeshClipper obj) [0x00039] in <95dce218b3734529a94b06c1704c5d56>:0 
  at Pathfinding.NavmeshUpdates.HandleOnEnableCallback (Pathfinding.NavmeshClipper obj) [0x0001f] in <95dce218b3734529a94b06c1704c5d56>:0 
  at (wrapper delegate-invoke) System.Action`1[Pathfinding.NavmeshClipper].invoke_void_T(Pathfinding.NavmeshClipper)
  at Pathfinding.NavmeshClipper.OnEnable () [0x00007] in <95dce218b3734529a94b06c1704c5d56>:0 
UnityEngine.GameObject:Internal_AddComponentWithType(Type)
UnityEngine.GameObject:AddComponent(Type)
UnityEngine.GameObject:AddComponent()
ModV1.Source.Utility_Common:CreateOrGetComponent(GameObject, Boolean&)
NavmeshCutUpdater:ProcessPhase3Complete()
NavmeshCutUpdater:OnPhase3Complete(Object, InterruptableEventArgs)
ModV1.Source.InterruptableEvent`1:Invoke(Object, TriggerableVariableChange)
ModV1.Source.TriggerableVariable`1:TriggerVariableChanged()
ModV1.Source.TriggerableVariable`1:Set(Boolean)
<>c__DisplayClass72_0:<OnAvatarSceneLoadFinished3>b__0(List`1)
ModV1.Source.AssetManager2.AM2Loader:ExecuteAsync(PrehashedString[], UnityAction`1)
GameSystem:ExecuteAsync(PrehashedString[], UnityAction`1)
AnyModeState:OnAvatarSceneLoadFinished3(List`1, List`1, String, Boolean, Boolean)
<SetSceneReadyForGameplay>d__68:MoveNext()
UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)

Looking at the code for NavmeshCut, it seems like you update the bounds in NavmeshUpdates.AddClipper(). However, this happens immediately in OnEnable and won’t work for bounds that are generated at runtime, which is what I do.

NavmeshCut2 navmeshCut = Utility_Common.CreateOrGetComponent<NavmeshCut2>(c.gameObject, out isNew);
if (isNew)
{
navmeshCut.type = Pathfinding.NavmeshCut.MeshType.Rectangle;
// Set center and other properties...

My guess is that you rely on AstarPath.active heavily, while I have multiple scenes each with one instance of AstarPath. Some scenes persist in an inactive state between levels while others do not. In the build it probably loads or unloads certain scenes faster and gets confused about which AstarPath is supposed to be active.

Edit:

Here’s a patch I wrote to address this. It will find the best instance of AstarPath when an instance is created, destroyed, set enabled, or set disabled. This is a modification to AstarPath.cs

// KevinJ:
    static void UpdateBestActiveFromAwake(AstarPath a)
    {
        if (active == a)
        {
            active = FindBestActive(a);
        }
    }
    static void UpdateBestActiveFromOnEnable(AstarPath a)
    {
        if (active == a)
        {
            active = FindBestActive(a);
        }
    }
    static void UpdateBestActiveFromOnDisable(AstarPath a)
    {
        if (active == a)
        {
            active = FindBestActive(a);
        }
    }
    static void UpdateBestActiveFromOnDestroy(AstarPath a)
    {
        active = FindBestActive(a);
        if (active == a)
            active = null;
    }
    static AstarPath FindBestActive(AstarPath lowestPriority)
    {
        // Priority is enabled in active scene, enabled in non-primary scene, disabled in primary scene, disabled in non-primary scene
        UnityEngine.SceneManagement.Scene activeScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
        List<AstarPath> activeInstancesInActiveScene = FindComponentsInScene<AstarPath>(activeScene, false);
        if (activeInstancesInActiveScene.Count > 0)
        {
            foreach (AstarPath a in activeInstancesInActiveScene)
            {
                if (a != lowestPriority)
                    return a;
            }
        }


        List<AstarPath> activeInstancesInNonActiveScene = new List<AstarPath>();
        for (int i = 0; i < UnityEngine.SceneManagement.SceneManager.sceneCount; i++)
        {
            UnityEngine.SceneManagement.Scene scene = UnityEngine.SceneManagement.SceneManager.GetSceneAt(i);
            if (scene.IsValid() && scene != activeScene)
            {
                activeInstancesInNonActiveScene.AddRange( FindComponentsInScene<AstarPath>(scene, false) );
            }
        }
        foreach (AstarPath a in activeInstancesInNonActiveScene)
        {
            if (a != lowestPriority)
                return a;
        }


        List<AstarPath> inactiveInstancesInActiveScene = FindComponentsInScene<AstarPath>(activeScene, true);
        if (inactiveInstancesInActiveScene.Count > 0)
        {
            foreach (AstarPath a in inactiveInstancesInActiveScene)
            {
                if (a != lowestPriority)
                    return a;
            }
        }


        List<AstarPath> inactiveInstancesInNonActiveScene = new List<AstarPath>();
        for (int i = 0; i < UnityEngine.SceneManagement.SceneManager.sceneCount; i++)
        {
            UnityEngine.SceneManagement.Scene scene = UnityEngine.SceneManagement.SceneManager.GetSceneAt(i);
            if (scene.IsValid() && scene != activeScene)
            {
                inactiveInstancesInNonActiveScene.AddRange(FindComponentsInScene<AstarPath>(scene, true));
            }
        }
        if (inactiveInstancesInNonActiveScene.Count > 0)
        {
            foreach (AstarPath a in inactiveInstancesInNonActiveScene)
            {
                if (a != lowestPriority)
                    return a;
            }
        }
        return lowestPriority;
    }
    static public List<T> FindComponentsInScene<T>(UnityEngine.SceneManagement.Scene scene, bool includeInactive) where T : Component
    {
        List<T> output = new List<T>();
        if (scene != null)
        {
            if (scene.IsValid() == false)
            {
                Debug.LogError("FindComponentsInScene called with a scene not yet valid / loaded " + scene.name);
                return null;
            }
            GameObject[] rootGameObjects = scene.GetRootGameObjects();
            foreach (GameObject go in rootGameObjects)
            {
                T[] foundChild = go.transform.GetComponentsInChildren<T>(includeInactive);
                if (foundChild != null)
                {
                    foreach (T t in foundChild)
                    {
                        if (includeInactive == true || t.gameObject.activeInHierarchy)
                            output.Add(t);
                    }
                }
            }
            //Debug.LogWarning("FindComponentsInScene could not find component of type " + typeof(T) + " in the scene " + scene.name);
        }
        else
        {
            Debug.LogWarning("FindComponentsInScene passed a null scene");
        }
        return output;
    }
	
	protected override void Awake () {
		base.Awake();

        // KEVINJ:
        // Very important to set this. Ensures the singleton pattern holds
        //active = this;
        UpdateBestActiveFromAwake(this);
		/// ...
	
	// KEVINJ:
    protected void OnEnable()
    {
        UpdateBestActiveFromOnEnable(this);
    }
	
	
		/// <summary>Cleans up meshes to avoid memory leaks</summary>
	void OnDisable () {
		if (!Application.isPlaying && active == this) {
			// We need to call dipose data here because in the editor the OnDestroy
			// method is not called but OnDisable is. It is vital that graph data
			// is destroyed even in the editor (e.g. when going from edit mode to play mode)
			// because a lot of data is stored as NativeArrays which need to be disposed.
			data.DisposeUnmanagedData();
		}

        // KevinJ:
        UpdateBestActiveFromOnDisable(this);
	}
	
	
	void OnDestroy () {
	
	/// ...
	
	if (active == this) {
			// Clear all callbacks
			OnAwakeSettings         = null;
			OnGraphPreScan          = null;
			OnGraphPostScan         = null;
			OnPathPreSearch         = null;
			OnPathPostSearch        = null;
			OnPreScan               = null;
			OnPostScan              = null;
			OnLatePostScan          = null;
			On65KOverflow           = null;
			OnGraphsUpdated         = null;

			active = null;
		}

        // KevinJ:
        UpdateBestActiveFromOnDestroy(this);
    }

	

I caught this in the debugger. OnEnableCallback gets HandleOnEnableCallback twice

This is because OnEnableCallback and OnDisableCallback are static, so if you have AstarPath in one scene, add another to a second scene, then OnEnableCallback will be called twice for both

///

Called every time a NavmeshCut/NavmeshAdd component is enabled.
static System.Action OnEnableCallback;

	/// <summary>Called every time a NavmeshCut/NavmeshAdd component is disabled.</summary>
	static System.Action<NavmeshClipper> OnDisableCallback;

This means HandleOnEnableCallback is called twice for every NavmeshCut.OnEnable, similar for HandleOnDisableCallback

Hmm, but the AstarPath component unregisters from those callbacks in OnDestroy -> navmeshUpdates.OnDisable -> NavmeshCut.RemoveEnableCallback.
So there should never be a point where it is called twice. Could you provide more details about your setup?

Oh, you mean that you have multiple AstarPath components at the same time? Yeah that is kinda not supported at the moment.

If there are two AstarPath components at once, each will call AddEnableCallback. This is a static function, therefore, OnEnableCallback has two delegates added to it. This results in the function being called twice.

Once I deleted the second AstarPath component most of my problems went away, so I did that.

Yeah. Having multiple AstarPath components loaded and active at the same time is not supported. The inspector should also show an error message and I think the AstarPath component will even try to delete itself and log an error if it detects that this is the case.

Just wanted to add that I was getting a similar message also due to having two pathfinder components enabled at the same time, on two different game objects even though one was disabled.

I had one in my menu scene, then another in my main gameplay scene which is loaded as an additive scene.

I was disabling the menu scene gameobject that had the pathfinder component. This doesn’t disable the pathfinder. I then tried disabling the pathfinder component. This also doesn’t help. I had to delete the component from the scene. I posted a new question about if/how to have multiple pathfinder components across multiple additive scenes:

Can you have 2 pathfinder components across different scenes?

Note that I didn’t get any errors or warnings when either the GameObject or pathfinder component was disabled.