Jump to content

Sync vars between client and server without triggerEvent


Stanley Sathler

Recommended Posts

Hey guys,

Today, I'm needing help with sync vars between server and client-side.

I'll populate a table (a literal table, an array, not a DB table) parsing a .XML file when resource starts, because I don't want to load the XML everytime. Initially, I used the function which parse this file in the server-side. So, my table is also in server-side. And as far as I know, we can not use server variables in client-side. Not directly.

First, I thought about create a custom event (addEvent) and attach it to a function which returns this server-side table, and call this event from client-side. But triggerServerEvent has its own return value (a boolean), so, I could not return my table from server-side.

What is the best way to sync these variables? There is another way without triggerEvent functions or, if I can use them, how could I?

Link to comment

I believe the only other way to sync besides triggering events is through element data. You could make a dummy element, server-side, and on it, store the table of variables, which would be both accessible to the client, and the server - however, those would be editable by client, which might be a security flaw if there's a guy who knows how to edit the data outside of MTA, though the user must be able to send a fake 'sync' packet otherwise the change will only work on his end.

Link to comment

But Miki, how do you deal with this? If I create a function in client-side returning the table and call it using triggerClientEvent, I won't get the function return because triggerClientEvent has its own return (a boolean indicating successful state). Or am I wrong?

Could you post a code explaining how do?

Edit

Also, I tried to create the table in the client-side and pass it into the parser function as argument (in server-side), because I heard that, in Lua, every table is a pointer. But it didn't work.

Link to comment

You may want to take a look at this example which builds a server side multi dimensional table of ACL rights in this case, and pass it to clients individually using triggerClientEvent(). I don't think that Lua supports pointers like in C. Instead you'll have to pass the variables. If you can't pass tables you could always convert them to JSON and pass them like a string and then convert them back maybe.

Link to comment
  • Moderators

You might not be able to sync a variable. But you can sync it through a meta table. (which I haven't used a single time in my live...)

Server

local sourceTable = {} 
  
local storeInSourceTable = setmetatable({}, {__newindex = function (thisTable, key, value) 
  sourceTable[key] = value 
  triggerClientEvent(root,"syncData",resourceRoot,key,value) 
end}) 
storeInSourceTable["myKey"] = "myData" 
  
  
-- debug -- 
outputChatBox(tostring(storeInSourceTable["myKey"])) -- nil < keep this in mind. 
outputChatBox(sourceTable["myKey"]) -- "myData" 
----------- 

Client

local sourceTable = {} 
  
addEvent("syncData",true) 
addEventHandler("syncData",resourceRoot, 
function (key,value) 
    sourceTable[key] = value 
end) 

Link to comment
But Miki, how do you deal with this? If I create a function in client-side returning the table and call it using triggerClientEvent, I won't get the function return because triggerClientEvent has its own return (a boolean indicating successful state). Or am I wrong?

Could you post a code explaining how do?

Edit

Also, I tried to create the table in the client-side and pass it into the parser function as argument (in server-side), because I heard that, in Lua, every table is a pointer. But it didn't work.

Server:

  
local table = {} 
addEvent("onTableRequested", true) 
addEventHandler("onTableRequested", root, 
function () 
triggerClientEvent(source, "onClientTableSent", root, table) 
end) 

Client:

triggerServerEvent("onTableRequested", localPlayer) 
  
addEvent("onClientTableSent", true) 
addEventHandler("onClientTableSent", root, 
function (data) 
table = data 
end) 

And it's synced

Link to comment
But Miki, how do you deal with this? If I create a function in client-side returning the table and call it using triggerClientEvent, I won't get the function return because triggerClientEvent has its own return (a boolean indicating successful state). Or am I wrong?

Could you post a code explaining how do?

Edit

Also, I tried to create the table in the client-side and pass it into the parser function as argument (in server-side), because I heard that, in Lua, every table is a pointer. But it didn't work.

Tables are passed as references. But that doesn't mean you can pass them to other Lua VMs. The thing that has to be understood is that the server doesn't run in the same Lua VM instance as the client, and especially not on the same server.

As for the actual question, I would say the most efficient way to sync the data here is to do so manually, only when required.

Link to comment

Well if you are ready to go through trouble then you can write a simple UDP server and client script to send and receive data in C or C++. I guess you can compile it (DLL) and use it in MTA for your server. I wrote a very simple working script for my test multiplayer game.

Server side code for linux ( Since most servers are running on linux ) in C:

  
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
 
 
#define PORT 28009 // Choose a free port
#define MAX_CLIENTS 250 // sync between clients
 
 
int compareAddress ( struct sockaddr_in *a1, struct sockaddr_in *a2) // useful function
{
    int Match = 0;
    if ( sizeof (a1)  == sizeof ( a2 ) && (a1->sin_family == a2->sin_family)
         && (a1->sin_addr.s_addr == a2->sin_addr.s_addr) && (a1->sin_port == a2->sin_port) )
    {
      Match++;
    }
    return Match;
   
}
 
// for now we will not use this function
void sendQuitMessagetoAll (int listener, struct sockaddr_in cli_addr[], struct sockaddr_in *cli_source )
{  
  char niceArray [] = "One of the player has quit the game.";
 
   int clients = sizeof ( cli_addr );
   int i;
 
    for ( i=0; i<clients; i++){
       
        int len = sizeof(niceArray);
        int total = 0;        // how many bytes we've sent
        int bytesleft = len; // how many we have left to send
        int n;
 
        while(total < len) {
            n =  sendto(listener, niceArray+total, bytesleft, 0, (struct sockaddr*)&cli_addr[i], sizeof (cli_addr[i]) );
   
                if (n == -1) { break; }
           
                total += n;
                bytesleft -= n;
        }
  
        len = total; // return number actually sent here
       
    }                      
   
 
   
}
 
 
 
 
 
int main(void)
{
    fd_set master;    // master file descriptor list
 
 
  
 
   int clients,  client_addrSize[MAX_CLIENTS], listener, retval, nbytes, i, c, j, newLoopPos, bytesSent;
  
      int len, total, bytesleft, n, clientRemoved;
 
 
   
     clients = 0; // 0 connected clients
     
socklen_t slen_temp;
 
 
    time_t last_update[MAX_CLIENTS]; // checking disconnection or time out
 
        double diff_t;
       
    struct sockaddr_in myaddr, cli_addr[MAX_CLIENTS], cli_temp; ;      /* our address  and client address*/
    socklen_t addrlen = sizeof(myaddr);            /* length of addresses */
 
   
memset((char *)&myaddr, 0, sizeof(myaddr));
myaddr.sin_family = AF_INET;
myaddr.sin_addr.s_addr = htonl(INADDR_ANY);
myaddr.sin_port = htons(PORT);
 
 
        /* create a UDP socket */
 
        if ((listener = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
                perror("cannot create socket\n");
                return 0;
        }
 
        /* bind the socket to any valid IP address and a specific port */
 
if (bind(listener, (struct sockaddr *)&myaddr, sizeof(myaddr)) < 0) {
    perror("bind failed");
    return 0;
}
 
 
 
 
 
    // main loop
    for( ; ; ) {
 
   
 
   
        FD_ZERO(&master);
        FD_SET(listener, &master);
           
        retval = select(listener+1, &master, NULL, NULL, NULL) ;
        if (retval == -1) {
            perror("select");
            exit(4);
        }
        else if ( retval )
        {
    printf (" data available\n ");
 
  
        // run through the existing connections looking for data to read
 
            if (FD_ISSET(listener, &master)) { // we got one!!
              
             
            char buf[1024];    // buffer for client data
           
       
                           
                    // handle data from a client
                    if ((n = recvfrom(listener, buf, sizeof(buf), 0, (struct sockaddr*)&cli_temp, &slen_temp)) <= 0) {
                        // got error or connection closed by client
                        if (nbytes == 0)
                            // connection closed
                             printf("selectserver: socket %d hung up\n", listener);
                         else
                         perror("ERROR: recvfrom");
                        
                          
 
                    }
            else {
               
 
 
                    char niceArray[n];
                   
                        for(i = 0; i < n; i++)
                          niceArray[i] = buf[i];
                     
                               
                   
 
                           
                j = 0; // j wil act as bool for checking if client address is already stored in array or no
               
         int this_length =  sizeof ( (struct sockaddr*)&cli_temp);
         
                    if (clients < MAX_CLIENTS) {
                       
                       for(i = 0; i < clients; i++) { // struct sockaddr*)&cli_addr[i]
       
                           if ( compareAddress(&cli_addr[i], &cli_temp) > 0 )
                            {
                                j++;
                            }
                        }
                    }
                    if ( j == 0 ) { // so yeah client doesn't exist so we store the address and last update time
                   
                        cli_addr[clients] = cli_temp;
                        last_update[clients] = time(NULL);
                   
                        clients++;
                       
                        printf("\n Client Added. \n"); 
                    }
                           
                           
                           newLoopPos = 0; // start loop from 0
 
                           
                           
                           
                   
                           
                    do {  
                          clientRemoved = 0;
                        for(i = newLoopPos; i < clients; i++) {
                           
                             
                             
           
                                                               
                                     diff_t = difftime( time(NULL), last_update[i] );
                   
                                   if ( diff_t >= 15 ) { // client has time out or disconnect
                                     
                                     
                                 
                                   
                                    if ( (i+1) == clients) { // we don't need to DO the do-while loop AGAIN if we are removing the last element
                                     
                                     // nothing here <!-- s;) --><img src=\"{SMILIES_PATH}/icon_wink.gif\" alt=\";)\" title=\"Wink\" /><!-- s;) -->
                                    }
                                   
                                    else
                                       
                                    clientRemoved++;
                                       
                                        newLoopPos = i;   
                                         clients--;
                           
                               struct sockaddr_in cli_source;
                                    cli_source = cli_addr[i]; // client that got disconnected
                                   
                                       for ( c = i - 1 ; c < MAX_CLIENTS - 1 ; c++ ) {
                                          // then its better to remove him from array
                                        cli_addr[c] = cli_addr[c+1];
                                        last_update[c] = last_update[c+1];
                                       }
                                       
                                        // we surely don't want to display disconnection messages from past 10 hours or so <!-- s;) --><img src=\"{SMILIES_PATH}/icon_wink.gif\" alt=\";)\" title=\"Wink\" /><!-- s;) -->  
                                        if ( diff_t < 60 ) {
                                   
                                   
                                         /* //lets disable quit message for a while until we add support in client side for reading  
                                            sendQuitMessagetoAll ( listener, cli_addr, &cli_source );
                                         */
                                        }
                                       
                                       break; // it's important to break for loop now since we don't need to loop again wee
                                    }
                                   
                           
                /* DEBUG PURPOSE           
                       printf("(%d) LOOP and (%d) clients variable.\n", i, clients);
                */
Link to comment

Guys, I'm sorry by the time. Have been working a lot in the last days.

Saml1er, I did a similar thing when creating a Naval Battle for the university. Btw, loved your suggestion just because it's normally a challenge work with sockets in C (at least for me, I love it). But, in this case, create a UDP Server maybe isn't the best and simplest way.

ixjf, I didn't think about LuaVMs. It's because I don't know so much about how C/C++ works together interpreting Lua. You gave me good infos about this subject. Just one question: what do you mean with "only required"? Which type of situations?

Anumaz, I tried it before but didn't work. Maybe I did something wrong.

I'll try the ways suggested by Moose and MIKI and I'll return with the results. If these doesn't work, I'll try IIYAMA suggestion.

Link to comment

I wouldn't follow Saml1er's suggestion as it's totally inappropriate.

Just one question: what do you mean with "only required"? Which type of situations?

I mean that you should do it manually, for example, triggering a sync event when an action happens, and not when an individual setting is changed. You should also only sync info that is actually required in the client (and avoid element data to sync info that is specific to one element and not needed to be accessible by all players - as that information is sent to all players).

Rereading your original post, it looks more like you can't get your head around programming with events. In your specific case, instead of having a function which requests the data from the server, and returns that, refactor it to simply trigger an event on the server to fetch the data, and listen to another event for the result, and only then continue. You could even use coroutines to put it all nicely in one single function, but that's just a random idea.

Link to comment
  • 2 weeks later...

ixjf, got it now! Yes, I don't understand so much things about event-based style, which makes me see less clearly some situations. But your last suggestion looks the same gave by Miki, right?

Anyway, at the end of all, I followed steps indicated by anumaz. I didn't know about shared type, and seems it is enough for me in this specific situation. Can you give me some more advice? I don't know, maybe it isn't the best way to take.

By the way, thanks to everybody who answered me. I got a lof of knowledge with these posts.

Link to comment
ixjf, got it now! Yes, I don't understand so much things about event-based style, which makes me see less clearly some situations. But your last suggestion looks the same gave by Miki, right?

Anyway, at the end of all, I followed steps indicated by anumaz. I didn't know about shared type, and seems it is enough for me in this specific situation. Can you give me some more advice? I don't know, maybe it isn't the best way to take.

By the way, thanks to everybody who answered me. I got a lof of knowledge with these posts.

I wasn't thinking of it the same way as MIKI, but it is more or less similar, yes.

Could you perhaps explain what you're trying to do, why you need all the data from the XML file on both sides in the first place? Placing it in an XML file might not be as good of an option as, say, a Lua table, if it's static content, or a JSON file, which can be easily mapped to Lua. Depends on what you're trying to do.

Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...