GMS 2.3+ Unions in GML


Hi, all.

(This is for use with networking, but it might be useful for save data)

I have created a BitPacker which allows you to read/write at the bit-level instead of the byte-level to/from buffers. The issue of course was reading and writing data types other than unsigned integers. The solution for signed integers was quite easy. When writing, just write it as you would an unsigned integer (since negative numbers will already be in two's complement). When reading, check if the nth bit is set (n being the number of bits you read for the signed integer) and if so, do some evil bitwise hacks to convert it to an actual signed integer that GMS2 can handle.

Strings were really easy as well; I have two options available: Null-terminated, or string-length specified at the start. For null-terminated, you just write 8 bits (unsigned integer) for the ord() of each character, followed by 8 bits of all zeroes. For string-length style, I just write a 16-bit unsigned integer for the length of the string followed by 8-bits per character. Obviously, these only work with ASCII encoding. I will have to check how GameMaker handles non-ASCII characters and create an encoding scheme for those.

Anyway, the thing that really made things difficult, however, were floating point numbers. GML doesn't have unions, and I don't know how to make extensions (I know C++, just never made an extension before), so I came up with a very simple workaround. I create a temporary buffer, write the float to it, and immediately read it back as an unsigned int, which I can write into the actual destination buffer. Reading is the same, read from the source buffer into a temp buffer as an unsigned int, then immediately read it back as a float and return that. This works with f16, f32, and f64 automatically. C++ however only uses floats (f32) and doubles (f64) so making an f16 requires implementing the half-precision float data type in C++ yourself.

Does anyone know of a better way to do this? Who knows how the performance of my code will hold up in practice. The other workaround I can think of would be to take the float, multiply it by some power of 10 (depending on desired accuracy), then divide it by that same power of 10 on read to get an approximation of the float.

EDIT: My workaround seems slightly incorrect with negative floats:
Writing F64: 3.14159265358979
Reading F64: 3.14159265358979000737

Writing F64: -3.14159265358979
Reading F64: -3.89159265358979000737

It appears as though the first two decimal places are off by 0.75, but the rest is the same as positive?

EDIT 2: Okay, it's only wrong if the start of the float is not aligned to a byte. I removed all test data and am just writing 64-bits for negative PI, then immediately reading it back and I get the same value. Must have something to do with the bits over a boundary.

EDIT 3: It's only wrong with 64-bit floats, and it seems to be a precision error in how GMS2 never has integers internally. Here is a chart of the number of bits padded before the F64 followed by the U64 that is read (with the binary next to that):

EDIT 4: Okay, I fixed it. It was definitely a GML precision error. I just wrote 2x 32-bit unsigned ints, and same when reading.
Last edited: