Removing an npcitem from the world?

edited October 2010 in Photon Server
So we have some NPCs in the world that are loosely based off the NpcItem code. I'm trying to figure out the correct way to dispose of these items when they are supposed to be removed from the world (i.e. get killed).

Here's the basic flow of what's supposed to happen.

- RemoveFromWorld on the NPC (technically our "Entity" class that is a parent to both MmoItem and NpcItem and a child of Item) gets called that does this:
public void RemoveFromWorld()
			this.World.ItemCache.RemoveItem( this.Type, this.Id );

- OnDestroy gets called for the NpcItem, which essentially does this:
protected override void OnDestroy()
			var eventInstance = new ItemDestroyed { ItemId = this.Id, ItemType = this.Type };
			var message = new ItemEventMessage( this, eventInstance.GetEventData( (byte)EventCode.ItemDestroyed, Reliability.Reliable, Settings.ItemEventChannel ) );
			this.EventChannel.Publish( message );

			// not sure the best place for the Dispose call. Removing it from the ItemCache doesn't seem to do it, nor does Destroy call Dipose automatically.
			interestArea.Detach(); // this is a NpcInterestArea

The problem is, a non-inconsequential percentage of the time, this exception is being thrown:
2010-10-25 08:41:40,111 [22] ERROR PhotonHostRuntime.PhotonDomainManager - System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
at System.Collections.Generic.Dictionary`2.ValueCollection.Enumerator.MoveNext()
at Photon.SocketServer.Mmo.InterestArea.#Dc() in c:\Dev\photon-socketserver-sdk\src\Photon.SocketServer\Mmo\InterestArea.cs:line 777
at Photon.SocketServer.Mmo.InterestArea.Dispose(Boolean disposing) in c:\Dev\photon-socketserver-sdk\src\Photon.SocketServer\Mmo\InterestArea.cs:line 563
at Photon.SocketServer.Mmo.InterestArea.Dispose() in c:\Dev\photon-socketserver-sdk\src\Photon.SocketServer\Mmo\InterestArea.cs:line 474
at Photon.MmoDemo.Server.NpcItem.OnDestroy() in C:\Dev\Ruby\trunk\server\src\Photon.MmoDemo.Server\NpcItem.cs:line 253
at Photon.SocketServer.Mmo.Item.Destroy() in c:\Dev\photon-socketserver-sdk\src\Photon.SocketServer\Mmo\Item.cs:line 362
at Photon.MmoDemo.Server.Entity.RemoveFromWorld() in C:\Dev\Ruby\trunk\server\src\Photon.MmoDemo.Server\Entity.cs:line 193
at Photon.MmoDemo.Server.NpcItem.DamagedFromDirection(QuaternionF rotation, String attackerId, Nullable`1 attackerType) in C:\Dev\Ruby\trunk\server\src\Photon.MmoDemo.Server\NpcItem.cs:line 160
at Photon.MmoDemo.Server.NpcItem.OnAttacked(Vector3F attackerPosition, QuaternionF attackerRotation, String attackerId, Byte attackerType) in C:\Dev\Ruby\trunk\server\src\Photon.MmoDemo.Server\NpcItem.cs:line 114
at Photon.MmoDemo.Server.Entity.OnCurrentInterestAreaItemActionPerformed(EventData eventData) in C:\Dev\Ruby\trunk\server\src\Photon.MmoDemo.Server\Entity.cs:line 162
at Photon.MmoDemo.Server.Entity.OnCurrentInterestAreaChannelItemEventMessage(ItemEventMessage message) in C:\Dev\Ruby\trunk\server\src\Photon.MmoDemo.Server\Entity.cs:line 128
at ExitGames.Concurrency.Channels.ChannelSubscription`1.#e.#z.#9b() in c:\Dev\exitgames-libs\src\Concurrency\Channels\ChannelSubscription.cs:line 16777215
at (Object )
at ExitGames.Concurrency.Core.BatchExecutor.ExecuteAll(Action[] toExecute) in c:\Dev\exitgames-libs\src\Concurrency\Core\BatchExecutor.cs:line 19
at ExitGames.Concurrency.Fibers.PoolFiber.Flush(Object ) in c:\Dev\exitgames-libs\src\Concurrency\Fibers\PoolFiber.cs:line 151
at System.Threading._ThreadPoolWaitCallback.WaitCallback_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallbackInternal(_ThreadPoolWaitCallback tpWaitCallBack)
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback(Object state)

Given that this doesn't happen all the time I'm assuming we're running into some kind of threading issue. Now I'm sure we're doing something wrong, but I'm not entirely sure the first place to start. Any suggestions?


  • There might be more to it, but here my first two thoughts:
    1) Make sure RemoveWorld is executed on the item fiber (aka operation /action queue)
    2) lock the interest area before calling detach & dispose (interest areas do not run on a fiber, so locking before accessing it is mandatory)
  • not locking the interest areas seems to be what was causing the issue, thanks.