PageRenderTime 34ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/js/network/ClientNetChannel.js

http://github.com/onedayitwillmake/RealtimeMultiplayerNodeJs
JavaScript | 392 lines | 215 code | 61 blank | 116 comment | 29 complexity | b26b7d28ab9941c3a0ec28f81a8a1f38 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, MPL-2.0-no-copyleft-exception, BSD-3-Clause
  1. /**
  2. File:
  3. ClientNetChannel.js
  4. Created By:
  5. Mario Gonzalez
  6. Project:
  7. RealtimeMultiplayerNodeJS
  8. Abstract:
  9. Communicates with the server and stores rolling world-entity-descriptions
  10. -> GameController talks to this object
  11. <--> ClientNetChannel waits to be ready, when it is
  12. <-- ClientNetChannel talks to the ServerNetChannel
  13. <--> ServerNetChannel does some stuff
  14. --> ServerNetChannel talks to ClientNetChannel
  15. --> ClientNetChannel talks to the GameController --^
  16. Basic Usage:
  17. Create an object that conforms to the following protocol
  18. netChannelDidConnect();
  19. netChannelDidReceiveMessage();
  20. netChannelDidDisconnect();
  21. */
  22. (function () {
  23. var BUFFER_MASK = RealtimeMultiplayerGame.Constants.CLIENT_SETTING.MAX_BUFFER;
  24. // Retrieve the namespace
  25. RealtimeMultiplayerGame.namespace("RealtimeMultiplayerGame.network");
  26. RealtimeMultiplayerGame.ClientNetChannel = function (aDelegate) {
  27. this.setDelegate(aDelegate);
  28. this.setupSocketIO();
  29. // this.setupWSClient();
  30. this.setupCmdMap();
  31. return this;
  32. };
  33. RealtimeMultiplayerGame.ClientNetChannel.prototype = {
  34. delegate: null, // Object informed when ClientNetChannel does interesting stuff
  35. socketio: null, // Reference to singluar Socket.IO instance
  36. clientid: null, // A client id is set by the server on first connect
  37. // Settings
  38. cl_updateRate: RealtimeMultiplayerGame.Constants.CLIENT_SETTING.CMD_RATE, // How often we can receive messages per sec
  39. // connection info
  40. latency: 1000, // Current latency time from server
  41. lastSentTime: -1, // Time of last sent message
  42. lastRecievedTime: -1, // Time of last recieved message
  43. // Data
  44. messageBuffer: [], // Store last N messages to be sent
  45. outgoingSequenceNumber: 0,
  46. incomingWorldUpdateBuffer: [], // Store last N received WorldDescriptions
  47. reliableBuffer: null, // We sent a 'reliable' message and are waiting for acknowledgement that it was sent
  48. cmdMap: {}, // Map the CMD constants to functions
  49. setupSocketIO: function () {
  50. debugger;
  51. this.socketio = new io.connect(RealtimeMultiplayerGame.Constants.SERVER_SETTING.GET_URI(), {transports: ['websocket', 'xhr-polling', 'jsonp-polling'], reconnect: false, rememberTransport: false});
  52. var that = this;
  53. this.socketio.on('connect', function () {
  54. that.onSocketConnect()
  55. });
  56. this.socketio.on('message', function (obj) {
  57. that.onSocketDidAcceptConnection(obj)
  58. });
  59. this.socketio.on('disconnect', function () {
  60. that.onSocketDisconnect()
  61. });
  62. },
  63. setupWSClient: function () {
  64. var that = this;
  65. this.connection = new WebSocket("ws://localhost:" + RealtimeMultiplayerGame.Constants.SERVER_SETTING.SOCKET_PORT + "/");
  66. this.socketio = this.connection;
  67. this.connection.onopen = function () {
  68. DemoHelloWorld.DemoClientGame.prototype.log("Connection.onopen");
  69. };
  70. this.connection.onmessage = function (event) {
  71. //DemoHelloWorld.DemoClientGame.prototype.log("Connection.onmessage");
  72. var message = BISON.decode(event.data);
  73. that.onSocketDidAcceptConnection(message);
  74. };
  75. this.connection.onclose = function (event) {
  76. DemoHelloWorld.DemoClientGame.prototype.log("Connection.onclose");
  77. that.onSocketDisconnect();
  78. };
  79. },
  80. /**
  81. * Map RealtimeMultiplayerGame.Constants.CMDS to functions
  82. */
  83. setupCmdMap: function () {
  84. this.cmdMap = {};
  85. this.cmdMap[RealtimeMultiplayerGame.Constants.CMDS.SERVER_FULL_UPDATE] = this.onServerWorldUpdate;
  86. },
  87. ///// SocketIO Callbacks
  88. onSocketConnect: function () {
  89. console.log("(ClientNetChannel):onSocketConnect", arguments, this.socketio);
  90. },
  91. /**
  92. * Called when ServerNetChannel has accepted your connection and given you a client id
  93. * This is only called once, use the info to set some properties
  94. */
  95. onSocketDidAcceptConnection: function (aNetChannelMessage) {
  96. console.log("(ClientNetChannel)::onSocketDidAcceptConnection", aNetChannelMessage);
  97. // Should not have received this msg
  98. if (aNetChannelMessage.cmd != RealtimeMultiplayerGame.Constants.CMDS.SERVER_CONNECT) {
  99. throw "(ClientNetChannel):onSocketDidAcceptConnection recieved but cmd != SERVER_CONNECT ";
  100. }
  101. this.clientid = aNetChannelMessage.id;
  102. this.delegate.log("(ClientNetChannel)::ClientID - ")
  103. this.delegate.netChannelDidConnect(aNetChannelMessage);
  104. // Set onMessage function back to normal - removing event listener didn't work, so for now changing the mapping
  105. // TODO: Do via removeEvent
  106. //this.socketio.removeEvent("message", function( obj ){ that.onSocketDidAcceptConnection( obj ) });
  107. //this.socketio.on('message', function( obj ){ that.onSocketMessage( obj ) });
  108. this.onSocketDidAcceptConnection = this.onSocketMessage;
  109. },
  110. /**
  111. * Called when Socket.io has received a new message
  112. * @param aNetChannelMessage
  113. */
  114. onSocketMessage: function (aNetChannelMessage) {
  115. this.lastReceivedTime = this.delegate.getGameClock();
  116. this.adjustRate(aNetChannelMessage);
  117. if (aNetChannelMessage.id == this.clientid) // We sent this, clear our reliable buffer que
  118. {
  119. if (aNetChannelMessage.cmd == RealtimeMultiplayerGame.Constants.CMDS.SERVER_FULL_UPDATE) {
  120. // debugger; // IF CALLED THIS IS A BUG
  121. }
  122. var messageIndex = aNetChannelMessage.seq & BUFFER_MASK;
  123. var message = this.messageBuffer[messageIndex];
  124. // Free up reliable buffer to allow for new message to be sent
  125. if (this.reliableBuffer === message) {
  126. this.reliableBuffer = null;
  127. }
  128. // Remove from memory
  129. this.messageBuffer[messageIndex] = null;
  130. delete message;
  131. return;
  132. }
  133. // Call the mapped function
  134. if (this.cmdMap[aNetChannelMessage.cmd])
  135. this.cmdMap[aNetChannelMessage.cmd].call(this, aNetChannelMessage);
  136. else
  137. console.log("(NetChannel)::onSocketMessage could not map '" + aNetChannelMessage.cmd + "' to function!");
  138. },
  139. onSocketDisconnect: function () {
  140. this.delegate.netChannelDidDisconnect();
  141. this.connection = null;
  142. this.socketio = null;
  143. console.log("(ClientNetChannel)::onSocketDisconnect", arguments);
  144. },
  145. /**
  146. * Send queued messages
  147. */
  148. tick: function () {
  149. // Can't send new message, still waiting for last imporant message to be returned
  150. if (this.reliableBuffer !== null) return;
  151. var hasReliableMessages = false;
  152. var firstUnreliableMessageFound = null;
  153. var len = this.messageBuffer.length;
  154. for (var i = 0; i < len; i++) {
  155. var message = this.messageBuffer[i];
  156. if (!message) continue; // Slot is empty
  157. // We have more important things to tend to sir.
  158. if (message.isReliable) {
  159. hasReliableMessages = true;
  160. this.sendMessage(message);
  161. return;
  162. }
  163. }
  164. // No reliable messages waiting, enough time has passed to send an update
  165. if (!hasReliableMessages && this.canSendMessage() && this.nextUnreliable != null) {
  166. this.sendMessage(this.nextUnreliable);
  167. this.nextUnreliable = null;
  168. }
  169. },
  170. /**
  171. *
  172. * @param aNetChannelMessage
  173. */
  174. onServerWorldUpdate: function (aNetChannelMessage) {
  175. var len = aNetChannelMessage.data.length;
  176. var i = -1;
  177. // Store all world updates contained in the message.
  178. while (++i < len) // Want to parse through them in correct order, so no fancy --len
  179. {
  180. var singleWorldUpdate = aNetChannelMessage.data[i];
  181. var worldEntityDescription = this.createWorldEntityDescriptionFromString(singleWorldUpdate);
  182. // Add it to the incommingCmdBuffer and drop oldest element
  183. this.incomingWorldUpdateBuffer.push(worldEntityDescription);
  184. if (this.incomingWorldUpdateBuffer.length > BUFFER_MASK)
  185. this.incomingWorldUpdateBuffer.shift();
  186. }
  187. },
  188. /**
  189. * Takes a WorldUpdateMessage that contains the information about all the elements inside of a string
  190. * and creates SortedLookupTable out of it with the entityid's as the keys
  191. * @param {String} aWorldUpdateMessage
  192. */
  193. createWorldEntityDescriptionFromString: function (aWorldUpdateMessage) {
  194. // Create a new WorldEntityDescription and store the clock and gametick in it
  195. var worldDescription = new SortedLookupTable();
  196. worldDescription.gameTick = aWorldUpdateMessage.gameTick;
  197. worldDescription.gameClock = aWorldUpdateMessage.gameClock;
  198. var allEntities = aWorldUpdateMessage.entities.split('|'),
  199. allEntitiesLen = allEntities.length; //
  200. // Loop through each entity
  201. while (--allEntitiesLen) // allEntities[0] is garbage, so by using prefix we avoid it
  202. {
  203. // Loop through the string representing the entities properties
  204. var entityDescAsArray = allEntities[allEntitiesLen].split(',');
  205. var entityDescription = this.delegate.parseEntityDescriptionArray(entityDescAsArray);
  206. // Store the final result using the entityid
  207. worldDescription.setObjectForKey(entityDescription, entityDescription.entityid);
  208. }
  209. return worldDescription;
  210. },
  211. /**
  212. * Sends a message via socket.io
  213. * @param aMessageInstance
  214. */
  215. sendMessage: function (aMessageInstance) {
  216. if (this.socketio == undefined) {
  217. console.log("(ClientNetChannel)::sendMessage - socketio is undefined!");
  218. return;
  219. }
  220. if (!this.socketio.socket.connected) { // Socket.IO is not connectd, probably not ready yet
  221. // console.log("(ClientNetChannel)::sendMessage - socketio is undefined!");
  222. return; //some error here
  223. }
  224. aMessageInstance.messageTime = this.delegate.getGameClock(); // Store to determine latency
  225. this.lastSentTime = this.delegate.getGameClock();
  226. if (aMessageInstance.isReliable) {
  227. this.reliableBuffer = aMessageInstance; // Block new connections
  228. }
  229. this.socketio.json.send(aMessageInstance);
  230. if (RealtimeMultiplayerGame.Constants.CLIENT_NETCHANNEL_DEBUG) console.log('(NetChannel) Sending Message, isReliable', aMessageInstance.isReliable, aMessageInstance);
  231. },
  232. /**
  233. * Prepare a message for sending at next available time
  234. * @param isReliable
  235. * @param anUnencodedMessage
  236. */
  237. addMessageToQueue: function (isReliable, aCommandConstant, payload) {
  238. // Create a NetChannelMessage
  239. var message = new RealtimeMultiplayerGame.model.NetChannelMessage(this.outgoingSequenceNumber, this.clientid, isReliable, aCommandConstant, payload);
  240. // Add to array the queue using bitmask to wrap values
  241. this.messageBuffer[ this.outgoingSequenceNumber & BUFFER_MASK ] = message;
  242. if (!isReliable) {
  243. this.nextUnreliable = message;
  244. }
  245. ++this.outgoingSequenceNumber;
  246. if (RealtimeMultiplayerGame.Constants.DEBUG_SETTING.CLIENT_NETCHANNEL_DEBUG) console.log('(NetChannel) Adding Message to queue', this.messageBuffer[this.outgoingSequenceNumber & BUFFER_MASK], " ReliableBuffer currently contains: ", this.reliableBuffer);
  247. },
  248. /**
  249. * Adjust the message chokerate based on latency
  250. * @param serverMessage
  251. */
  252. adjustRate: function (serverMessage) {
  253. var deltaTime = serverMessage.gameClock - this.delegate.getGameClock();
  254. this.latency = deltaTime;
  255. // TODO: Adjust cl_updateRate based on message thruput
  256. // time -= 100; // Subtract 100ms
  257. // if(this.)
  258. // console.log('Time:', time)
  259. // time -= 0.1; // subtract 100ms
  260. //
  261. // if(time <= 0)
  262. // {
  263. // this.rate = 0.12; /* 60/1000*2 */
  264. // }
  265. // else
  266. // {
  267. // }
  268. },
  269. ///// Memory
  270. /**
  271. * Clear memory
  272. */
  273. dealloc: function () {
  274. this.connection.close();
  275. delete this.connection;
  276. delete this.messageBuffer;
  277. delete this.incomingWorldUpdateBuffer;
  278. },
  279. ///// Accessors
  280. /**
  281. * Set the NetChannelDelegate after validation
  282. * @param aDelegate
  283. */
  284. setDelegate: function (aDelegate) {
  285. var theInterface = RealtimeMultiplayerGame.ClientNetChannelDelegateProtocol;
  286. for (var member in theInterface) {
  287. if ((typeof aDelegate[member] != typeof theInterface[member])) {
  288. console.error("object failed to implement interface member " + member);
  289. return false;
  290. }
  291. }
  292. // Checks passed
  293. this.delegate = aDelegate;
  294. },
  295. /**
  296. * Determines if it's ok for the client to send a unreliable new message yet
  297. */
  298. canSendMessage: function () {
  299. var isReady = (this.delegate.getGameClock() > this.lastSentTime + this.cl_updateRate);
  300. return isReady;
  301. },
  302. getClientid: function () {
  303. return this.clientid
  304. },
  305. getIncomingWorldUpdateBuffer: function () {
  306. return this.incomingWorldUpdateBuffer
  307. },
  308. getLatency: function () {
  309. return this.latency
  310. }
  311. };
  312. /**
  313. * Required methods for the ClientNetChannel delegate
  314. */
  315. RealtimeMultiplayerGame.ClientNetChannelDelegateProtocol = {
  316. netChannelDidConnect: function () {
  317. },
  318. netChannelDidReceiveMessage: function (aMessage) {
  319. },
  320. netChannelDidDisconnect: function () {
  321. },
  322. parseEntityDescriptionArray: function () {
  323. },
  324. log: function () {
  325. },
  326. getGameClock: function () {
  327. }
  328. }
  329. })()