JgslLib

Design doc of Jabber Game Server Lite(JGSL)

Author(s) LiXizhi
Date: 2007/11/6, doc replenished 2008.6.22 by LiXizhi, refactored by 2008.12.27 LiXizhi

JGSL is a special server type in ParaWorld. A PC can be both a client and a server at the same time. However, usually it is just either of them. This is a entry file that contains public functions to the JGSL module.

Client types

- There is only one client called JGSL_client that is bound with the current UI. It is a singleton instance of JGSL.client. - At the same time, there can be many emulated JGSL.client client instance running in the background. They are created with client.IsEmulated set to true. It never update from or to real avatar character. Instead, settings are read from a configuration file at config/EmuUsersDB.xml

the clients communicates with the server in almost identical ways.

Server types

There are two kinds of JGSL servers(dedicated and grid). Currently, only grid is supported. dedicated servers are deprecated for the moment. All servers can be load balanced via multiple gateway servers. The user first connect to a gateway server to find a grid server node that can simulate a given region, and then the client directly communicate with the gridnode for simulation in that region. In case a client user moves from one region or world to another, it may need to constantly communicating with different gridnodes. See below. it is a multiple to multiple to multiple graph.

Client1:jid_100001 ------- Gateway1:jid_1000 ------- GridNode1:jid_1100,1 Client2:jid_100002 ------- Gateway2:jid_1001 ------- GridNode2:jid_1100,2 Client3:jid_100003 ____/ \____ GridNode3:jid_1101,1

Each client user, gateway, or grid node has a jid. each user and gateway can must have one and only one jid. multiple gridnode can have the same jid,, but with different id. therefore to unique identify client, gateway, or grid node, the following must be provided correctly. - client(jid, sessionkey) - gateway(jid, sessionkey) - gridnode(jid, id, sessionkey)

dedicated server

The dedicated server only has one instance running per process and it will utilize the physics and proximity info from the loaded game world on the server side. thus it can serve more accurate simulation of the game world. Another advantage is that it allows the server to be running on a 3D world where the user can interactive with, thus the server can be on the computer of a player, and serve clients where the player plays in the world. The implementation of dedicated server is in jgsl_server file.

gateway server

grid server

Grid server can have many instances running per process and it does not utilize the physics and proximity info from the loaded game world on the server side. Hence, it is usually run as a system service and serve several game worlds at the same time. It is ideal for offcial game server hosting many public worlds. Because we have bandwidth-limit for JGSL nodes, such servers must not be limited by the bandwidth limit and usually physically deployed near the jabber central server cluster. The implementation of dedicated server is in jgslg_server file.

Reset server sessions.

when client and server connect, they ping each other and exchange their session keys. By regenerating session keys, we will reject any previous established JGSL game connections. Usually we need to regenerate session when we load a different world. In other word, if either client or server receives a packet from someone with an unknown session key, it will simply ignore it. In most cases, this could be an offline message.
   -- note: we will logout currently connected server if any. 
   Map3DSystem.JGSL.Reset()

Login/Logout to a JGSL server

Login/Logout is done by just two simple calls.
   -- It will logged out from previous server and then logged in to the new one. 
   Map3DSystem.JGSL_client.LoginServer(JID, callbackFunc)
   -- log out either by sending the logout message to server or not. 
   Map3DSystem.JGSL_client.LogoutServer(bSilent)

Getting Server Status

a client can get server status table by calling
   -- serverInfo is nil if not connected to any server now. 
   -- more information, see Map3DSystem.JGSL_client.server
   local serverInfo = Map3DSystem.JGSL_client.GetServerInfo();
   
   -- get the jabber id of this computer. 
   local sJID = Map3DSystem.JGSL.GetJID()

JGSL Events

one can hook to following messages to get informed about server or client status, such as whether the last login is successful, etc.
   -- called whenever this computer successfully signed in to a remote server. Input contains server JID
   -- msg = {serverJID=JID}
   GAME_JGSL_SIGNEDIN = AutoEnum(),
   -- called whenever this computer signed out of a remote server or just can not connect to the server due to time out. . Input contains server JID.
   -- msg = {serverJID=JID}
   GAME_JGSL_SIGNEDOUT = AutoEnum(),
   -- called whenever connection to a remote server computer timed out. 
   -- it may due to server unavailable or server just shut down. If the server is connected previously, GAME_JGSL_SIGNEDOUT will entails. 
   GAME_JGSL_CONNECTION_TIMEOUT = AutoEnum(),
   -- called whenever some user come in to this world.
   -- msg = {userJID=JID}
   GAME_JGSL_USER_COME = AutoEnum(),
   -- called whenever some user leaves this world. 
   -- msg = {userJID=JID}
   GAME_JGSL_USER_LEAVE = AutoEnum(),
   -- a game client or server status message. 
   -- msg = {text=string}
   GAME_LOG = AutoEnum(),

client logic

The jabber client will send the first message to the server, and wait for the server's reply until the next message is sent. If the client does not receive any reply, it will assume that the connection is lost. When a jabber client receives a server message, it will extract sub messages for each JGSL_server_agent. And for each agent, it will create such a character if it has not been done before. It will also carry out the action sequence immediately.

Both client and server utilizes the creation and env message history. And they will try to broadcast every history from the moment the server or client is started. however, we can change this behavior to broadcast history from the time the world is loaded. see variable below.

   Map3DSystem.JGSL_client.LastCreationHistoryTime
   Map3DSystem.JGSL_client.LastEnvHistoryTime

server logic

When a jabber server receives a message from the client, it will accept it or reject it. If accepted, it will reply so and create a JGSL_client_agent character on the server computer if it has never been created before. This JGSL_client_agent will be responsible to keep track of an active client on the server.

enable Map3DSystem.JGSL.dump_[server|client]_msg for debugging.

grid server

the main file for grid server is JGSL_grid, the supporting files * JGSL_gateway, which manages users and their sessions. * JGSL_history, which contains server creation and env history. * JGSL_servermode, which allows the game engine to enter grid server silient mode. One can run following command line to enter servermode directly upon startup D:\lxzsrc\ParaEngine\ParaWorld\ParaWorld.exe username="LiXizhi1" password="password" servermode="true" d3d="false"

Messages

We use a epoll(event polling) style communication, which means that the client will wait for server response before sending the next message, unless a long time out is seen; and the server response to a client as fast as it could. We identify the messages by A,B,C,D category and listed below.

- The JGSL_gateway and JGSL_grid may be running on multiple (one or one hundred) computers. - JGSL_client is a singleton instance of JGSL.client. it can be running on one computer - EmuUsers can have multiple instances of JGSL.client with IsEmulated set to true

JGSL_client(_emu) <================> JGSL_gateway JGSL_grid

(A.1) Connect to a gateway CS_PING(csk)---->JGSL_gateway (authorization: not performed yet) send gateway session key and forward csk back to client <-SC_PING_REPLY(sk, csk, IsGrid=true)

(B.1) Client login to gateway using a given world path and a default character position. CS_Login(sk, WorldPath, x,y,z)---->JGSL_gateway gateway find a best grid node and return grid node id(gid), grid node session key(gsk), and grid region info(gx,gy,gsize). the best grid node is defined as: the smallest sized grid node that contains the user location. <-SC_Login_Reply(Role, worldpath, gjid, gid, gx,gy,gsize)

(B.2) the client checks if it has connection to grid nodes that contains the 4 corners(7,9,1,3 on numeric pad) around the current player position, if not, the client will ask the gate way for it, like in (B.1).

(C.1) Client send normal update to grid node that it belongs to (with grid node session key, grid id, and forwarded st(server time, first time is nil)), send ct(client time) CS_NormalUpdate(sk, id, st, agent, recover, ct)---->JGSL_grid the grid node add the agent if not before, and send back all other agents (and/or ping) in the grid node. it will send the st(server time), and forward ct(client time). If we find intact agent, we will set the client time to nil, so that the client is expected to sent full update to us in the next update. <-SC_NormalUpdate(st, agents, ping, ct)

(C.2) When client agent is no longer in the region of the grid node, it leave the server by sending IsLeaving=true message. CS_Logout(sk, id)---->JGSL_grid the grid node mark the agent as left (it will permanently remove it during the next slow timer recycle)

(D.1) Client send observer update request to neighbouring grid nodes that it is currently connected to. CS_ObserverUpdate(sk, id, recover, st)---->JGSL_grid the grid node add the observer if not added before, and send back all other agents (and/or ping) in the grid node, like in (C.1) SC_ObserverUpdate(st, agents,ping)

(D.2) When client agent is no long observing the region of the grid node, it leave the grid node server by sending CS_Logout message. CS_Logout(sk, id)---->JGSL_grid

(E.1) Please note that: the difference between JGSL_client and emulated JGSL.client is that - the later never send CS_ObserverUpdate to server.thus it makes the server less busy. However, the server does NOT distinguish between these two different kinds of JGSL clients. - the later never update from or to real avatar character.

(F.1) query a gateway or grid node CS_QUERY(csk, fields, forward)---->JGSL_gateway (authorization: not performed yet) result table containing the result of the query fields <-SC_QUERY_REPLY(sk, csk, forward, result)

structure explanations

- JGSL.gateway: keeps all connected GridUsers. Also a mapping from jid to GridUser structure. - gateway config file: rules about which world this server can serve, and how to allocate grid node resources for a given world. - JGSL.gateway.GridUser: session key of the gateway and info of the grid node that the user is currently in. - GridNode: a grid node server for simulating a given region of the world. It keeps track of all agents and observers in its region. - gridnode config file: rules about which JGSL.gateway.JID to trust. it can be multiple and it always trust the local gateway (i.e. gateway with the same JID as gridnode). - JGSL.client: keeps the current player agent stream, all network agent streams, and info about all gridnodes that it has visited. - JGSL_client: a singleton instance of JGSL.client.

agent stream serialization

keeping date exchange minimum

We use three rules to send update to client - the grid node must boardcast the jid of all active agents in the region to all connected agents or observers on each polling request. This is an exemption from the minimum rule, it will prevent client agents to time out on the client side. - in addition, the grid node server only send back changed data fields of agents since the last send call. To make this work, the grid server inject a frameid, which the client must forward in its next update message. gridserver.frameid is increased by one each time it processes a message, and it is also used as time key for keeping historical record of all agent data fields. Thus, the frameid from client can be used to detect whether data on the server has changed since last send call. A new client has a 0 frameid, thus all data fields are sent back.
NOTE1
The first message for client, always needs to send all info of all agents, which could be a peek in bandwidth, but the throughput is still minimum. I used to think other ways to distribute the first packet in the subsequent frames. but the above design is simple and easy.
NOTE2
when any agent field is changed. all connected agents are immediately informed of it, this could mostly be position and facing. I used to think other ways to distribute the peek to subsequent frames, but the current keeps the client more accurate. And if most characters are static, its throughput is minimum.
- agent data are compressed with opcode and data; and known strings are replaced by their ids.

such logics can be found in JGSL_opcode and JGSL_agent file. JGSL_agent:UpdateFromStream() and JGSL_agent:GenerateUpdateStream() are the function to deal with it.

server rules

the server grid node keeps track of each agent by keeping its most recent state as well as a short history of its past states. The server also keeps tracks of the number of times that it has received update from each agent. see below server.agent { rec_count, -- number of times it has recevied update from the client. lastSendTime, -- the last time (by server timer) that it has sent update back to the client. history { valuetracker }, -- agent position, facing, asset, etc and a short history of them. } when the grid node receives a new normal update request, it uses the above info, to deduce how much data to sent back to the client. As a rule, if agent.rec_count%FieldRefreshRate==1, then the field of all agents are sent back to the client. for example, FieldRefreshRate can be 5 or 50, for different field like position, AssetFile, etc. on all other frames, we should only update other changed agents' fields since the incoming agent's lastSendTime.

client rules

on the client side, it also keeps an agent structure for the current player. client.agent { send_count, -- number of times it has sent update to server. lastSendTime, -- the last time (by client timer) that it has sent update to the server. history{ valuetracker}, -- agent position, facing, asset, etc and a short history of them. } As a rule, if agent.send_count%FieldRefreshRate==1, then the field of all agents are sent to the server. for example, FieldRefreshRate can be 5 or 50, for different field like position, AssetFile, etc.

recovery rules

in some rare situations, something goes wrong, and either server or client is not receiving enough information. Such information may be the minimum character info, such as agent.AssetInfo or agent's position. In such case, both client and server can send normal updates with recovery requests.
  • If client keeps getting server pings that it does not recognize:
    • the client can include a recover field {recover="nid,nid,nid"} in its (C.1) or (D.1) update. Please note that, the server can only recover a limited amount (such as 3) of users at a time.
    • the server will then send full agent info for all agents in the recover field in reply in its msg.agents fields.
  • [Not implemented] If the server receive a normal agent update that it does not have previous history:
    • the server will include a recover field {recover=true} in its (C.1) or (D.1) reply.
    • the client should then send full agent info of itself to the server in its next agent update to the serve.

Debugging

client side debugging scripts.
   -- dump everything on the current client 
   Map3DSystem.JGSL_client:Dump();
   -- get gateway server round trip time
   Map3DSystem.JGSL_client:QueryGateway();

Jabber GSL server and client.

Title Jabber GSL server and client.
Author(s) LiXizhi
Date 2007/11/6, doc replenished 2008.6.22 by LiXizhi
File script/kids/3DMapSystemNetwork/JGSL.lua

Description

Member Functions

JGSL.InitStringMap

 boolean: whether this server is a grid server or not.
JGSL.IsGrid = nil;

 dump all msg to log. should always be nil, except u are debugging
JGSL.dump_server_msg = true;
JGSL.dump_client_msg = true;

 default neuron files for client and server. 
JGSL.DefaultServerFile = "script/kids/3DMapSystemNetwork/JGSL_server.lua";
JGSL.DefaultClientFile = "script/kids/3DMapSystemNetwork/JGSL_client.lua";

 TODO: the avatar to be displayed when the appearance is not synchronized in JGSL. Use something simple, such as a stick or a nude avatar. 
JGSL.DefaultAvatarFile = "character/v3/dummy/dummy.x";
 JGSL.DefaultAvatarFile will be used as key to find the ccsstring. 
JGSL.DefaultAvatarCCSStrings = {
   ["character/v3/Human/Female/HumanFemale.xml"] = "0#0#0#1#0#@0#F#0#0#0#0#0#F#0#0#0#0#0#F#0#0#0#0#0#F#0#0#0#0#0#F#0#0#0#0#0#F#0#0#0#0#0#F#0#0#0#0#@0#0#0#0#0#10#12#0#0#0#0#0#0#0#",
   ["character/v3/Human/Male/HumanMale.xml"] = "0#0#4#2#0#@0#F#0#0#0#0#0#F#0#0#0#0#0#F#0#0#0#0#0#F#0#0#0#0#0#F#0#0#0#0#0#F#0#0#0#0#0#F#0#0#0#0#@0#0#0#0#0#11#13#0#0#0#0#0#0#0#",
   ["character/v3/Human/Female/HumanFemale.x"] = "0#0#0#1#0#@0#F#0#0#0#0#0#F#0#0#0#0#0#F#0#0#0#0#0#F#0#0#0#0#0#F#0#0#0#0#0#F#0#0#0#0#0#F#0#0#0#0#@0#0#0#0#0#10#12#0#0#0#0#0#0#0#",
   ["character/v3/Human/Male/HumanMale.x"] = "0#0#4#2#0#@0#F#0#0#0#0#0#F#0#0#0#0#0#F#0#0#0#0#0#F#0#0#0#0#0#F#0#0#0#0#0#F#0#0#0#0#0#F#0#0#0#0#@0#0#0#0#0#11#13#0#0#0#0#0#0#0#",
}
;

add any string map as you like once and for all. and make sure that the server has the same mapping. It is better to fetch this via local or remote XML file.

syntax

function JGSL.InitStringMap()

JGSL.GetJC


public function
get a connected jabber client. it may return nil if jabber client is invalid.

syntax

function JGSL.GetJC()

JGSL.GetJID

get the JID of this jabber client.

syntax

function JGSL.GetJID()

JGSL.Reset

when client and server connect, they must exchange their session keys. By regenerating session keys, we will reject any previous established JGSL game connections. Usually we need to regenerate session when we load a different world.

  • note __ : we will logout currently connected server if any.

syntax

function JGSL.Reset()

JGSL.ResetIfNot

reset if not.

syntax

function JGSL.ResetIfNot()

JGSL.Log

display text to in-game log panel.

  • param text : string
  • param level : the level of importance of the message. it can be nil.

syntax

function JGSL.Log(text, level)

parameters

text string
level  

JGSL.SearchRequestGroup

return the index of a value from table Req_group, which equals value. Return nil if not found e.g SearchRequestGroup({[1] = "lxz@pe"}, "lxz@pe") returns 1

syntax

function JGSL.SearchRequestGroup(Req_group, value)

parameters

Req  
group  
value  

JGSL.CompressEnvs

compress environment updates, removing redundent ones. It will ensure that the following messages will only have one latest copy in env array. OCEAN_SET_WATER, SKY_SET_Sky

  • param env : array of env messages.

syntax

function JGSL.CompressEnvs(env)

parameters

env array of env messages.
Topic revision: r1 - 2008-02-29 - LiXizhi
 
This site is powered by the TWiki collaboration platform Powered by PerlCopyright © 2008-2024 by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding TWiki? Send feedback