mas-bandwidth / yojimbo

A network library for client/server games written in C++
BSD 3-Clause "New" or "Revised" License
2.46k stars 239 forks source link

Sending arrays of variable size #180

Closed kingoftheconnors closed 2 years ago

kingoftheconnors commented 2 years ago

Sorry to leave so many questions on the issues page. I promise this is the last one!

Is it possible to send arrays of variable size? This is for a server->client message that sends the state of all players. There can be up to 20, but there should usually be only 10 players at a time. Right now, each player only takes 8 bytes to send all its data, so that's manageable, but I want to know in case the player info gets bigger and packet size becomes a concern.

I've tried using serialize_bytes, which works if the array has a pre-defined size:

struct GameStateMessage : public Message
{
    uint8_t arr[5];
    GameStateMessage() {}

    template <typename Stream> bool Serialize( Stream & stream )
    {        
        // serialize
        serialize_bytes(stream, arr, 5);
        return true;
    }

    YOJIMBO_VIRTUAL_SERIALIZE_FUNCTIONS();
};

Tries sending {1, 2, 5, 12, 17}, gets back out {1, 2, 5, 12, 17}

But when I change it to a pointer, it no longer works (size is assumed to be 5 for buffer simplicity)

struct GameStateMessage : public Message
{
    uint8_t *arr;
    GameStateMessage() {}

    template <typename Stream> bool Serialize( Stream & stream )
    {        
        // serialize
        std::cout << "Game state message " << (short)arr[0] << (short)arr[1] << (short)arr[2] << std::endl;
        serialize_bytes(stream, arr, 5);
        return true;
    }

    YOJIMBO_VIRTUAL_SERIALIZE_FUNCTIONS();
};

Tries sending {1, 2, 5, 12, 17}, gets back out {???, ???, ???, ???, ???} The output changes every time.

Any ideas?

Green-Sky commented 2 years ago

I recommend running with a debugger.

kingoftheconnors commented 2 years ago

After some experimentation, I figured out that it wasn't the pointer that was the problem, it was the initialization of the array!

GameStateMessage *message = (GameStateMessage*)client.CreateMessage(MessageType::GAME_STATE_MESSAGE);
message->arr = new int[global::MAX_PLAYERS];
for (int i = 0; i < global::MAX_PLAYERS; i++) {
    message->arr[i] = i*2;
}
client.SendMessage(message, UNRELIABLE_UNORDERED_CHANNEL);

This is the client sending the array. Obviously, it declares the array, but when reading the array, it never got initialized!

So this example worked if forcing the array to be size 5:

struct GameStateMessage : public Message
{
    int *arr = new short[5]; // Initalized for both sender and reciever

    GameStateMessage() {}

    template <typename Stream> bool Serialize( Stream & stream )
    {
        for (int i = 0; i < 5; i++) {
            serialize_int(stream, arr[i], -390, 3700);
        }
        return true;
    }

    YOJIMBO_VIRTUAL_SERIALIZE_FUNCTIONS();
};

So, pointer array works, right? Well, the main problem is how the reader will know what the length will be. That's the entire point, right?

Taking some inspiration from the serialize_string function, we just need to send the size FIRST! And then use that to dynamically allocate the received array:

struct GameStateMessage : public Message
{
    const int MAX_ARRAY_SIZE = 64;  // This is our theoretical highest array size
    int *arr;
    int length;

    GameStateMessage() {}

    template <typename Stream> bool Serialize( Stream & stream )
    {
        serialize_int( stream, length, 0, MAX_ARRAY_SIZE );
        if (Stream::IsReading)
        {
            arr = new int[length];
        }
        for (int i = 0; i < length; i++) {
            serialize_int(stream, arr[i], -390, 3700);
        }
        return true;
    }

    YOJIMBO_VIRTUAL_SERIALIZE_FUNCTIONS();
};

And this works! Tries sending {1, 2, 4, 6, 8}, gets back out {1, 2, 4, 6, 8}

Thanks @Green-Sky for the little extra push to figure this out!

Green-Sky commented 2 years ago

Remember to free the memory, or you have a memory leak in your hands. Personally I would just go with std::vector.

kingoftheconnors commented 2 years ago

Ah, good point! Thanks!

Since this issue is taken care, I'll close it.