Game Development Community

Mounted lights: semi complex ghosting / scoping problem

by Clint S. Brewer · in Torque Game Engine · 11/14/2005 (4:46 pm) · 7 replies

A while ago I added the ability to link/mount particle systems and lights to other objects in the scene. It had seemed to be working great except for a small bug:

Ever so often, as I travelled around my world, I would find a particle system or light, sitting by itself not attached to the object it should be attached to.

I let it go for a long time, but decided to track it down this weekend. I finally managed to reproduce it.

Lets say object A has object B linked to it.

When the player runs far enough away, object A goes out of scope on the client. This triggers an on delete notify which triggers the unlinking code. Now when the player moves closer object A pops back into scope, but the light is no longer mounted to him.


I've examined the shapebase mounting code, other resources, and forum comments but I'm still having trouble figuring out how to handle this.

What I want maybe is when onDeleteNotify is called, check to see if we are a ghost, and if we are, then do not perform the unmounting, but set a flag that the mount isn't valid, so the light won't crash the engine when it tries to get it's position from object A.

I'd really appreciate any advice you TGE networking wizards might have.



Here is the gist of my linking code:

GameBase.h
protected:
   //clint: support for linking objects
   //    to shape base nodes
   //    from script
   ShapeBase* mTargetLinkedObject;
   U32 mLinkNode;
   bool mLinkedToMuzzle; //should we be linked to a muzzle transform?
  public:
   /// linkToShapeNode links this emitter node's position to the named
   /// node on targetObject
   /// @param targetObject a ShapeBase object that contains the node you want to link to
   /// @param node the identifier of the node to link to, for example: 0,1,2, etc
   /// @param linkToMuzzle if true then we are linked to the muzzleTransform of the object
   void linkToShapeNode(ShapeBase* targetObject , U32 node, bool linkToMuzzle);
   void unlinkFromShapeNode();
   virtual void onDeleteNotify(SimObject *obj); 

.....

enum GameBaseMasks {
      InitialUpdateMask =     Parent::NextFreeMask,
      DataBlockMask =         InitialUpdateMask << 1,
      ExtendedInfoMask =      DataBlockMask << 1,
      LinkedObjectChangeMask = ExtendedInfoMask <<1,
      NextFreeMask =          LinkedObjectChangeMask << 1
   };

#1
11/14/2005 (4:46 pm)
In GameBase::packUpdate
if (mask & LinkedObjectChangeMask) {
      if (mTargetLinkedObject) {
         S32 gIndex = con->getGhostIndex(mTargetLinkedObject);
         if (stream->writeFlag(gIndex != -1)) {
            stream->writeFlag(true);
			stream->writeInt(gIndex,NetConnection::GhostIdBitSize);
			stream->write(mLinkNode);
			stream->write(mLinkedToMuzzle);
            
         }
         else
            // Will have to try again later
            retMask |= LinkedObjectChangeMask;
      }
      else
         // Unmount if this isn't the initial packet
         if (stream->writeFlag(!(mask & InitialUpdateMask)))
            stream->writeFlag(false);
   }
   else
      stream->writeFlag(false);


in GameBase::unpackUpdate
if (stream->readFlag()) {
      if (stream->readFlag()) {
         S32 gIndex = stream->readInt(NetConnection::GhostIdBitSize);
         ShapeBase* obj = dynamic_cast<ShapeBase*>(con->resolveGhost(gIndex));
         stream->read(&mLinkNode);
		 stream->read(&mLinkedToMuzzle);
         if(!obj)
         {
            con->setLastError("Invalid packet from server.");
            return;
         }
		 linkToShapeNode(obj , mLinkNode, mLinkedToMuzzle);
         
      }
      else
         unlinkFromShapeNode();
   }


onDelete Notify

void GameBase::onDeleteNotify(SimObject *obj) {

	if (obj == getProcessAfter())
      clearProcessAfter();

	Parent::onDeleteNotify(obj);
	
	if (obj == mTargetLinkedObject) 
	{
      //we were linked to something that was deleted
      //we might want to delete ourselves here, or maybe not?
	  unlinkFromShapeNode();
	  Con::errorf("GameBase:: onDeleteNotify mTargetLinkedObject is set to null");
   }   
   

}

linking and unlinking functions
void GameBase::unlinkFromShapeNode()
{
	if(mTargetLinkedObject)
	{
		clearProcessAfter();
		clearNotify(mTargetLinkedObject);
		mTargetLinkedObject = 0;
		mLinkNode = 0;
		mLinkedToMuzzle = false;
		setMaskBits(LinkedObjectChangeMask);
		//clint could call a script on unlink here
	}
}

void GameBase::linkToShapeNode(ShapeBase* targetObject , U32 node, bool linkToMuzzle)
{
	AssertWarn(  targetObject, "GameBase::linkToShapeNode targetObject is null!" );

	if(mTargetLinkedObject)
	{
		unlinkFromShapeNode();
	}
	// set-up dependency.
	processAfter(targetObject);
	// Make sure we know if it's deleted.
	deleteNotify(targetObject);
	
	setMaskBits(LinkedObjectChangeMask);
	mTargetLinkedObject = targetObject;
	mLinkNode = 0;
	mLinkedToMuzzle = linkToMuzzle;
	if(node >= 0 && node <= targetObject->getNumMountNodes() )
	{
		mLinkNode = node;
	}
	else
	{
		Con::warnf("linkToShapeNode: node %d not in attempt to link", node);
	}
	//clint: could call an onlink function here
}



The Console Method to do the linking:
//----------------------------------------------------------------------------
ConsoleMethod(GameBase, linkNodeToShapeNode, void, 4,5, "(ShapeBase obj, S32 nodeID, bool linkToMuzzle)")
{
   ShapeBase* targetObject;
   
   //if linkToMuzzle is true,
   //then the object should link to the muzzletransform for whatever
   //node slot.
   bool linkToMuzzle = (argc == 5)? dAtob(argv[4]): false;

   if (Sim::findObject(argv[2],targetObject)) {
	 object->linkToShapeNode(targetObject , dAtoi(argv[3]), linkToMuzzle );
   }
   else
   {
	   Con::errorf("linkNodeToShapeNode:  Unable to find target shapebase object: %s", argv[2]);
   }
}
#2
11/14/2005 (4:49 pm)
So to rephrase the problem:

how can I maintain the mounted link even when one of the objects goes out of scope?
But get rid of the link when one of the objects is deleted on the server?


thanks again for any advice.
#3
11/14/2005 (5:36 pm)
I suppose that in onDeleteNotify,
I could do something like:

if(isClientObject())
   flag as invalid link
else
   unlink as normal.

then in onAdd, which gets called when the ghost comes back into scope, I could do something like:
flag as valid link

and where I use the link info in the fxlight I could check that the link is valid before using it.

But the problem then, seems to be that the ghost/ object pointer I had is now invalid, and I need the server to resend the info? maybe that's not true, maybe it is the same, I'll have to look into that.
#4
11/18/2005 (10:38 am)
Unfortunately, this -is- a bit of a challenge! Off the top of my head the only suggestion I can give is to dig into the code that handles packing up InitialUpdateMask stuff in your object, and have it order things correctly, as well as have each mounted/linked object also pack up properly so that the clients always get the entire setup as a "package" of objects.
#5
11/19/2005 (6:44 pm)
Stephen, thanks for the reply.

quick question is this index I'm sending in pack update:
S32 gIndex = con->getGhostIndex(mTargetLinkedObject);

continually valid even if the local ghost is deleted?
I'll try to track it down, but if you know off the top of your head that'd help.

What I'm thinking I could do is on the client side when the delete occurs, do the temporary flagging of an invalid link, and in the objects tick check to see if the link is flagged in valid, and if so attempt to re-resolve the ghost, so when the object pops back into existence locally it will reconnect.
#6
11/19/2005 (6:56 pm)
Ok looks like it's not, looks like that index returned is the index into a free ghost array for the client which could change when the object pops back into scope?
#7
11/20/2005 (7:51 am)
No, it will not be valid, and will certainly not be consistent between "inscope", "out of scope", "back in scope" sequences for a particular connection.

I do think that you should look at the problem from an alternate perspective: it sounds as if your root issue is that with your linked objects, some can go out of scope when others are still in scope--so fix that. I would make it so that if any of the linked objects are in scope, all of the linked objects are in scope. That -should- keep things pretty steady for you (barring saturation of the bitstream, where you would then need to also look at updatePriority to make sure they come across as a unit).