Game Development Community

Value in field in stand alone different from dedicated

by Graham Evans · in Torque Game Engine · 01/26/2007 (8:42 am) · 13 replies

Greetings all.

Okay, hopefully someone can help me to preserve the little amount of hair I have left.

I was trying to add a field to the Item datablock. In code so did the following :-

In Item.cc

In ItemData::ItemData. I added.
InvOrbitDist = 1;

In ItemData::initPersistFields. I added
AddField("InvOrbitDist", TypeF32,Offset(InvOrbitDist,ItemData))j

In ItemData::packData(Bitstream* stream). I added
Stream->writeFloat(InvOrbitDist, 10);

And in ItemData::unpackData(Bitstream* stream) I added
InvOrbitDist = stream->readFloat(10);

Finally in Item.h I added

F32. InvOrbitDist; into ItemData: public ShapebaseData

Okayyyyyy. Here's what happens

If I do NOT add a field to the item datablock, when I dump the datablock I get InvOrbitDist reported as being 1 in both standalone and dedicated server modes. (Good result)

If I DO add a field to the item datablock of 1.5 in the declaration. Then in dedicated server mode I get 1.5 reported on a dump. HOWEVER using the exact same code with the exact same dump in standalone the same dump reports the field to have a value of 0.49862

What the heck am I doing wrong 'cause it must. Be me missing something obvious :)

Regards

Graham

#1
01/26/2007 (9:06 am)
Graham, I think it's because writeFloat() is only for values 0-1. 1.5 is out-of-bounds.
#2
01/26/2007 (9:28 am)
WriteFloat () is not only for values 0-1.
#3
01/26/2007 (9:39 am)
Correction... According to a comment in the header writeFloat() is only for values 0 to 1. writeSignedFloat() is only for -1 to 1. I haven't tested though, so maybe the comment is crap.
#4
01/26/2007 (9:45 am)
Thanks for the info.

I will try some othe stream options but it still copnfuses me thst the dedicated server correctly passes 1.5. But the stand alone doesn't.

I would be happier if they both passed the same value even if npt the correct one ;)

I will look for something else to pass in the stream and report back

Regards

Graham
#5
01/26/2007 (9:58 am)
After looking at the implementation... it does appear that writeFloat() is *not* limited to 0-1. The code comment was erroneous, therefore discrediting my initial theory.
#6
01/26/2007 (10:20 am)
Hmm. my reading of the source lines up with the comment that it's only good for values in [0, 1].

let's say bitcount is 4.

writeInt((S32)(f * ((1 << bitCount) - 1)), bitCount);

(1 << 4) - 1 = 15
so
writeInt(f * 15, 4).

now as you know the largest int which can be transmitted in 4 bits is 15.

so if f is larger than one, we're gonna lose the high-order bits.

yeah ?
#7
01/26/2007 (10:28 am)
Oh well, it looks like I was right the first time... I just went stupid when I ran the math through my calculator and forgot to truncate the bits.

Graham, the dedicated server may be keeping the correct value because it never has to pack/unpack the value to itself, so it probably keeps the orignal value which never gets munged by the bit-compression going on in writeFloat.
#8
01/26/2007 (10:36 am)
Hi all,

Thanks for the info. - am still not sure how on earth the dedicated server was passing the correct info fo values > 1 but anyhoo I have changed it so it now seems to work. The big problem is I can't program so I have to look for examples to copy.

For reference this is what I changed.... In packdata I changed to :-

If(stream->writeflag(InvOrbitDist != 1.0))
Stream->write(InvOrbitDist);

And in unpack I changed to :-

If(stream->readflag())
Stream->read(&InvOrbitDist);
Else
InvOrbitDist = 1.0;


This seems to work although full testing on multiple remote clients will need to wait till this evening.

Thanks again all, and if anyone can work out why the origonal method has different results on the same code in stand alone and dedicated I would love to hear it ;)

Regards

Graham
#9
01/26/2007 (10:43 am)
Ahhh. Thanks Jeff.

Hmmmm. I have to get my brain round this to try to understand. My dumps were taken on the server and were before any info was being passed to the client portion but that implies to me that standalone is transmitting info from the server portion to the client portion when a dedicated server doesn"t do likewise.

Okay I will let the brain rest a while but thanks for the info and I think it's solved now:)

Regards

Graham
#10
01/26/2007 (11:03 am)
If you put simple printf statements next to the place where you pack/unpack your values, you'll observe some interesting behavior. (It helps if you also print the address of "this" just so you can match messages with datablock objects.)

The dedicated server will pack the value once and the client will unpack it once. As expected , I think. Since the server never unpacks, it will still have the original value.

In stand alone things get a little strange, the value will get packed and then unpacked back to the same object. It's as if the server pretends to ship the value over the network but just unpacks it back to itself. Even more strange, somewhere along the way a temporary copy of the datablock is created where the value is unpacked then packed. I think the engine must have to follow this process in order to make sure the stand alone client datablocks end up in the same state as remote client datablocks.
#11
01/26/2007 (11:20 am)
Quote:
After looking at the implementation... it does appear that writeFloat() is *not* limited to 0-1. The code comment was erroneous, therefore discrediting my initial theory.

I actually took a look at how health was calculated and it looked like you were correct from the beginning. Are you sure the comment was crap?

Edit: Doh. Please disregard my mindless talking, I didn't read the whole thread.
#12
01/26/2007 (11:41 am)
Just to confirm the final findings--writeFloat is certainly capped to writing values between 0-1, and writeSignedFloat is -1 to 1.

The use for when you want to send numbers that don't meet that range is to normalize by finding a constant you can divide by on the sending side to get down to the nomalization range, and multiplying on the receiving side to "un-normalize" the actual value.

If you look through player.cc and search for writeFloat/writeSignedFLoat, you'll see multiple uses of the function this way.
#13
01/26/2007 (12:10 pm)
I went and added some asserts for range in the four write[Signed][Float/Int] routines and found that writeInt() is being called with out-of-range values all over the place, so it's pretty useless to add warnings around it.

however there were no violations of write[Signed]Float(), which sort of surprised me,
so folks may find the following useful:

void BitStream::writeFloat(F32 f, S32 bitCount)
{
   AssertWarnBreak(f >= 0.0f && f <= 1.0f, "BitStream::writeFloat(): value out of range");
   writeInt((S32)(f * ((1 << bitCount) - 1)), bitCount);
}
...
void BitStream::writeSignedFloat(F32 f, S32 bitCount)
{
   AssertWarnBreak(f >= -1.0f && f <= 1.0f, "BitStream::writewriteSignedFloat(): value out of range");
   writeInt((S32)(((f + 1) * .5) * ((1 << bitCount) - 1)), bitCount);
}

actually, the asserts could be a little better there for writeSignedFloat,
since the range is actually a function bitCount.
eg with 4 bits, it's about [-0.9, 0.9].

note AssertWarnBreak is my own addition,
and it goes a little like this:
platformAssert.h:
#define AssertWarnBreak(x, y)      \
         { if ((x)==0) { \
            PlatformAssert::processAssert(PlatformAssert::Warning, __FILE__, __LINE__,  y); \
            Platform::debugBreak(); } }

edit oops - don't forget to add this a few lines down, below the #else:
#define AssertWarnBreak(x, y)    { }

- this way if you've got a debugger attached, you get a nice breakpoint.
if you don't have a debugger attached, the program "crashes", but there's a line in the console.log saying why. Debug builds only, of course.