Game Development Community

Please explain un/packUpdate and read/writePacketData

by Michael Bacon · in Torque Game Engine · 09/18/2007 (4:02 pm) · 12 replies

Can somebody just tell me if I have this straight?


un/packUpdate is used (regardless of who is the control object etc) if any of the Mask bits change right?


read/writePacketData just always happens for the control object.


So... for my camera I should probably only send the position/pitch in read/writePacketData and send the other parameters (the ones that don't change much, look offset, target object) in un/packUpdate.

Right?

#1
09/18/2007 (4:31 pm)
Quote:
un/packUpdate is used (regardless of who is the control object etc) if any of the Mask bits change right?

That's slightly confusing. un/packUpdate is called even if none of the mask bits are dirty, and is called for both control objects and the rest. In standard Torque there's an if statement which returns the function where the movement updates (or close to where that begins) begin. Movement updates and energy updates are handled in writePacketData instead.

Quote:
read/writePacketData just always happens for the control object.

Almost. read/writePacketData is used for the control object when the CRC doesn't match or when the control object changes - not sure about that last one.

Quote:
So... for my camera I should probably only send the position/pitch in read/writePacketData and send the other parameters (the ones that don't change much, look offset, target object) in un/packUpdate.

Totally depends on your game. If we assume that your camera is server-side and you don't want it visible to other clients, then you should only ever update movement in writePacketData. If you want cameras visible to other clients or in other ways let them know where they are located, you need to use packUpdate.
#2
09/18/2007 (5:00 pm)
Thank you for your quick reply!

My camera is server controlled but I would like the clients to see it (I'd like players that are not busy playing to be able to follow other players around and watch them, much like other games).

Currently I'm noticing that both methods are being called on my object and the same data is being transmitted each time.

I think I will be correct in my initial assumption. I should send the position/pitch in the PacketData methods and everything else in the Update methods. packUpdate should only send position/pitch to non-control objects (since control objects will already get that information in the PacketData methods).

It seems that most of the resources I've seen don't quite use these methods properly.
#3
09/18/2007 (5:14 pm)
I believe there's info in TDN about these.
#4
09/19/2007 (3:44 am)
I'm still very new to this stuff and I find navigating the TDN a little difficult (I have trouble with most wikis.. I think I'm incompatible with them).

Here is a direct quote that I could find...
[b]Control Object--the "Player"[/b]

...

That is the entire section on the control object.


I do my best to find an answer myself but sometimes it is easier just to ask.
#5
09/19/2007 (6:25 am)
Quote:
I think I will be correct in my initial assumption. I should send the position/pitch in the PacketData methods and everything else in the Update methods. packUpdate should only send position/pitch to non-control objects (since control objects will already get that information in the PacketData methods).

Yeah, that's the right way to do it.
#6
09/19/2007 (7:24 am)
I think there is an unstated assumption here that needs to be highlighted:

a "control object" is considered a control object for only one client--the one controlling it. Stefan's confirmation of the statement above is true, but only for one particular client. All other clients need to have position/pitch sent to them for all player class (or any object that can or [/imay[/i] be a control object for another client) in the packUpdate.

Let me try to draw up a use case here to try to clear it up a bit:

Client A is controlling Orc X

Client B is controlling Elf Y

If both Orc X and Elf Y are standing next to each other, everything is in scope to both clients.


When Client A gets updates, they come in two "chunks" within the packet: a control object update for their controlling object (Orc X), and normal ghost updates for all objects (which includes Orc X) :
<control object update--Orc X> <stuff> <normal ghost updates--Orc X, Elf Y>
When Client B gets updates, they also come in two chunks:
<control object update--Elf Y> <stuff> <normal ghost updates--Orc X, Elf Y>

Now, this is where it becomes complicated--the reason a control object gets a special update on a single object--the one they are controlling--is because this is considered the most important object to that client. Therefore, we use a different delivery mechanism for making sure the information gets to the client.

With normal ghost object updates, the server sends the data along, and if it shows up at the client, cool. If, for whatever reason, the packet containing that data gets dropped (the client never reports back that it received it), the server is going to eventually send a version of the data again (called a latest state update), but this only happens in response to two things:

--time passing
--the receiving client never acknowledging the packet itself

If you think about data transfer times, it will take 3 trips from server to client to server before the server knows it needs to send the data again (or an updated form of the data), because the data is sent in one packet, then never sent again until the server realizes it needs to re-send.

Control Object updates (write/readPacketData) however, get sent in every single packet until acknowledged. What this effectively means is that it doesn't matter if a key packet drops--the very next packet will have the control object update as well. This eliminates the need for the 3 trip acknowledgment and resend strategy of normal updates, providing important data as rapidly as possible to the client for their control object.
#7
09/19/2007 (8:52 am)
Could someone please clarify one point for me.

Quote:That's slightly confusing. un/packUpdate is called even if none of the mask bits are dirty, and is called for both control objects and the rest.

My experience has shown me that packUpdate is only called if at least one of the mask bits are set.

I have just tested this and it seems to be the case.

Can anyone confirm either way?

Gabriel
#8
09/21/2007 (3:23 am)
Okay, so I am still working on this (I've been busy the last couple days). I have run into a dilemma...


In my GameConnection::onCientEnterGame I create a camera, then spawn a player which sets the target of the camera to the player. This sounds good except that the player hasn't been ghosted yet. The server recognizes that its target is the player but when this information gets sent to the client for the very first time it is ignored because the ghost index is invalid (i.e. NOT_YET_GHOSTED).

Is there any way I can force the player to be ghosted before setting my camera target (without having to schedule an event)?
#9
09/21/2007 (4:15 am)
Quote:
My experience has shown me that packUpdate is only called if at least one of the mask bits are set.

I have just tested this and it seems to be the case.

Can anyone confirm either way?

Take a look at Player::packUpdate (). At the very bottom of that function, there's the energy update. It's not controlled by a mask bit. Are you saying it doesn't get written if none of the other mask bits are dirty? That sounds bad.
#10
09/21/2007 (8:41 am)
@Stefan: It's not bad for the pure demo, but it isn't a good example for building upon. As stock stands, everything that uses energy will cause another mask bit to be set, and therefore packUpdate will be called, which will in turn always pack up energy. That covers that case.

The reason it's not within a mask bit check is that energy is used client side to make sure you are allowed to perform a move (even though the cost is so low we never really notice it), so you want to make sure energy is updated whenever anything is updated, just to have an accurate value.

Definitely not a "best practice" technique since it's confusing, but an example of a reference implementation accomplishing a production requirement.

@Brian: You have a couple of options there:

--send a net event back to the server that indicates "unable to aim camera", and when receiving the event oin the server, re-send the initial update for the camera (or just a regular update)
--create a SimEvent on the client that waits for the player to get ghosted, and then on that event updates the client side camera object.
--do it in synchronized stages (like how mission loading happens)--i.e. ghost the player, send an event to client. When client receives player ghost, send an event to server. then server sends camera to client.
#11
09/21/2007 (5:07 pm)
The loading sequence calls ActivateGhosting to cause all the mission objects to be ghosted. There doesn't seem to be any equivalent calls for a single object and ActivateGhosting only works when ghosting is not already activated. Actual ghosting gets performed in the NetConnection::ghostWritePacket method. This method gets called every connection update.

It seems like too much of a pain to ensure player existence on the client before setting up the camera (it can easily be done as you describe above with event messages and such). However I'll take the easier route (I hope).

- The camera will be created and attached to the connection (setCameraObject).
- The player will be created.
- From the player's onAdd() method I can then attach to the connection's Camera Object.

Easy (to type.. unfortunately I have to wait until I get home from work tonight to try it).

What if for some reason the camera hasn't been ghosted at this point???

Maybe they should both try to attach to each other if not yet attached....
#12
09/22/2007 (5:05 am)
That idea didn't quite pan out but I found a VERY easy solution:

if (bstream->writeFlag(mask & TargetMask))
   {
      S32 gIndex = bool(mTargetObject) ? con->getGhostIndex(mTargetObject) : -1;
      if (bstream->writeFlag(gIndex != -1))
         bstream->writeInt(gIndex, NetConnection::GhostIdBitSize);
      [b]else
         retMask |= TargetMask;[/b]
   }

Thats what I call using the system how it is intended...