GM:S 1.4 Multiplayer - Send Surfaces

Discussion in 'Programming' started by Anixias, Jan 10, 2017.

  1. Anixias

    Anixias Member

    Joined:
    Jun 27, 2016
    Posts:
    204
    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: Jan 10, 2017
  2. Deanaro

    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.
     
  3. Anixias

    Anixias Member

    Joined:
    Jun 27, 2016
    Posts:
    204
    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.
     
  4. Murzy

    Murzy Member

    Joined:
    Jul 28, 2016
    Posts:
    24
    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.
     
  5. Anixias

    Anixias Member

    Joined:
    Jun 27, 2016
    Posts:
    204
    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: Jan 10, 2017
  6. petteri

    petteri Member

    Joined:
    Oct 11, 2016
    Posts:
    1
    Hi Anixias, I strugle with the same issue. Did you manage to send sutface over the net ? Could you tell how you did that?
     
  7. Bingdom

    Bingdom Googledom

    Joined:
    Jul 1, 2016
    Posts:
    1,678
    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.
     
  8. Anixias

    Anixias Member

    Joined:
    Jun 27, 2016
    Posts:
    204
    What's with the necro, guys? This thread is over 2 years old.
     

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice