455 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
		
		
			
		
	
	
			455 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
| 
								 | 
							
								package kha.netsync;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import haxe.io.Bytes;
							 | 
						||
| 
								 | 
							
								#if sys_server
							 | 
						||
| 
								 | 
							
								import js.node.Http;
							 | 
						||
| 
								 | 
							
								import js.Node;
							 | 
						||
| 
								 | 
							
								#end
							 | 
						||
| 
								 | 
							
								#if js
							 | 
						||
| 
								 | 
							
								import js.Browser;
							 | 
						||
| 
								 | 
							
								import js.html.BinaryType;
							 | 
						||
| 
								 | 
							
								import js.html.WebSocket;
							 | 
						||
| 
								 | 
							
								#end
							 | 
						||
| 
								 | 
							
								import kha.System;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class State {
							 | 
						||
| 
								 | 
							
									public var time: Float;
							 | 
						||
| 
								 | 
							
									public var data: Bytes;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public function new(time: Float, data: Bytes) {
							 | 
						||
| 
								 | 
							
										this.time = time;
							 | 
						||
| 
								 | 
							
										this.data = data;
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class Session {
							 | 
						||
| 
								 | 
							
									public static inline var START = 0;
							 | 
						||
| 
								 | 
							
									public static inline var ENTITY_UPDATES = 1;
							 | 
						||
| 
								 | 
							
									public static inline var CONTROLLER_UPDATES = 2;
							 | 
						||
| 
								 | 
							
									public static inline var REMOTE_CALL = 3;
							 | 
						||
| 
								 | 
							
									public static inline var PING = 4;
							 | 
						||
| 
								 | 
							
									public static inline var SESSION_ERROR = 5;
							 | 
						||
| 
								 | 
							
									public static inline var PLAYER_UPDATES = 6;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public static inline var RPC_SERVER = 0;
							 | 
						||
| 
								 | 
							
									public static inline var RPC_ALL = 1;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									static var instance: Session = null;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									var entities: Map<Int, Entity> = new Map();
							 | 
						||
| 
								 | 
							
									var controllers: Map<Int, Controller> = new Map();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public var maxPlayers: Int;
							 | 
						||
| 
								 | 
							
									public var currentPlayers: Int = 0;
							 | 
						||
| 
								 | 
							
									public var ping: Float = 1;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									var address: String;
							 | 
						||
| 
								 | 
							
									var port: Int;
							 | 
						||
| 
								 | 
							
									var startCallback: Void->Void;
							 | 
						||
| 
								 | 
							
									var refusedCallback: Void->Void;
							 | 
						||
| 
								 | 
							
									var resetCallback: Void->Void;
							 | 
						||
| 
								 | 
							
									#if sys_server
							 | 
						||
| 
								 | 
							
									var server: Server;
							 | 
						||
| 
								 | 
							
									var clients: Array<Client> = new Array();
							 | 
						||
| 
								 | 
							
									var current: Client;
							 | 
						||
| 
								 | 
							
									var isJoinable: Bool = false;
							 | 
						||
| 
								 | 
							
									var lastStates: Array<State> = new Array();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									static inline var stateCount = 60 * 10; // 10 seconds with 60 fps
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									#else
							 | 
						||
| 
								 | 
							
									var localClient: Client;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public var network: Network;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									var updateTaskId: Int;
							 | 
						||
| 
								 | 
							
									var pingTaskId: Int;
							 | 
						||
| 
								 | 
							
									#end
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public var me(get, null): Client;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									function get_me(): Client {
							 | 
						||
| 
								 | 
							
										#if sys_server
							 | 
						||
| 
								 | 
							
										return current;
							 | 
						||
| 
								 | 
							
										#else
							 | 
						||
| 
								 | 
							
										return localClient;
							 | 
						||
| 
								 | 
							
										#end
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public function new(maxPlayers: Int, address: String, port: Int) {
							 | 
						||
| 
								 | 
							
										instance = this;
							 | 
						||
| 
								 | 
							
										this.maxPlayers = maxPlayers;
							 | 
						||
| 
								 | 
							
										this.address = address;
							 | 
						||
| 
								 | 
							
										this.port = port;
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public static function the(): Session {
							 | 
						||
| 
								 | 
							
										return instance;
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public function addEntity(entity: Entity): Void {
							 | 
						||
| 
								 | 
							
										entities.set(entity._id(), entity);
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public function addController(controller: Controller): Void {
							 | 
						||
| 
								 | 
							
										trace("Adding controller id " + controller._id());
							 | 
						||
| 
								 | 
							
										controller._inputBufferIndex = 0;
							 | 
						||
| 
								 | 
							
										controllers.set(controller._id(), controller);
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									#if sys_server
							 | 
						||
| 
								 | 
							
									private function send(): Bytes {
							 | 
						||
| 
								 | 
							
										var size = 0;
							 | 
						||
| 
								 | 
							
										for (entity in entities) {
							 | 
						||
| 
								 | 
							
											size += entity._size();
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										var offset = 0;
							 | 
						||
| 
								 | 
							
										var bytes = Bytes.alloc(size + 9);
							 | 
						||
| 
								 | 
							
										bytes.set(offset, ENTITY_UPDATES);
							 | 
						||
| 
								 | 
							
										offset += 1;
							 | 
						||
| 
								 | 
							
										bytes.setDouble(offset, Scheduler.time());
							 | 
						||
| 
								 | 
							
										offset += 8;
							 | 
						||
| 
								 | 
							
										for (entity in entities) {
							 | 
						||
| 
								 | 
							
											entity._send(offset, bytes);
							 | 
						||
| 
								 | 
							
											offset += entity._size();
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										lastStates.push(new State(Scheduler.time(), bytes));
							 | 
						||
| 
								 | 
							
										if (lastStates.length > stateCount) {
							 | 
						||
| 
								 | 
							
											lastStates.splice(0, 1);
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return bytes;
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									#end
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public function sendControllerUpdate(id: Int, bytes: haxe.io.Bytes) {
							 | 
						||
| 
								 | 
							
										#if !sys_server
							 | 
						||
| 
								 | 
							
										if (controllers.exists(id)) {
							 | 
						||
| 
								 | 
							
											if (controllers[id]._inputBuffer.length < controllers[id]._inputBufferIndex + 4 + bytes.length) {
							 | 
						||
| 
								 | 
							
												var newBuffer = Bytes.alloc(controllers[id]._inputBufferIndex + 4 + bytes.length);
							 | 
						||
| 
								 | 
							
												newBuffer.blit(0, controllers[id]._inputBuffer, 0, controllers[id]._inputBufferIndex);
							 | 
						||
| 
								 | 
							
												controllers[id]._inputBuffer = newBuffer;
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											controllers[id]._inputBuffer.setInt32(controllers[id]._inputBufferIndex, bytes.length);
							 | 
						||
| 
								 | 
							
											controllers[id]._inputBuffer.blit(controllers[id]._inputBufferIndex + 4, bytes, 0, bytes.length);
							 | 
						||
| 
								 | 
							
											controllers[id]._inputBufferIndex += (4 + bytes.length);
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										#end
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									function sendPing() {
							 | 
						||
| 
								 | 
							
										#if !sys_server
							 | 
						||
| 
								 | 
							
										var bytes = haxe.io.Bytes.alloc(5);
							 | 
						||
| 
								 | 
							
										bytes.set(0, kha.netsync.Session.PING);
							 | 
						||
| 
								 | 
							
										bytes.setFloat(1, Scheduler.realTime());
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										sendToServer(bytes);
							 | 
						||
| 
								 | 
							
										#end
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									function sendPlayerUpdate() {
							 | 
						||
| 
								 | 
							
										#if sys_server
							 | 
						||
| 
								 | 
							
										currentPlayers = clients.length;
							 | 
						||
| 
								 | 
							
										var bytes = haxe.io.Bytes.alloc(5);
							 | 
						||
| 
								 | 
							
										bytes.set(0, PLAYER_UPDATES);
							 | 
						||
| 
								 | 
							
										bytes.setInt32(1, currentPlayers);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										sendToEverybody(bytes);
							 | 
						||
| 
								 | 
							
										#end
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public function receive(bytes: Bytes, client: Client = null): Void {
							 | 
						||
| 
								 | 
							
										#if sys_server
							 | 
						||
| 
								 | 
							
										switch (bytes.get(0)) {
							 | 
						||
| 
								 | 
							
											case CONTROLLER_UPDATES:
							 | 
						||
| 
								 | 
							
												var id = bytes.getInt32(1);
							 | 
						||
| 
								 | 
							
												var time = bytes.getDouble(5);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												var width = bytes.getInt32(13);
							 | 
						||
| 
								 | 
							
												var height = bytes.getInt32(17);
							 | 
						||
| 
								 | 
							
												var rotation = bytes.get(21);
							 | 
						||
| 
								 | 
							
												SystemImpl._updateSize(width, height);
							 | 
						||
| 
								 | 
							
												SystemImpl._updateScreenRotation(rotation);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												if (controllers.exists(id)) {
							 | 
						||
| 
								 | 
							
													processEventRetroactively(function() {
							 | 
						||
| 
								 | 
							
														current = client;
							 | 
						||
| 
								 | 
							
														var offset = 22;
							 | 
						||
| 
								 | 
							
														while (offset < bytes.length) {
							 | 
						||
| 
								 | 
							
															var length = bytes.getInt32(offset);
							 | 
						||
| 
								 | 
							
															controllers[id]._receive(bytes.sub(offset + 4, length));
							 | 
						||
| 
								 | 
							
															offset += (4 + length);
							 | 
						||
| 
								 | 
							
														}
							 | 
						||
| 
								 | 
							
														current = null;
							 | 
						||
| 
								 | 
							
													}, time);
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
											case REMOTE_CALL:
							 | 
						||
| 
								 | 
							
												processRPC(bytes);
							 | 
						||
| 
								 | 
							
											case PING:
							 | 
						||
| 
								 | 
							
												// PONG, i.e. just return the packet to the client
							 | 
						||
| 
								 | 
							
												if (client != null)
							 | 
						||
| 
								 | 
							
													client.send(bytes, false);
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										#else
							 | 
						||
| 
								 | 
							
										switch (bytes.get(0)) {
							 | 
						||
| 
								 | 
							
											case START:
							 | 
						||
| 
								 | 
							
												var index = bytes.get(1);
							 | 
						||
| 
								 | 
							
												localClient = new LocalClient(index);
							 | 
						||
| 
								 | 
							
												Scheduler.resetTime();
							 | 
						||
| 
								 | 
							
												startCallback();
							 | 
						||
| 
								 | 
							
											case ENTITY_UPDATES:
							 | 
						||
| 
								 | 
							
												var time = bytes.getDouble(1);
							 | 
						||
| 
								 | 
							
												var offset = 9;
							 | 
						||
| 
								 | 
							
												for (entity in entities) {
							 | 
						||
| 
								 | 
							
													entity._receive(offset, bytes);
							 | 
						||
| 
								 | 
							
													offset += entity._size();
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
												Scheduler.warp(time);
							 | 
						||
| 
								 | 
							
											case REMOTE_CALL:
							 | 
						||
| 
								 | 
							
												switch (bytes.get(1)) {
							 | 
						||
| 
								 | 
							
													case RPC_SERVER:
							 | 
						||
| 
								 | 
							
													// Mainly a safeguard, packets with RPC_SERVER should not be received here
							 | 
						||
| 
								 | 
							
													case RPC_ALL:
							 | 
						||
| 
								 | 
							
														executeRPC(bytes);
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
											case PING:
							 | 
						||
| 
								 | 
							
												var sendTime = bytes.getFloat(1);
							 | 
						||
| 
								 | 
							
												ping = Scheduler.realTime() - sendTime;
							 | 
						||
| 
								 | 
							
											case SESSION_ERROR:
							 | 
						||
| 
								 | 
							
												refusedCallback();
							 | 
						||
| 
								 | 
							
											case PLAYER_UPDATES:
							 | 
						||
| 
								 | 
							
												currentPlayers = bytes.getInt32(1);
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										#end
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									#if sys_server
							 | 
						||
| 
								 | 
							
									private function processEventRetroactively(event: Void->Void, time: Float) {
							 | 
						||
| 
								 | 
							
										if (time <= Scheduler.time()) {
							 | 
						||
| 
								 | 
							
											// var temp = time;
							 | 
						||
| 
								 | 
							
											// Process after earliest saved state if it is too far back
							 | 
						||
| 
								 | 
							
											if (time <= lastStates[0].time) {
							 | 
						||
| 
								 | 
							
												time = lastStates[0].time + 0.00001;
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											var i = lastStates.length - 1;
							 | 
						||
| 
								 | 
							
											while (i >= 0) {
							 | 
						||
| 
								 | 
							
												if (lastStates[i].time < time) {
							 | 
						||
| 
								 | 
							
													var offset = 9;
							 | 
						||
| 
								 | 
							
													for (entity in entities) {
							 | 
						||
| 
								 | 
							
														entity._receive(offset, lastStates[i].data);
							 | 
						||
| 
								 | 
							
														offset += entity._size();
							 | 
						||
| 
								 | 
							
													}
							 | 
						||
| 
								 | 
							
													// Invalidate states in which the new event is missing
							 | 
						||
| 
								 | 
							
													if (i < lastStates.length - 1) {
							 | 
						||
| 
								 | 
							
														lastStates.splice(i + 1, lastStates.length - i - 1);
							 | 
						||
| 
								 | 
							
													}
							 | 
						||
| 
								 | 
							
													Scheduler.warp(lastStates[i].time);
							 | 
						||
| 
								 | 
							
													break;
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
												--i;
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										Scheduler.addTimeTask(event, time - Scheduler.time());
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									#end
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									#if sys_server
							 | 
						||
| 
								 | 
							
									public function processRPC(bytes: Bytes) {
							 | 
						||
| 
								 | 
							
										switch (bytes.get(1)) {
							 | 
						||
| 
								 | 
							
											case RPC_SERVER:
							 | 
						||
| 
								 | 
							
												executeRPC(bytes);
							 | 
						||
| 
								 | 
							
											case RPC_ALL:
							 | 
						||
| 
								 | 
							
												sendToEverybody(bytes);
							 | 
						||
| 
								 | 
							
												executeRPC(bytes);
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									#end
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									function executeRPC(bytes: Bytes) {
							 | 
						||
| 
								 | 
							
										var args = new Array<Dynamic>();
							 | 
						||
| 
								 | 
							
										var syncId = bytes.getInt32(2);
							 | 
						||
| 
								 | 
							
										var index: Int = 6;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										var classnamelength = bytes.getUInt16(index);
							 | 
						||
| 
								 | 
							
										index += 2;
							 | 
						||
| 
								 | 
							
										var classname = "";
							 | 
						||
| 
								 | 
							
										for (i in 0...classnamelength) {
							 | 
						||
| 
								 | 
							
											classname += String.fromCharCode(bytes.get(index));
							 | 
						||
| 
								 | 
							
											++index;
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										var methodnamelength = bytes.getUInt16(index);
							 | 
						||
| 
								 | 
							
										index += 2;
							 | 
						||
| 
								 | 
							
										var methodname = "";
							 | 
						||
| 
								 | 
							
										for (i in 0...methodnamelength) {
							 | 
						||
| 
								 | 
							
											methodname += String.fromCharCode(bytes.get(index));
							 | 
						||
| 
								 | 
							
											++index;
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										while (index < bytes.length) {
							 | 
						||
| 
								 | 
							
											var type = bytes.get(index);
							 | 
						||
| 
								 | 
							
											++index;
							 | 
						||
| 
								 | 
							
											switch (type) {
							 | 
						||
| 
								 | 
							
												case 0x42: // B
							 | 
						||
| 
								 | 
							
													var value: Bool = bytes.get(index) == 1;
							 | 
						||
| 
								 | 
							
													++index;
							 | 
						||
| 
								 | 
							
													trace("Bool: " + value);
							 | 
						||
| 
								 | 
							
													args.push(value);
							 | 
						||
| 
								 | 
							
												case 0x46: // F
							 | 
						||
| 
								 | 
							
													var value: Float = bytes.getDouble(index);
							 | 
						||
| 
								 | 
							
													index += 8;
							 | 
						||
| 
								 | 
							
													trace("Float: " + value);
							 | 
						||
| 
								 | 
							
													args.push(value);
							 | 
						||
| 
								 | 
							
												case 0x49: // I
							 | 
						||
| 
								 | 
							
													var value: Int = bytes.getInt32(index);
							 | 
						||
| 
								 | 
							
													index += 4;
							 | 
						||
| 
								 | 
							
													trace("Int: " + value);
							 | 
						||
| 
								 | 
							
													args.push(value);
							 | 
						||
| 
								 | 
							
												case 0x53: // S
							 | 
						||
| 
								 | 
							
													var length = bytes.getUInt16(index);
							 | 
						||
| 
								 | 
							
													index += 2;
							 | 
						||
| 
								 | 
							
													var str = "";
							 | 
						||
| 
								 | 
							
													for (i in 0...length) {
							 | 
						||
| 
								 | 
							
														str += String.fromCharCode(bytes.get(index));
							 | 
						||
| 
								 | 
							
														++index;
							 | 
						||
| 
								 | 
							
													}
							 | 
						||
| 
								 | 
							
													trace("String: " + str);
							 | 
						||
| 
								 | 
							
													args.push(str);
							 | 
						||
| 
								 | 
							
												default:
							 | 
						||
| 
								 | 
							
													trace("Unknown argument type.");
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										if (syncId == -1) {
							 | 
						||
| 
								 | 
							
											Reflect.callMethod(null, Reflect.field(Type.resolveClass(classname), methodname + "_remotely"), args);
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										else {
							 | 
						||
| 
								 | 
							
											Reflect.callMethod(SyncBuilder.objects[syncId], Reflect.field(SyncBuilder.objects[syncId], methodname + "_remotely"), args);
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public function waitForStart(callback: Void->Void, refuseCallback: Void->Void, errorCallback: Void->Void, closeCallback: Void->Void,
							 | 
						||
| 
								 | 
							
											resCallback: Void->Void): Void {
							 | 
						||
| 
								 | 
							
										startCallback = callback;
							 | 
						||
| 
								 | 
							
										refusedCallback = refuseCallback;
							 | 
						||
| 
								 | 
							
										resetCallback = resCallback;
							 | 
						||
| 
								 | 
							
										#if sys_server
							 | 
						||
| 
								 | 
							
										isJoinable = true;
							 | 
						||
| 
								 | 
							
										#if direct_connection
							 | 
						||
| 
								 | 
							
										trace("Starting server at " + port + ".");
							 | 
						||
| 
								 | 
							
										#end
							 | 
						||
| 
								 | 
							
										server = new Server(port);
							 | 
						||
| 
								 | 
							
										server.onConnection(function(client: Client) {
							 | 
						||
| 
								 | 
							
											if (!isJoinable) {
							 | 
						||
| 
								 | 
							
												var bytes = Bytes.alloc(1);
							 | 
						||
| 
								 | 
							
												bytes.set(0, SESSION_ERROR);
							 | 
						||
| 
								 | 
							
												client.send(bytes, true);
							 | 
						||
| 
								 | 
							
												return;
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											clients.push(client);
							 | 
						||
| 
								 | 
							
											current = client;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											Node.console.log(clients.length + " client" + (clients.length > 1 ? "s " : " ") + "connected.");
							 | 
						||
| 
								 | 
							
											sendPlayerUpdate();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											client.receive(function(bytes: Bytes) {
							 | 
						||
| 
								 | 
							
												receive(bytes, client);
							 | 
						||
| 
								 | 
							
											});
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											client.onClose(function() {
							 | 
						||
| 
								 | 
							
												Node.console.log("Removing client " + client.id + ".");
							 | 
						||
| 
								 | 
							
												clients.remove(client);
							 | 
						||
| 
								 | 
							
												sendPlayerUpdate();
							 | 
						||
| 
								 | 
							
												// isJoinable is intentionally not reset here immediately, as late joining is currently unsupported
							 | 
						||
| 
								 | 
							
												if (clients.length == 0) {
							 | 
						||
| 
								 | 
							
													reset();
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
											});
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											if (clients.length >= maxPlayers) {
							 | 
						||
| 
								 | 
							
												isJoinable = false;
							 | 
						||
| 
								 | 
							
												Node.console.log("Starting game.");
							 | 
						||
| 
								 | 
							
												var index = 0;
							 | 
						||
| 
								 | 
							
												for (c in clients) {
							 | 
						||
| 
								 | 
							
													trace("Starting client " + c.id);
							 | 
						||
| 
								 | 
							
													var bytes = Bytes.alloc(2);
							 | 
						||
| 
								 | 
							
													bytes.set(0, START);
							 | 
						||
| 
								 | 
							
													bytes.set(1, index);
							 | 
						||
| 
								 | 
							
													c.send(bytes, true);
							 | 
						||
| 
								 | 
							
													++index;
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
												Scheduler.resetTime();
							 | 
						||
| 
								 | 
							
												startCallback();
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										});
							 | 
						||
| 
								 | 
							
										#else
							 | 
						||
| 
								 | 
							
										network = new Network(address, port, errorCallback, function() {
							 | 
						||
| 
								 | 
							
											closeCallback();
							 | 
						||
| 
								 | 
							
											reset();
							 | 
						||
| 
								 | 
							
										});
							 | 
						||
| 
								 | 
							
										network.listen(function(bytes: Bytes) {
							 | 
						||
| 
								 | 
							
											receive(bytes);
							 | 
						||
| 
								 | 
							
										});
							 | 
						||
| 
								 | 
							
										updateTaskId = Scheduler.addFrameTask(update, 0);
							 | 
						||
| 
								 | 
							
										ping = 1;
							 | 
						||
| 
								 | 
							
										pingTaskId = Scheduler.addTimeTask(sendPing, 0, 1);
							 | 
						||
| 
								 | 
							
										#end
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									function reset() {
							 | 
						||
| 
								 | 
							
										#if sys_server
							 | 
						||
| 
								 | 
							
										isJoinable = true;
							 | 
						||
| 
								 | 
							
										server.reset();
							 | 
						||
| 
								 | 
							
										#else
							 | 
						||
| 
								 | 
							
										Scheduler.removeFrameTask(updateTaskId);
							 | 
						||
| 
								 | 
							
										Scheduler.removeTimeTask(pingTaskId);
							 | 
						||
| 
								 | 
							
										#end
							 | 
						||
| 
								 | 
							
										currentPlayers = 0;
							 | 
						||
| 
								 | 
							
										ping = 1;
							 | 
						||
| 
								 | 
							
										controllers = new Map();
							 | 
						||
| 
								 | 
							
										entities = new Map();
							 | 
						||
| 
								 | 
							
										resetCallback();
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public function update(): Void {
							 | 
						||
| 
								 | 
							
										#if sys_server
							 | 
						||
| 
								 | 
							
										var bytes = send();
							 | 
						||
| 
								 | 
							
										sendToEverybody(bytes);
							 | 
						||
| 
								 | 
							
										#else
							 | 
						||
| 
								 | 
							
										for (controller in controllers) {
							 | 
						||
| 
								 | 
							
											if (controller._inputBufferIndex > 0) {
							 | 
						||
| 
								 | 
							
												var bytes = haxe.io.Bytes.alloc(22 + controller._inputBufferIndex);
							 | 
						||
| 
								 | 
							
												bytes.set(0, kha.netsync.Session.CONTROLLER_UPDATES);
							 | 
						||
| 
								 | 
							
												bytes.setInt32(1, controller._id());
							 | 
						||
| 
								 | 
							
												bytes.setDouble(5, Scheduler.time());
							 | 
						||
| 
								 | 
							
												bytes.setInt32(13, System.windowWidth(0));
							 | 
						||
| 
								 | 
							
												bytes.setInt32(17, System.windowHeight(0));
							 | 
						||
| 
								 | 
							
												bytes.set(21, 0); // System.screenRotation.getIndex());
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												bytes.blit(22, controller._inputBuffer, 0, controller._inputBufferIndex);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												sendToServer(bytes);
							 | 
						||
| 
								 | 
							
												controller._inputBufferIndex = 0;
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										#end
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									#if sys_server
							 | 
						||
| 
								 | 
							
									public function sendToEverybody(bytes: Bytes): Void {
							 | 
						||
| 
								 | 
							
										for (client in clients) {
							 | 
						||
| 
								 | 
							
											client.send(bytes, false);
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									#end
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									#if !sys_server
							 | 
						||
| 
								 | 
							
									public function sendToServer(bytes: Bytes): Void {
							 | 
						||
| 
								 | 
							
										network.send(bytes, false);
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									#end
							 | 
						||
| 
								 | 
							
								}
							 |