Game Development Community

Why is projectile mSourceObjectSlot sent as U32 when it's S32?

by Lukas Joergensen · in Torque 3D Professional · 06/03/2012 (3:43 pm) · 7 replies

As the title says, I got an out of bounds error ( probably self provoked as no one else seems to have had that problem )
But i stumbled in to this line of code:
stream->writeRangedU32( U32(mSourceObjectSlot),
                                    0, 
                                    ShapeBase::MaxMountedImages - 1 );
But according to the docs, mSourceObjectSlot is an S32 so shouldn't look like this?
stream->writeRangedS32( S32(mSourceObjectSlot),
                                    0, 
                                    ShapeBase::MaxMountedImages - 1 );
Long story short, my out of bounds bug went away when changing it to S32.
Now I am stuck on a stack overflow problem instead, are the two related?

#1
06/04/2012 (12:12 am)
Good catch. I seem to remember seeing this some time ago and wondering about it. I may have even fixed it as well.

Wikipedia has some good information on stack overflows.. I'd start with the two biggies: infinite recursion, and large local variables. Check any recursive functions to make sure there's always a base case!
#2
06/04/2012 (1:37 am)
The stack overflow was kindda related :P Seems like the root of my problems was that i had accidentally overriden the emitParticles function which the projectile needed so it called the wrong one :)
#3
06/04/2012 (8:49 pm)
Don't forget to read S32 in unpackUpdate... :)

Thanks Lukas for your good eye.

Now, though, I'm curious: Right above this is another S32, ghostIndex. Why is this being written out as a U32 as well, when looking in NetConnection it was indeed an S32 being returned?
#4
06/08/2012 (8:51 pm)
It makes no difference at all, as neither ghostIndex nor mSourceObjectSlot should ever be >= 2,147,483,648 and signed and unsigned 32bit integers are represented exactly the same for all values lower than that.

It's a sad consequence of using a scripting engine that does not support unsigned integers that variables that represent array indexes are typed as S32 instead of U32, as they more properly should be. mSourceObjectSlot was probably typed as an S32 because it is exposed to script as an object member field; but when packing it to the stream, writeRangedU32 was used because it makes sense, as it should never be < 0.
#5
06/09/2012 (9:09 am)
@Scott,

I had gone ahead and tried it as S32 and it worked--I'd decided a silent cast was performed and allowed because the value always fell into the valid range regardless--that a level has a finite, "real" number of objects which is quite small I already knew as I'd smacked into the ceiling.

I got puzzled why the WriteRangedU32 method would be used when there exists a (presumably more applicable) WriteRangedS32 method--for practical purposes they may be interchangeable, but why have two methods in the first place?

It's not the kind of thing you find an answer to in a beginning C++ book.

Thank you very much for taking the time!
#6
06/09/2012 (3:27 pm)
Well you're right, it is simply performing an implicit cast. Your compiler may or may not bother to throw a warning, but it won't cause an error. If you convert a negative S32 to a U32, you'll end up with an absurdly large value, and if you convert a U32 over 2,147,483,647 you'll end up with a negative S32.

I would prefer to use a U32 for an index, as it innately asserts that the value is never negative. When you're reading the code, you know that will never be a negative value, and when you're range checking, you need only check the upper bound. Beyond that, however, it really makes no difference which you use.

writeRangedU32 is used in many places because it existed long before writeRangedS32 was added. writeRangedU32 uses writeInt, which takes a value and a bitcount as arguments, and automatically calculates the optimal bitcount. writeRangedU32 was likely intended for the purpose of sending index and id values, like datablock and ghost ids, which can never be negative. writeInt and writeSignedInt already existed for sending more general purpose signed integer values. At some much later point, after Torque 1.5 I think, someone decided that having a writeRangedS32 function would be useful, and so it was added (writeRangedS32 simply adds the minimum to both the value and the max and them passes it on to writeRangedU32), but it wasn't necessary to find all the existing uses of writeRangedU32 and change them all to match, especially when most cases involved S32s which were index or id values that would never be negative anyway (they were only S32 for ease of use with the scripting engine).

edit: That doesn't explicitly answer your question, though, does it? Why are have both functions? Because RangedU32 came first, for sending ids, then someone added RangedS32 because they wanted to send signed integers without having to figure out the optimal bitcount every time.

Really, you could replace both functions with a single writeRangedInt function if you wanted to. The only downside to that would be that you loose the ability to send U32 values over 2,147,483,647. Would anyone ever need to send an integer that large over the network? Probably not, but you never know.
#7
06/12/2012 (6:26 am)
@Scott,

"...writeRangedU32 is used in many places because it existed long before writeRangedS32 was added..."

Well, see, that makes sense once placed in context.

I can see one reason to want numbers more than 2.1 billion... if there is some type of earnings system, it is entirely possible that a dedicated player would come up with more than two billion economic units eventually in a persistent world.