GM:S 1.4 Multiplayer - Send Surfaces

Anixias

Member
So, I tried using buffer_get_surface and buffer_set_surface to send a surface of a dynamically created spaceship from the server to each client whenever a player changes their active ship. However, my cousin is unable to see any ships (except pirates, who use sprites defined in the actual project).

Here is the sending code:
Code:
///Sync player ships' graphics
if started and tick % round((60)/1) == 0 //1 update per second
{
    if ds_list_size(client_id) > 0
    {
        for(var i = 0; i < ds_list_size(client_id); i++)
        {
            //i is receiving
            var keep_going = true;
            with(obj_client) if client_id == other.client_id[| i] keep_going = false;
            if keep_going
            {
                for(var j = 0; j < ds_list_size(client_id); j++)
                {
                    var o = client_object[| j];
                    if instance_exists(o)
                    {
                        buffer_seek(buff,buffer_seek_start,0);
                        buffer_write(buff,buffer_string,server_id);
                        buffer_write(buff,buffer_u16,command.changeship);
                        buffer_write(buff,buffer_u32,o.id);
                        buffer_write(buff,buffer_u32,sprite_get_width(o.sprite_index));
                        buffer_write(buff,buffer_u32,sprite_get_height(o.sprite_index));
                        buffer_write(buff,buffer_u32,sprite_get_xoffset(o.sprite_index));
                        buffer_write(buff,buffer_u32,sprite_get_yoffset(o.sprite_index));
                        var psurf = surface_create(sprite_get_width(o.sprite_index),sprite_get_height(o.sprite_index));
                        surface_set_target(psurf);
                        draw_sprite(o.sprite_index,0,-sprite_get_xoffset(o.sprite_index),-sprite_get_yoffset(o.sprite_index));
                        surface_reset_target();
                        buffer_get_surface(buff,psurf,0,buffer_tell(buff),0);
                        surface_free(psurf);
                        psurf = undefined;
                        network_send_udp(server,client_ip[| i],client_port[| i],buff,buffer_tell(buff));
                    }
                }
            }
            else continue;
        }
    }
}
The important code is this part:
Code:
                        buffer_seek(buff,buffer_seek_start,0);
                        buffer_write(buff,buffer_string,server_id); //This is received earlier than the posted receive code.
                        buffer_write(buff,buffer_u16,command.changeship);//This is received earlier than the posted receive code.
                        buffer_write(buff,buffer_u32,o.id);
                        buffer_write(buff,buffer_u32,sprite_get_width(o.sprite_index));
                        buffer_write(buff,buffer_u32,sprite_get_height(o.sprite_index));
                        buffer_write(buff,buffer_u32,sprite_get_xoffset(o.sprite_index));
                        buffer_write(buff,buffer_u32,sprite_get_yoffset(o.sprite_index));
                        var psurf = surface_create(sprite_get_width(o.sprite_index),sprite_get_height(o.sprite_index));
                        surface_set_target(psurf);
                        draw_sprite(o.sprite_index,0,-sprite_get_xoffset(o.sprite_index),-sprite_get_yoffset(o.sprite_index));
                        surface_reset_target();
                        buffer_get_surface(buff,psurf,0,buffer_tell(buff),0);
                        surface_free(psurf);
                        psurf = undefined;
                        network_send_udp(server,client_ip[| i],client_port[| i],buff,buffer_tell(buff));
This is the receiving code:
Code:
    case command.changeship:
        if connected and r_id == server_id
        {
            var o = noone;
            var oid = buffer_read(bf,buffer_u32);
            with(obj_ship) if object_id == oid o = id;
            if instance_exists(o)
            {
                var sw = buffer_read(bf,buffer_u32);
                var sh = buffer_read(bf,buffer_u32);
                var sx = buffer_read(bf,buffer_u32);
                var sy = buffer_read(bf,buffer_u32);
                var psurf = surface_create(sw,sh);
                buffer_set_surface(bf,psurf,0,buffer_tell(bf),0);
                if asset_get_index(sprite_get_name(o.sprite_index)) == -1 sprite_delete(o.sprite_index);
                o.sprite_index = sprite_create_from_surface(psurf,0,0,sw,sh,false,false,sx,sy);
                surface_free(psurf);
                psurf = undefined;
            }
        }
        break;
The client always generates an empty or invisible graphic.
Shouldn't this code work perfectly? I've been having so many issues with buffer_get/set_surface that I had to stop using those functions elsewhere in the game but this time it would be much faster than sending individual pixel data, maybe. I could just make the server send each individual pixel but I want this to work.
 
Last edited:
D

Deanaro

Guest
If the dynamic ship sprite is built from smaller sprites that are in the project, it would be better off to send the coordinates of those sprites in relation to the ship's origin and have it be constructed on the other end.
It would be much more efficient than sending a surface. This also means you can do animation much more easily (and efficiently).
But i guess if the ship is hand drawn or for any other reason it will be necessary to send images.
In that case there is a problem with the fact that surfaces are valotile and can be deleted from memmory at any time without your control.
Maybe save the created sprite in a temp folder as png and send it to the other end at the start of the match.

Just remember if you send and download an image every second thats going to potentialy use alot of data for a game.
 

Anixias

Member
Ships are painted in a paint program I've made into the game's workshop.
I would only need to sync once every time a player changes their current ship.

I think a slightly more efficient (than your send-a-png) method would be to just loop through the image, row by row, saving the 32 bit color (including alpha) of each pixel and then just sending the packet. Since the client would know the width and height of the image already, they could just as easily reconstruct the image.

The only problem is that large ships (my cousin made a HUGE one) would be hefty on the server (and large ships would just freeze the server).

I could let the server only send the image in chunks of like 10KB at a time or something, so it doesn't freeze the server. Then, the client would wait until it has the whole width and height of data before telling the server it got it and then reconstructing the image.
 

Murzy

Member
A couple of problems:
  • You are sending something that sounds like it should be reliably updated on the other client's end over unreliable UDP channel.
  • You cannot just squash multiple kBs of data into a single network packet, espcially UDP, and assume that it is sent/transmitted properly. Try something in the territory of a ~500 bytes message as the MTUs do vary. TCP, being a stream oriented connection should handle this more neatly, but then other problems may rise. Still, I would try to compress this or split it to multiple messages.
  • Also, I wouldn't update something as heavy as this on a timer / once per second as your code suggests. Why should you? Do the update only when the actual changes are happening.
 

Anixias

Member
I want to only send when it first changes but I'm not sure how to send TCP messages when the entire game is built in UDP... If I could figure that out, I'd have no more issues. He can now see my ship properly once I looped through the whole sprite's color data and sent that.

EDIT:
For him, our ships randomly turn black, but still retain the right alpha values.
 
Last edited:

petteri

Member
Hi Anixias, I strugle with the same issue. Did you manage to send sutface over the net ? Could you tell how you did that?
 

Bingdom

Googledom
You cannot just squash multiple kBs of data into a single network packet, espcially UDP, and assume that it is sent/transmitted properly. Try something in the territory of a ~500 bytes message as the MTUs do vary. TCP, being a stream oriented connection should handle this more neatly, but then other problems may rise. Still, I would try to compress this or split it to multiple messages.
It's the transport layer's responsibility for packet segmentation. It's fine to pass down files larger than the MTU from the application layer.
In other words; don't split the buffer.

I haven't tested this code, but I believe the culprit comes from the buffer_set_surface() function. See this thread. Here's the bug report.
Try updating your Intel drivers, or switch to your dedicated graphics card.

Given that this is a dxd9 problem, GMS2 shouldn't have any issues with this.

If all the above solutions don't help, try taking out the networking part and see if the generation and reading of the buffer works.
If the reading/loading works fine, then it's likely your router dropping packets due to network congestion. Try sending less over a short period of time.

I'd suggest using TCP. With UDP, there's no guarantee that the packets will arrive and in the correct order.
 
Top