Game Development Community

Ghosting questions

by Anthony Lovell · in Torque Game Engine · 08/15/2006 (7:58 am) · 4 replies

Problem one: I cannot ghost NetObjects for some reason.

I create a NetObject on the server whose mNetMask = Ghostable.
I set up the established GhostConnection on the server to this:

c->setGhostFrom(true);
c->activateGhosting();
c->objectLocalScopeAlways(&myNetObject);


the client's connection is setGhostTo(true).

The client's log reports:
LogGhostConnection: Got GhostingStarting 1


The server's log reports:
LogGhostConnection: Ghosting activated - 1
LogGhostConnection: Got ready for normal ghosts 1 1


And yet the client never receives a ghost of the net object.

What other steps am I missing?

Is it necessary to set objectLocalScopeAlways() on all NetObjects which will use this static scoping BEFORE calling activateGhosting()?


----- on a related topic:

I see that the non-static scoping uses a call objectInScope() -- but there is no means by which an object can be declared to be OUT of scope. How do things fall out of scope under normal conditions -- simply by a failure for the scoping object to call objectInScope() on them when asked its opinion on the matter?

tone

#1
08/15/2006 (8:22 am)
Hmm... I am beginning to think that scopeAlways has been removed from the TGE version of this networking layer.

tnlNetObject.h declares NetObject has a friend class GhostAlwaysObjectEvent; -- but no such class exists in the project

A comment in this header file also indicates a mNetFlags mask bit of "ScopeAlways", and yet this is also missing in action.

I think perhaps the objectLocalScopeAlways() method means only to scope always to the local client (a function whose utility escapes me).

Is there no simple means by which the server can place a NetObject in persistent scope of a given connection, and a flipside means by which it can remove it from such scope?

tone
#2
08/16/2006 (9:12 am)
In response to Problem #1:

On your server, you should also call: setGhostTo(false)
On your client, you should also call: setGhostFrom(false)

I'm not sure if this is 'really' necessary, or if it will solve your problem, but it's worth a try.

In my application, I do not use objectLocalScopeAlways(), but I do have some objects that will always be in scope. I simply call setScopeObject(), and in the performScopeQuery() function, I set all the NetObjects to be in scope, using objectInScope(). This works for me, and it leaves room for later algorithmic improvement if I choose to cull certain objects from scope.

In response to "Related Topic":
You are right - failure to call objectInScope will force the object to be out of scope for that update.

Edit: I see in another post you made, you got your problem solved. I am curious as to what you did, and if you got your original method working.
#3
08/16/2006 (9:15 am)
Ok. I got this working with some difficulty. I may have found a bug, and I certainly found a needless constraint in GhostConnection which I can advise others in how to overcome.

First, the *possible* bug I found is in ghostConnection.cc's ::prepateWritePacket() code. It just looked wrong, especially when comparing it to the TGE version

void GhostConnection::prepareWritePacket()
{
   Parent::prepareWritePacket();

#ifdef MY_FIX
   if(!doesGhostFrom() || !mGhosting)
        return;
#else
   if(!doesGhostFrom() && !mGhosting) // this original TNL code seems wrong?  TONE
      return;
#endif

Second, I was failing to see my NetObjects ghosted, and it turns out that one reason was that I had no scoping object -- but there are certainly ways to use ghosting that do not rely on this mechanism. A quick pair of fixes to GhostConnection's ::writePacket() method allowed this to work when there was a null mScopingObject

first fix:
void GhostConnection::writePacket(BitStream *bstream, PacketNotify *pnotify)
{
   Parent::writePacket(bstream, pnotify);
   GhostPacketNotify *notify = static_cast<GhostPacketNotify *>(pnotify);

   if(mConnectionParameters.mDebugObjectSizes)
      bstream->writeInt(DebugChecksum, 32);

   notify->ghostList = NULL;
   
   if(!doesGhostFrom())
      return;

#ifdef MY_FIX
   if(!bstream->writeFlag(mGhosting))
      return;
#else
   if(!bstream->writeFlag(mGhosting && mScopeObject.isValid()))
      return;
#endif

and further down in the same function:
// don't do any ghost processing on objects that are being killed
      // or in the process of ghosting
      else if(!(walk->flags & (GhostInfo::KillingGhost | GhostInfo::Ghosting)))
      {
         if(walk->flags & GhostInfo::KillGhost)
            walk->priority = 10000;
#ifdef MY_FIX // this code should be made to work without a scoping object, right?
         else if (mScopeObject.isNull())
            walk->priority = F32(walk->updateSkipCount) * 0.1f; // cribbed from NetObject::getUpdatePriority()
#endif
         else
            walk->priority = walk->obj->getUpdatePriority(mScopeObject, walk->updateMask, walk->updateSkipCount);
      }
      else
         walk->priority = 0;

I will post right after this a helpful little bit that allows the server to publish a NetObject globally to an entire NetInterface -- all existing client connections will see it, and new joiners will see it. It is a very simple data model for objects you want EVERYONE to see.

tone
#4
08/16/2006 (9:24 am)
For the sake of completeness, in trying to mind Ben Garney's suggestion to monitor the logging output, I found many places where things could "go wrong" which did not generate any logging material at all. The following logging mods make GhostConnection a little more explicative when things are not going as the coder may have intended:

void GhostConnection::objectLocalScopeAlways(NetObject *obj)
{
   // TONE added this log msg
   if(!doesGhostFrom()) {
      TNLLogMessageV(LogGhostConnection, ("WARNING: objectLocalScopeAlways() called, but !doesGhostFrom()"));
      return;
   }

void GhostConnection::objectLocalClearAlways(NetObject *obj)
{
   // TONE added this log msg
   if(!doesGhostFrom()) {
      TNLLogMessageV(LogGhostConnection, ("WARNING: objectLocalClearAlways() called, but !doesGhostFrom()"));
      return;
   }

void GhostConnection::objectInScope(NetObject *obj)
{
      // TONE added these log messages
   if (!mScoping) {
      TNLLogMessageV(LogGhostConnection, ("WARNING: objectInScope() called, but !mScoping"));
      return;
   }
   if (!doesGhostFrom()) {   
      TNLLogMessageV(LogGhostConnection, ("WARNING: objectInScope() called, but !doesGhostFrom()"));
      return;
   }
   if (!obj->isGhostable()) {
      TNLLogMessageV(LogGhostConnection, ("WARNING: objectInScope() called for !%s->isGhostable()", obj->getClassName()));
		return;
   }
   if (obj->isScopeLocal() && !isLocalConnection()) {
      TNLLogMessageV(LogGhostConnection, ("WARNING: objectInScope() called for a %s which isScopeLocal() on non-local connection", obj->getClassName()));
		return;
   }
   // end of TONE's alterations

And, a bit further down in ::objectInScope:

// TONE added this warning
   if (mGhostFreeIndex == MaxGhostCount) {
      TNLLogMessageV(LogGhostConnection, ("ERROR: objectInScopeCalled, but maxGhosts=%d has been reached!", MaxGhostCount));
      return;
   }

And, finally...

void GhostConnection::activateGhosting()
{
   if(!doesGhostFrom()) {
      // TONE added this logging message
      TNLLogMessageV(LogGhostConnection, ("ERROR: activateGhosting() called, but !doesGhostFrom()"));
      return;
   }

I hope this helps make this code easier to track when others are hitting snags in future

tone