UNPKG

elation-engine

Version:
452 lines (392 loc) 14.3 kB
elation.extend("engine.systems.server", function(args) { elation.implement(this, elation.engine.systems.system); //var wrtc = require('wrtc'); this.clients = {}; this.adminClients = []; this.transport = 'websocket'; var UPDATE_RATE = 40; // ms this.lastUpdate = null; //ms this.system_attach = function(ev) { console.log('INIT: networking server'); this.world = this.engine.systems.world; // this.adminServer = new elation.engine.systems.server.adminserver; }; this.start = function(args) { this.server = new elation.engine.systems.server.websocket; this.server.start(args.port); var events = [ [this.server, 'client_disconnected', this.onClientDisconnect], [this.server, 'client_connected', this.onClientConnect], [this.world, 'world_thing_add', this.onThingAdd], // [this.adminServer, 'admin_client_connected', this.onAdminClientConnect], [this.world, 'world_thing_remove', this.onThingRemove], // [this.world, 'thing_change', this.onThingChange] ]; for (var i = 0; i < events.length; i++) { this.addEvent(events[i]); } }; this.serialize_clients = function() { var obj = {}; for (var client in this.clients) { if (this.clients.hasOwnProperty(client)) { obj[client] = { id: this.clients[client].id }; } } return obj; }; this.onAdminClientConnect = function(ev) { ev.data.channel.send(JSON.stringify(this.serialize_world())); this.adminClients.push(ev.data.channel); }; this.addEvent = function(args) { elation.events.add(args[0], args[1], elation.bind(this, args[2])); }; this.serialize_world = function() { var worldmsg = { type: 'world_data', data: this.world.serialize(true) }; return worldmsg; }; this.engine_frame = function() { this.sendChanges(); }; this.sendToAll = function(data) { for (var client in this.clients) { if (this.clients.hasOwnProperty(client)) { this.clients[client].send(data); } } }; this.onClientConnect = function(ev) { console.log('onclientconnect'); var client = new elation.engine.systems.server.client({ transport: 'websocket', id: ev.data.id, socket: ev.data.channel }); this.clients[ev.data.id] = client; elation.events.add(client, 'received_id', elation.bind(this, this.clientReceivedId)); elation.events.add(client, 'new_player', elation.bind(this, this.handleNewPlayer)); elation.events.add(client, 'thing_changed', elation.bind(this, this.onRemoteThingChange)); elation.events.add(client, 'add_thing', elation.bind(this, this.handleNewThing)); elation.events.add(client, 'socket_message_sent', elation.bind(this, this.onSocketSend)); console.log('client connected', client.id); client.send({ type: 'id_token', data: client.id }); console.log(Object.keys(this.clients)); }; this.handleNewPlayer = function(ev) { elation.events.fire({element: this, type: 'add_player', data: {id: ev.target.id, thing: ev.data.data.thing, camera: ev.data.data.camera}}); }; this.handleNewThing = function(ev) { console.log('thing properties', ev.data.data.thing.properties.tags) elation.events.fire({element: this, type: 'add_thing', data: {thing: ev.data.data.thing}}); }; this.sendWorldData = function(evt) { // var client = this.clients[evt.data.data]; // client.send(this.serialize_world()); }; this.clientReceivedId = function(ev) { // elation.events.fire(ev) console.log('got received_id from client') elation.events.fire({element: this, type: 'client_received_id', data: ev.data.data}); } this.sendThingState = function(thing, state) { // states: 'thing_changed', 'thing_added', 'thing_removed' var client_id = thing.properties.player_id; var msg = { type: state, data: thing.serialize() }; if (this.clients.hasOwnProperty(client_id)) { for (var client in this.clients) { if (this.clients.hasOwnProperty(client_id) && client != client_id) { this.clients[client].send(msg); } } } else { this.sendToAll(msg); } } this.onThingAdd = function(ev) { // bind thing remove here? ev.data.thing.properties.tags = ''; // FIXME - local_sync should be moved from a tag to a property of its own, so we don't need to clear tags console.log('thing add', ev.data.thing.name, 'tags:', ev.data.thing.properties.tags); elation.events.add(ev.data.thing, 'thing_change', elation.bind(this, this.onThingChange)); this.sendThingState(ev.data.thing, 'thing_added'); }; this.onThingRemove = function(ev) { // TODO console.log('thing remove', ev.data.thing.name); this.sendThingState(ev.data.thing, 'thing_removed'); }; this.onThingChange = function(ev) { var thing = ev.target || ev.element; if (!thing.hasTag('thing_changed')) { thing.addTag('thing_changed'); } }; this.sendChanges = function() { if (Date.now() - this.lastUpdate > UPDATE_RATE) { var changed = this.world.getThingsByTag('thing_changed'); for (var i = 0; i < changed.length; i++) { var thing = changed[i]; thing.removeTag('thing_changed'); this.sendThingState(thing, 'thing_changed'); this.lastUpdate = Date.now(); } } }; this.onRemoteThingChange = function(ev) { elation.events.fire({element: this, type: 'remote_thing_change', data: ev.data}); }; this.removeClient = function(id) { delete this.clients[id]; }; this.onClientDisconnect = function(ev) { var client = this.clients[ev.data.id]; elation.events.remove(client, 'received_id', elation.bind(this, this.sendWorldData)); elation.events.remove(client, 'new_player', elation.bind(this, this.handleNewPlayer)); this.removeClient(ev.data.id); elation.events.fire({element: this, type: 'player_disconnect', data: ev.data}); console.log('Client disconnected, num clients:', Object.keys(this.clients).length); }; this.onSocketSend = function(ev) { var msg = { type: ev.type, data: ev.data }; /* this.adminServer.wss.clients.forEach(function(client){ client.send(JSON.stringify(msg)); }); */ }; }); elation.extend("engine.systems.server.client", function(args) { /** * This object represents a client connection * */ this.transport = args.transport; this.id = args.id; this.socket = args.socket; this.lastMessage = null; this.transport = args.transport; //FIXME - make this a proper polymorphic object if (this.transport == 'webrtc') { this.send = function(data) { if (this.socket.readyState == 'open') { // console.log('sent a msg'); data.timestamp = Date.now(); this.socket.send(JSON.stringify(data)); } }; this.socket.onmessage = function(evt) { console.log('msg from client on systems.server.client'); var msgdata = JSON.parse(evt.data); var timestamp = msgdata.timestamp; if (!this.lastMessage) this.lastMessage = timestamp; if (timestamp >= this.lastMessage) { // only fire an event if the message is newer than the last received msg var evdata = { element: this, type: msgdata.type, data: { id: this.id, data: msgdata.data } }; elation.events.fire(evdata); this.lastMessage = timestamp; } else { console.log('discarded a message'); } }.bind(this); } if (this.transport == 'websocket') { this.send = function(data) { // console.log('foo'); try { data.timestamp = Date.now(); this.socket.send(JSON.stringify(data)); elation.events.fire({element: this, type: 'socket_message_sent', data:{type: data.type, data: data.data, client_id: this.id, timestamp: data.timestamp}}); } catch(e) { console.log(e) } }; this.socket.on('message', function(msg, flags) { var msgdata = JSON.parse(msg); var timestamp = msgdata.timestamp; if (!this.lastMessage) this.lastMessage = timestamp; if (timestamp >= this.lastMessage) { // only fire an event if the message is newer than the last received msg var evdata = { element: this, type: msgdata.type, data: { id: this.id, data: msgdata.data } }; elation.events.fire(evdata); this.lastMessage = timestamp; }; }.bind(this)); } }); // FIXME - servers should take args for port/etc elation.extend("engine.systems.server.websocket", function() { var wsServer = require('ws').Server; this.start = function(port) { if (wss) return; var wss = new wsServer({ port: port }); console.log('websocket server running on', port); wss.on('connection', function(ws) { console.log('game server websocket conn'); var id = Date.now(); elation.events.fire({element: this, type: 'client_connected', data: {id: id, channel: ws}}); ws.on('close', function() { elation.events.fire({element: this, type: 'client_disconnected', data: {id: id}}); }.bind(this)); }.bind(this)); } }) elation.extend("engine.systems.server.adminserver", function() { var wsServer = require('ws').Server; //FIXME - port hardcoded this.wss = new wsServer({ port: 9002 }); console.log('admin server running on 9002'); this.wss.on('connection', function(ws) { console.log('admin server websocket conn'); var id = Date.now(); elation.events.fire({element: this, type: 'admin_client_connected', data: {id: id, channel: ws}}); ws.on('close', function() { elation.events.fire({element: this, type: 'admin_client_disconnected', data: id}); }.bind(this)); }); }) elation.extend("engine.systems.server.webrtc", function() { var http = require('http'); //var webrtc = require('wrtc'); var ws = require('ws'); var net = require('net'); var MAX_REQUEST_LENGTH = 1024; var pc = null, offer = null, answer = null, remoteReceived = false; var dataChannelsettings = { // 'reliable': { // ordered: false, // maxRetransmits: 0 // } 'unreliable': {} }; this.pendingDataChannels = [], this.dataChannels = [], this.pendingCandidates = []; var socketPort = 9001; var self = this; var wss = new ws.Server({'port': 9001}); wss.on('connection', function(ws) { function doComplete(chan) { console.info('complete'); } function doHandleError(error) { throw error; } function doCreateAnswer() { remoteReceived = true; self.pendingCandidates.forEach(function(candidate) { if (candidate.sdp) { pc.addIceCandidate(new webrtc.RTCIceCandidate(candidate.sdp)); } }); pc.createAnswer(doSetLocalDesc, doHandleError); } function doSetLocalDesc(desc) { answer = desc; pc.setLocalDescription(desc, doSendAnswer, doHandleError); } function doSendAnswer() { ws.send(JSON.stringify(answer)); console.log('awaiting data channels'); } function doHandledataChannels() { var labels = Object.keys(dataChannelsettings); pc.ondatachannel = function(evt) { var channel = evt.channel; var id = Date.now(); console.log('ondatachannel', channel.label, channel.readyState); self.pendingDataChannels.push(channel); channel.binaryType = 'arraybuffer'; channel.onopen = function() { self.dataChannels.push(channel); self.pendingDataChannels.splice(self.pendingDataChannels.indexOf(channel), 1); elation.events.fire({element: this, type: 'client_connected', data: {id: id, channel: channel}}); doComplete(self.dataChannels[self.dataChannels.indexOf(channel)]); // } }.bind(this); channel.onmessage = function(evt) { var msgdata = JSON.parse(evt.data); console.log('onmessage:', evt.data); var evdata = { element: this, type: msgdata.type, data: { id: id, data: msgdata.data } } elation.events.fire(evdata); }.bind(this); channel.onclose = function() { self.dataChannels.splice(self.dataChannels.indexOf(channel), 1); elation.events.fire({element: this, type: 'client_disconnected', data: {id: id, channel: channel}}) console.info('onclose'); }.bind(this); channel.onerror = doHandleError; }; doSetRemoteDesc(); }; function doSetRemoteDesc() { // console.info(offer); pc.setRemoteDescription( offer, doCreateAnswer, doHandleError ); }; ws.on('message', function(data) { data = JSON.parse(data); if('offer' == data.type) { offer = new webrtc.RTCSessionDescription(data); answer = null; remoteReceived = false; pc = new webrtc.RTCPeerConnection( { iceServers: [{ url:'stun:stun.l.google.com:19302' }] }, { 'optional': [{DtlsSrtpKeyAgreement: false}] } ); pc.onsignalingstatechange = function(state) { console.info('signaling state change:', state); }; pc.oniceconnectionstatechange = function(state) { console.info('ice connection state change:', state); }; pc.onicegatheringstatechange = function(state) { console.info('ice gathering state change:', state); }; pc.onicecandidate = function(candidate) { ws.send(JSON.stringify( {'type': 'ice', 'sdp': {'candidate': candidate.candidate, 'sdpMid': candidate.sdpMid, 'sdpMLineIndex': candidate.sdpMLineIndex} })); }; doHandledataChannels(); } else if('ice' == data.type) { if(remoteReceived) { if(data.sdp.candidate) { pc.addIceCandidate(new webrtc.RTCIceCandidate(data.sdp.candidate)); } } else { self.pendingCandidates.push(data); } } }.bind(this)); }.bind(this)); });