In Lacewing there are three different kinds of messages that you can send to the server, to the channel, or to peers.

You can send or blast them, using TCP or UDP respectively.

  1. The most common kind suitable to chat applications is the Text message, which is literally just plain text.
    Some guides and games advise you to pair coordinates up like
    Str$(X("Object")) + "|" + Str$(Y("Object"))
    …and decoding it by splitting the text with a string parser, but this is inefficient, so only use this if you are a beginner to Fusion.
  2. The least commonly used is the Number message, which sends only an integer number in the same range that Fusion supports (around ±2.1 billion). It cannot send floating-point values, such as "2.5", "3.001", etc.
  3. The third type is the Binary message, the most efficient way for multiple pieces of data to be sent at once.

With the binary message, you can send any kind of data you want, in any combination, including text, numbers, parts of a file… just any data in any order you want, in any order or combination, making it perfect for messages handling any scenario..

Binary messages can be used to send things like X,Y coordinate pairs, or even X, Y, Angle, Velocity, Animation, Animation Frame, etc., which is why it is most suitable for games.

The rest of this article is dedicated to those types of messages.

Binary messages

Based on an forum post by Jamie McLaughlin.

Binary messages (previously called Stack messages) are mixed-content messages. They can contain any combination of, and in an order, strings, numbers, files, etc.

Because you design the structure of the messages yourself, you can, for example, use them to transfer the state of an object; what position it is on screen, what animation is playing, if it's taking damage, etc.

For example, instead of using Text messages and separating different parts of the message with a token (such as a comma "," or a pipe "|"), you can use binary messages to send them in a more condensed format.

Consider sending a position, as an X-Y coordinate pair:


As a string, this is 7 characters in length, plus a null termination character to mark the end of the string, meaning that you will be sending 8 whole bytes for every movement!

Image showing numbers of 104, 178, which each digit is a byte, making seven bytes:49, 48, 52, 44, 49, 55,56.

Using a binary message, however, you can condense the message size to 4 or even 2 bytes:

Image showing two byte, with values 104, 178.

In the above example, two bytes (104 and 178) are sent, rather than eight bytes. This is a huge bandwidth saver, as it is four times smaller than the Text message.

Not only that, but splitting the text message to read the components doesn't need to happen. So you don't have to do this:

  1. Find the comma dividing up the text, to find how many digits X is.
    Find(received_msg, ",", 0)
  2. Plug the position into a Left$() to get just the left side of that comma
    Left$(received_msg, Find(received_msg, ",", 0))
  3. Convert the read text from Left$(), in this case "104", to a number 104:
    Val(Left$(received_msg, Find(received_msg, ",", 0)))
  4. Do the same for Y of 178:
    Val(Right$(received_msg, Len(received_msg)-Find(received_msg, ",", 0) – 1))

All of the conversion complication is avoided when using binary messages.

This is important for reducing lag in games, as movement happens (usually) at 60 FPS, so 60 times a second, and doing all these parsing and re-parsing introduces slowdown that really adds up when multiplied by 60 and multiplied again by the number of players in the channel all sending you their positions.

For example, in a 5 player game, that's 240 text splitting and parsing a second.

Using binary messages

A binary message must be created in its components before it can be sent.

To do this, you 'push' bytes onto the end of the message. The bytes are made of bits, and depending on how they are read and written, they can make up strings, numbers, or files.

  • A bit is a single 0/1 in computer memory. Not too different from Fusion's internal flags.
  • A byte is a number made of 8 bits. 

How a byte works is that each bit is assigned a power of 2 based on its position in the byte; for example, a bit in a byte can be worth 1, 2, 4, 8, 16, 32, 64, or 128, and adding all those together gives you the byte's max value of 255.

As a visual demonstration:

So the byte 43 in binary is 0010 1011.

When it comes to writing text, if you've done childhood ciphers (A = 1, B = 2) you'll understand how a byte of 0 to 255 (or 0000 0000 to 1111 1111) can be a letter.

The exact text translation of byte value includes more than letters; any character such as numbers, punctuation, even the Delete key on your keyboard sends its own character (an invisible one called a control character).

But for a Greek child making a cipher, the Greek alphabet α-ω isn't like the Latin alphabet A-Z… and so 1-26 couldn't mean A-Z, and so a Greek operating system wouldn't use that, but would require a new list of letters and their byte values.

The table of characters being used to translate the byte values to text is called a "character set", or "charset" for short. The charsets that Fusion uses are covered under greater detail in the Unicode notes topic, but if you're building a binary message, all you will need to know is that one byte is not always one character.

(The reason is that a byte isn't enough to cover every character in every alphabet and syllabary, and Unicode covers every character, even ancient Egyptian hieroglyphs. So in scenarios where the character is less common, it uses multiple bytes for that character.

So don't think that if you read from message index 1, you'll be reading the 2nd character.)

Which variable should I use?

Here's a simple flowchart.

Flowchart describing types to use. Use fixed size text for single characteres that aren't ASCII, but the rest is deduction.

Using binary messages will save a lot of message size in the long run, and also helps you with other coding languages, as languages such as C# use the same types.

Variables you can add to a binary message take up different amount of space in the message:

  • Byte (8-bit; takes up 1 byte)
    -128 to +127 (signed)
       0 to +255 (unsigned)
    Does not allow fractional numbers.
  • Short (16-bit; takes up 2 bytes)
    -32,768 to +32,767 (signed)
          0 to +65,535 (unsigned)
    Does not allow fractional numbers.
  • Integer (32-bit; takes up 4 bytes)
    -2,147,483,648 to +2,147,483,647 (signed)
                 0 to +4,294,967,295 (unsigned)
    Does not allow fractional numbers.
  • Float (32-bit; takes up 4 bytes)
     3.4e +/- 38 (always signed)
    Floats are used for fractional numbers.

    Floats are "floating-point" numbers, so called because where the decimal point is "floating" somewhere in the digits. You can use floating-point for numbers with no decimal places, though.
    A float can go much higher and lower than an integer, but loses precision at longer numbers. Unfortunately, Fusion does not support a more accurate floating-point type than a 32-bit float.
  • String is text, and takes up variable amount of bytes.
    If you stick to null-terminated strings in binary messages, and read the binary messages back in the order you added to the binary when sending it, you won't need to worry about the size of the strings.
    The characters and how much space they use in a binary message are somewhat complicated, and described under Unicode notes.

Building a binary message is easy, but reading it can be slightly harder.

Message indexes are byte-based, and start at 0, meaning the first byte in a binary message is byte number 0, the second is byte number 1, the third is 2, etc.

Cursor expressions (described below) eliminate the need to know byte indexes for the most part, but as you shouldn't use them in conditions or read the same variable twice, knowing the sizes is important.

For a demonstration of message size differences, consider a binary message comprised of the frame of an animation (e.g. 3), the size of a circle (e.g. 572), the score (e.g. 31333337), and the percent completion of the level (e.g. 100):

Image showing the previously described list of variables as they would be put in a binary message. Adding a byte, short, integer, then another byte.

To read the binary message upon receiving it, you would get the animation frame by getting the unsigned byte at 0, then at index 1 you would read the size of the circle as an unsigned short.

Remember though that a short takes up two bytes of space, so you would read the score as a signed integer two bytes later at index 3. An integer is four bytes, so four bytes later at index 7 you could read the unsigned (or signed) byte that represents the percent progress through the level.

So, it will look like:

Fusion event, 1 condition, 4 actions. On received binary message. Set Animation frame to unsigned byte expression, reading from index 0. Set circle size to unsigned short expression, reading from index 1. Can't fit the rest.

That makes all eight bytes accounted for. If you had sent this as a text message: "3,572,31333337,100" that would be 18 byte message! That's over twice the 8 byte binary message!

Not to mention all the performance wasted splitting up the received text again, parsing from text to number and vice versa.

When reading, you may prefer to use cursor expressions, which have their own internal index, making keeping track of sizes and indexes not as necessary. Knowing the underlying mechanic helps avoid confusion.

Cursor expressions

The cursor uses an internal index, starting at 0 for each message, and increments the index automatically as you read using cursor expressions; for example, if you read a short (2 bytes), the index is incremented by 2.

Fusion event, 1 condition, 4 actions. On received binary message. Set Animation frame to cursor unsigned byte expression. Set circle size to cursor unsigned short expression. Can't fit the rest.

As long as you read in the same order that you added them on the sender's side, then you will have no reason to keep track of the index manually.

However, you can confuse things for yourself – it may not be obvious, but reading the cursor expressions in a condition will still move the cursor position (even the condition doesn't return true), so it's recommended to not use cursor expressions in conditions like Compare Two General Values, as this will result in unexpected results.

For example:

2 Fusion event, 2 conditions for both. Lacewing Client, on binary message received. First event checks Cursor Byte is 0, second event checks Cursor Byte is not 0.

If a two-byte message is received, with the two bytes 5 and 0, neither event will trigger, even though you expect the second to run.

This is because the first CursorByte() expression reads the byte 5 at message position 0 as expected, but moves the cursor to position 1, so the second event's CursorByte() will read from position 1, and read the byte 0.

Signed vs unsigned

You may now wonder why you can't choose to add/write a signed or unsigned type to the binary to send, but you have to choose when reading a number. For any given type, signed and unsigned take the same amount of memory and are represented exactly the same way!

As seen above, an unsigned byte has 256 different values, 0 to +255. A signed byte also has 256 different values, -128 through +127.

This is because the signed byte takes part of the unsigned byte's range, +128 to +255 and shifts it on the number scale, so it is used instead for -128 to -1. In binary terms, in a signed number, the greatest power of 2 bit is instead used for sign; 0 is +, 1 is -.

As a result, if you add an unsigned byte as +255, then read it back as signed, it will read back as -1.

Because the data is the same, the only reason to interpret it as signed (having a positive or negative sign) or unsigned (only being positive) is whether you want to be able to have it negative.

You can experiment with the range, but just make sure you read and write back the same. If you expect a number you're sending will go as both -120 and +250, then that's too wide a range for a byte, as you won't know how to read it on the receiving side. Instead, use a short.