Page 1 of 1
I need some explaining done about how the server works...
Posted: 29 Nov 2010, 13:59
by yuvallahav
I've trying to work with different methods of assigning people to play with each other on our system, where it used to be on the client side, now I'm working on moving it to the server side using an actionscript extension.
I've been getting some weird turn outs and I would like, if possible for you, the guys who know the system well enough, to explain to me a little about how the server works when it comes to accessing specific object from multiply client calls, and the situation is this, as an example:
A user is logging in to the system, and since he's the only one connected (at the zone level), I put him into an array that will hold the users objects of connected users.
A second and a third users connected, and since one user is already connected, I would have liked to assign one of the new users the old user to play with.
When I assign an old user (already in array of users) to a new user, I eliminate the user object in the users array, so, when putting this into effect in this example, the second logged in user will log in, find the old user in the list, and so will start to play with him, I will eliminate the old user object from my array, and so, when the third user logs in, and sees the array is empty, and so he goes into it as a waiting users.
Could there be a situation when a message is coming from both users at the same time, and so both of them actually see the array as holding the waiting users, or does the server work in a way that will process the calling from the clients one by one, so under no circumstances could one user see the array as still populated since the user before him got to it a millisecond before or less, but still, his call was not processed while the call of the first of the two new users have been working on the server??
I know I'm not very good at describing these situations, maybe some code examples will be good here (remember, this is not an actual running application, it's just an example to make my point clear):
on server zone init:
Code: Select all
var users;
function init(){
users = new Array();
}
then, after login:
Code: Select all
function handleRequest(cmd, params, user, fromRoom){
if(cmd == "play"){
if(users.length == 0){
users.push(user);
}else{
//get user id.
//send id to the newly logged player, and id of the new
//player to the old waiting player.
delete users[0];
}
}
}
Now, in this case, ideally, first user logs, checks list, finds no one, go into list, second user logs, find first user, takes him off the list, third user logs, finds no one, goes into list, and so on...
And the question is, supposing 2 users have called the "play" request at the same time, or at least, the server receives them at the same time, does the server process one request at a time, so no matter what, one user would be the "first" and "only" user working on the "users" array, or does it work on more then one request at a time, and thus actually we have the possibility of 2 users "thinking" that there is a user waiting and both are being sent the play message, and thus, when a forth player logs in, instead of finding player 3 in the waiting list, the list is empty and he get into the list himself...
I hope this is clear enough for an answer.
Yuval Lahav.
Posted: 29 Nov 2010, 15:53
by ext0sus
I'm think all the requests are handled in the same thread on the server side and that requests are queued. So if user 2 and 3 send a request at the "same time" one will be processed before the other - so if the first request causes a change in the data, the second will see it after it has been changed.
It's probably best to get an answer from someone who knows for sure though.
Posted: 29 Nov 2010, 17:23
by yuvallahav
Yeah, that was what I was thinking too, but since my application acts differently (meaning users 2 and 3 both see user one in the list), I wanted to get the pros here to confirm the working of the server one way or the other, since if it does work like we think, then probably the problem I'm seeing is somewhere else on the code...
Yuval.
Posted: 29 Nov 2010, 21:42
by BigFIsh
I'm think all the requests are handled in the same thread on the server side and that requests are queued. So if user 2 and 3 send a request at the "same time" one will be processed before the other - so if the first request causes a change in the data, the second will see it after it has been changed.
It's probably best to get an answer from someone who knows for sure though.
What you said is true for one thread. If there was multiple threads for that particular request, then yes - it is possible that the same method/call will be executed simultaneously from different users.
Actionscript array isn't thread safe.
However, Java have thread safety collections (java.util.concurrent) that you can use. See
http://cupi2.uniandes.edu.co/javadoc/j2 ... mmary.html
In your situation, use ArrayBlockingQueue (I think)
To use the java classes in Actionscript, see
http://smartfoxserver.com/docs/index.ht ... script.htm
Posted: 30 Nov 2010, 08:24
by yuvallahav
Damn.... this is getting complicated... and I thought I had it all figured out... :(
Yuval.
Posted: 30 Nov 2010, 08:39
by yuvallahav
I've been reading these articles but since I know nothing of java I would probably need to think of some other solution for the problem, and also, by what I read about the "ArrayBlockingQueue", I don't know how much this will help me since the arrays it will work on needs to be a fixed size array, while the arrays I'm working on are dynamic, I always add or subtract users from them and they can not be limited in size...
I really don't see the end of this...
Is there no way I could make something like this work on an AS extension without resorting to java?
I mean, I wouldn't mind slowing the joining of the room process for users by telling the server that all requests to these parts of the code be handled in a queue....
Yuval Lahav.
Posted: 30 Nov 2010, 08:47
by yuvallahav
This reminds me something I done a few years ago, while still playing with the possibility of making our online line server in PHP (until we bought smartfox, of course), and that was having a Boolean variable at the start of the function, where it is set to true at the start of the processing, and if requests come while the function have no finished executing, I have my own queue that I populate, and when the function is at the end I check the queue and if there are messages or actions waiting I execute them in turn... but my fear is that that same variable will have the same problem I'm having with the dealing of these arrays, that calls coming from 2 or more different users will see the status if the variable as false and execute the function at the same time... or does global zone variables and access to them work the same as with these arrays? I believe they do...
Yuval Lahav.
Posted: 30 Nov 2010, 09:11
by yuvallahav
Maybe it's best if I describe here the part of code I'm actually working on in real life, and maybe someone reading this will have a good idea I could use (that someone will get full credit on my website when I'm declared the rightful ruler of the universe, some years from now!!):
We have 4 arrays, holding a list of users in each, by a predefined waiting list variable, and these arrays are set up to receive users , of course, from 4 different groups of players:
pro_vs_pro = holds high level users who wants to play with high level users.
pro_vs_basic = holds high level users who wants to play against low level.
basic_vs_basic = hold low level user who wants to play against low level.
basic_vs_pro = holds low level users who wants to play against high level.
for our example, lets just use low level users who wants to play against other low level users, since the action done to each group is the same, only the information is drawn from different arrays, so whats will work for one, will work for all.
Lets set our waiting list variable to 2 (meaning 2 in waiting list for each of the groups), and start the scenario:
Player 1 enters the stage, he's a low level player, who wants to play a low level opponent.
The system will check the basic_vs_basic array, see that it is shorter then 2, and so, will push the user object into the array.
Player 2 comes in, again, low level user who want to play a low level opponent.
the system checks the basic_vs_basic array again, see that it's, again, shorter then 2, and so pushes the new user into it.
Player 3 logs, and the same as before, low level user against a low level opponent.
The system check the basic_vs_basic array, sees its length to be 2, and so, it will choose randomly a cell in the array, it will snatch the user object from it, and then delete that cell of the array.
At this point, the system will send messages to player 3 and the chosen player, with their respected opponent details (using the user variables in the user object).
The basic_vs_basic array is now 1 long.
Player 4 enters and actually goes through the same process as player 2, checks array, find length smaller then 2, push user into the array.
This was the idle scenario.
Everything worked just fine.
The problem start when 2 users make the request to play at the same time, and the system, processing their requests, in both cases, sees the array as 2 in length, and get the opponent object from the array, and tried to join up 3 people at the same time where it can only have 2 related to each other.
I have tried checking the status of the array and the information in the user object (I set a op_id variable in the user object of both chosen player and populate it with the user id of each opponent) up until the moment before I actually send the message to the users that they can start playing (or in our case, join a newly created game room), but it would still seem that every once in a while, a call comes from 2 different users which get processed at the same time, which causes the system to "join up" 3 players instead of only 2.
If I know (and I don't) that, for example, the joining of room on the server is somehow queued and not processed at the same time for all request, I could check the number of users in the new room, and if it already 2, I know 2 users have been joined up already, and I could process the new redundant user back into the waiting list... could this work??).
Please ignore this part, since it will not work, I create a new room for the 2 players which is named after the users names, so actually I have a situation where I'm trying to join a single player (the chosen opponent) to 2 different and unique rooms with 2 different users.
I hope this helps to understand the situation, and I hope one of you guys can see the problem, and have a handy solution to this kind of problem.
Yuval Lahav.
Posted: 30 Nov 2010, 10:02
by yuvallahav
Hey, just for the fun of it, here is the actual code, if you can make sense out of it:
Code: Select all
if(cmd == "joinPlayRoom"){
response.wait_stat = false;
response.play_error = false;
//getting and setting the user variables.
var user_id = user.getUserId();
var gversion = params.gversion;
var play_with = params.play_with;
var user_lvl = params.user_lvl;
var user_ip = params.user_ip;
var user_points = params.user_points;
var user_fgid = params.fgid;
var wait_list = params.wait_list;
//saving the user variables.
var varList = {gversion:gversion,play_with:play_with,lvl:user_lvl,ip:user_ip,points:user_points,fgid:user_fgid,op_id:"-",wait_time:0};
_server.setUserVariables(user, varList, false);
//play_match variable will tell us if we're going to play or going into the waiting list.
var play_match = true;
var skip_wait = false;
//check for user level and who he wants to play with.
if(user_lvl < 4 && play_with == "Basic"){
for(var i in basic_vs_basic){
//checking to see if there are long waiting users in the list, and in that case, we don't go into the waiting list in case the list is shorter then the set number, but we start them playing right away.
//the length attribute is self created to hold the length of the assuciative array.
if(i != "length"){
if(basic_vs_basic[i].getVariable("wait_time").getValue() >= 30){
//found a user with a long waiting time, so lets skip the going into the waiting list.
skip_wait = true;
break;
}
}
}
//lets short list the list of waiting players.
if(basic_vs_basic.length >= wait_list || skip_wait){
var short_list = new Array();
for(var i in basic_vs_basic){
//checking for users without an oponent, which have different ip then the users and the same game version.
if(i != "length" && basic_vs_basic[i].getVariable("op_id").getValue() == "-" && basic_vs_basic[i].getVariable("ip").getValue() != user_ip && gversion == basic_vs_basic[i].getVariable("gversion").getValue()){
//push the found opponent into the short list.
short_list.push(basic_vs_basic[i]);
}
}
}else{
//we don't skip waiting, or there are too few waiting players, so lets go into waiting too.
play_match = false;
}
}
if(play_match){
//our short list is ready and we can choose the opponent.
if(short_list.length > 0){
//chose a random cell from the short list.
var n = Math.floor(Math.random() * short_list.length);
//check again, to be sure, that the opponnet we are choosing does not have an opponent already.
if(short_list[n].getVariable("op_id").getValue() == "-"){
//opponent is free, lets get his details.
var op_obj = short_list[n];
var user_name = String(user.getName());
var op_name = String(op_obj.getName());
var op_id = op_obj.getUserId();
var op_ip = op_obj.getVariable("ip").getValue();
var op_lvl = op_obj.getVariable("lvl").getValue();
var op_fgid = op_obj.getVariable("fgid").getValue();
if(user_lvl < 4 && play_with == "Basic"){
//lets delete the new opponent object from the original waiting list, so no one, we hope, could get him too.
delete basic_vs_basic["_"+op_id];
//shorten the length attribute of the array (remember, this is an assuciative array).
basic_vs_basic["length"]--;
}
//set the user variables op id and opponent variables op id to the right id's and their status var to busy.
_server.setUserVariables(op_obj, {op_id:user_id,stat:"busy"}, false);
_server.setUserVariables(user, {op_id:op_id,stat:"busy"}, false);
//creat the room for the new players with some variables in them.
var roomVars = new Array({name:"Player1", val:user_name, priv:true, persistent:true},{name:"Player2", val:op_name, priv:true, persistent:true});
var roomObj = {name:"G@"+user_name+"@"+op_name,isGame:true,maxU:2,uCount:false,isLimbo:false,maxS:0};
var new_room = _server.createRoom(roomObj, user, true, true, roomVars, null, true);
if(new_room != null){
//the room was created just fine, lets log in the guys:
_server.joinRoom(user,user.getRoomsConnected()[0], true, new_room.getId(), null, false, true);
_server.joinRoom(op_obj,op_obj.getRoomsConnected()[0], true, new_room.getId(), null, false, true);
//send the users the information about the each other.
var response = {_cmd:"op_information",user_id:user_id,user_ip:user_ip,user_lvl:user_lvl,user_fgid:user_fgid,user_name:user_name,user_points:user_points,op_id:op_id,op_lvl:op_lvl,op_ip:op_ip,op_fgid:op_fgid,op_name:op_name,op_points:op_points,op_avatar:op_avatar};
_server.sendResponse(response, -1, null, [user,op_obj]);
//set up some buddy variables.
varList = {stat:"busy",room:"Playroom"};
_server.setBuddyVariables(user, varList);
}else{
//room was not created right, the user and oponent go back into waiting.
response.wait_stat = true;
//reset the op id for both users.
_server.setUserVariables(op_obj, {op_id:"-"}, false);
_server.setUserVariables(user, {op_id:"-"}, false);
if(user_lvl < 4 && play_with == "Basic"){
//push the users into the waiting lists.
basic_vs_basic["_"+user_id] = user;
basic_vs_basic["_"+op_id] = op_obj;
basic_vs_basic.length+=2;
}
}
}else{
//opponent chosen have an opponent already, damn him and go into waiting instead.
play_match = false;
}
}else{
//short list is empty, go into waiting.
play_match = false;
}
}
//check to see if the match was started, if not, go into waiting list.
if(!play_match){
if(user_lvl < 4 && play_with == "Basic"){
//push the user into the waiting list.
basic_vs_basic["_"+user_id] = user;
//lift up the length count of the array.
basic_vs_basic.length++;
response.wait_stat = true;
}
}
//if user was put into waiting list, join him into the waiting room, send him a message about it!
if(response.wait_stat){
var join_play_room = _server.joinRoom(user,user.getRoomsConnected()[0], true, playroom_id, null, false, true);
if(join_play_room){
//set up some buddy variables.
varList = {stat:"busy",room:"Playroom"};
_server.setBuddyVariables(user, varList);
}else{
response.play_error = true;
}
_server.sendResponse(response, -1, null, [user]);
}
}
Now, what I was thinking is, maybe, in the part before I join the users into the game room, to check if they are not already connected to a game room, but will that not have the problem as with the arrays?? I'm not so sure that it will not....
Yuval Lahav.
Posted: 30 Nov 2010, 10:20
by yuvallahav
Related to my last message, this is the code I'm thinking to add:
Code: Select all
//original code, creating the game room:
var new_room = _server.createRoom(roomObj, user, true, true, roomVars, null, true);
//new code, check to be sure the opponent is not already connected to a game room.
var op_room = op_obj.getRoomsConnected()[0].isGame();
if(new_room != null && !op_room){
Again, I'm not sure about this, since I don't know if the user will be able to catch the room the opponent is in in real time before he logs him into his own created game room.
Yuval.
Posted: 30 Nov 2010, 17:52
by BigFIsh
yuvallahav wrote:I mean, I wouldn't mind slowing the joining of the room process for users by telling the server that all requests to these parts of the code be handled in a queue....
Well, in this case - maybe you could do something like this in actionscript:
1. Gets the request from client
2. Push it into an array (a waiting line)
3. Use Scheduler (sort of like Timer/SetInterval) to get the first request in the array (shift) and then execute your method for that user. This way, your method won't be executed concurrently.
Never tried this, could be messy..
Posted: 30 Nov 2010, 18:13
by yuvallahav
Well, since I've done this before, it not all that messy, but the problem is the same, since the variable that will hold the status of the function will suffer from the same synchronization problem as the array, let alone the array that holds the queue, which will suffer from the same problem, but not as much as the variable that tells the new call coming to the function that the function is busy at the moment processing another call and the message should be queued... 2 clients calling it at the same time both will see the trigger variable as free and go into the function... you get my drift?
Unless at some point of the process we have an element that will always work synchronously and not asynchronously, it will always be a problem.
Yuval.