Files
GarageServer.IO/client/garageserver.io.js
T
Jeremiah Billmann b5449342cd Updated comments
2013-07-17 22:13:39 -04:00

386 lines
13 KiB
JavaScript

/*
options = {
onPlayerConnect: function()
onPlayerDisconnect: function (),
onPlayerReconnect: function (),
onPlayerUpdate: function (state),
onEntityUpdate: function (state),
onPlayerRemove: function (id),
onEntityRemove: function (id),
onEvent: function (data),
onWorldState: function (state),
onPing: function (pingDelay),
onUpdatePlayerPhysics: function (state, inputs, deltaTime),
onInterpolation: function(previousState, targetState, amount)
logging: true
}
api methods
initializeGarageServer(path, options)
addInput({})
getStates(function (playerStates: [{state}], entityStates: [{state})])
getId() : 'playerid'
*/
var GarageServerIO = (function (socketio) {
"use strict";
function StateController() {
this.clientTime = 0;
this.renderTime = 0;
this.physicsDelta = 0.0;
this.id = '';
this.pingDelay = 100;
this.interpolationDelay = 100;
this.interpolation = false;
this.pingInterval = 2000;
this.clientSidePrediction = false;
this.smoothingFactor = 0.3;
this.worldState = {};
}
StateController.prototype = {
setTime: function (serverTime) {
this.clientTime = serverTime;
this.renderTime = this.clientTime - this.interpolationDelay;
}
};
function Input(input, seq) {
this.input = input;
this.seq = seq;
}
function InputController() {
this.inputs = [];
this.sequenceNumber = 1;
}
InputController.prototype = {
any: function () {
return this.inputs.length > 0;
},
add: function (input) {
this.sequenceNumber += 1;
this.inputs.push(new Input(input, this.sequenceNumber));
},
remove: function (seq) {
for (var i = 0; i < this.inputs.length; i ++) {
if (this.inputs[i].seq === seq) {
this.inputs.splice(0, i + 1);
break;
}
}
}
};
function Update(state, seq, time) {
this.state = state;
this.seq = seq;
this.time = time;
}
function Entity(id) {
this.updates = [];
this.id = id;
this.state = {};
this.inputController = new InputController();
}
Entity.prototype = {
addUpdate: function (state, seq, time) {
var newUpdate = new Update(state, seq, time);
if (this.updates.length === 0) {
this.state = newUpdate.state;
}
this.updates.push(newUpdate);
if (this.updates.length > 120) {
this.updates.splice(0, 1);
}
},
updateState: function (state, seq, time) {
var updateFound = false;
this.updates.some(function (update) {
if (update.seq === seq) {
update.state = state;
updateFound = true;
return true;
}
});
if (!updateFound) {
this.addUpdate(state, seq, time);
}
},
anyUpdates: function () {
return this.updates.length > 0;
},
latestUpdate: function () {
return this.updates[this.updates.length - 1];
},
surroundingPositions: function (time) {
var positions = {};
for (var i = 0; i < this.updates.length; i ++) {
var previous = this.updates[i],
target = this.updates[i + 1];
if (previous && target && time > previous.time && time < target.time) {
positions.previous = previous;
positions.target = target;
break;
}
}
return positions;
}
};
function Player(id) {
Entity.call(this, id);
}
Player.prototype = Object.create(Entity.prototype);
function EntityController() {
this.entities = [];
}
EntityController.prototype = {
add: function (id) {
var entity = new Entity(id);
this.entities.push(entity);
return entity;
},
remove: function (id) {
for (var i = 0; i < this.entities.length; i ++) {
if (this.entities[i].id === id) {
this.entities.splice(i, 1);
return;
}
}
}
};
function PlayerController() {
EntityController.call(this);
}
PlayerController.prototype = Object.create(EntityController.prototype);
PlayerController.prototype.add = function (id) {
var player = new Player(id);
this.entities.push(player);
return player;
};
var _io = socketio,
_socket = null,
_options = null,
_stateController = new StateController(),
_playerController = new PlayerController(),
_entityController = new EntityController(),
initializeGarageServer = function (path, options) {
_options = options;
_socket = _io.connect(path + '/garageserver.io');
registerSocketEvents();
},
registerSocketEvents = function () {
_socket.on('connect', function () {
_stateController.id = _socket.socket.sessionid;
_playerController.add(_stateController.id);
if (_options.onPlayerConnect) {
_options.onPlayerConnect();
}
if (_options.logging) {
console.log('garageserver.io:: socket connect');
}
});
_socket.on('state', function (data) {
if (_options.onWorldState) {
_options.onWorldState(data.worldState);
}
_stateController.physicsDelta = data.physicsDelta;
_stateController.interpolation = data.interpolation;
_stateController.interpolationDelay = data.interpolationDelay;
_stateController.pingInterval = data.pingInterval;
_stateController.clientSidePrediction = data.clientSidePrediction;
_stateController.smoothingFactor = data.smoothingFactor;
_stateController.worldState = data.worldState;
setInterval(function (){
_socket.emit('ping', new Date().getTime());
}, _stateController.pingInterval);
});
_socket.on('disconnect', function () {
if (_options.onPlayerDisconnect) {
_options.onPlayerDisconnect();
}
if (_options.logging) {
console.log('garageserver.io:: socket disconnect');
}
});
_socket.on('reconnect', function () {
if (_options.onPlayerReconnect) {
_options.onPlayerReconnect();
}
if (_options.logging) {
console.log('garageserver.io:: socket reconnect');
}
});
_socket.on('update', function (data) {
update(data);
});
_socket.on('ping', function (data) {
_stateController.pingDelay = new Date().getTime() - data;
if (_options.onPing) {
_options.onPing(_stateController.pingDelay);
}
if (_options.logging) {
console.log('garageserver.io:: socket ping delay ' + _stateController.pingDelay);
}
});
_socket.on('removePlayer', function (id) {
removePlayer(id);
if (_options.onPlayerRemove) {
_options.onPlayerRemove(id);
}
if (_options.logging) {
console.log('garageserver.io:: socket removePlayer ' + id);
}
});
_socket.on('removeEntity', function (id) {
removeEntity(id);
if (_options.onEntityRemove) {
_options.onEntityRemove(id);
}
if (_options.logging) {
console.log('garageserver.io:: socket removeEntity ' + id);
}
});
_socket.on('event', function(data) {
if (_options.onEvent) {
_options.onEvent(data);
}
if (_options.logging) {
console.log('garageserver.io:: socket event ' + data);
}
});
},
getId = function () {
return _stateController.id;
},
addInput = function (clientInput) {
_playerController.entities.some(function (player) {
if (player.id === _stateController.id) {
if (_stateController.clientSidePrediction && _options.onUpdatePlayerPhysics) {
player.inputController.add(clientInput);
player.state = _options.onUpdatePlayerPhysics(player.state, [{ input: clientInput }], _stateController.physicsDelta);
}
_socket.emit('input', [ clientInput, player.inputController.sequenceNumber, _stateController.renderTime ]);
}
});
},
getStates = function (stateCallback) {
var playerStates = [], entityStates = [];
if (_stateController.interpolation && _options.onInterpolation) {
processEntityStatesInterpolated(_entityController);
processEntityStatesInterpolated(_playerController);
} else {
processEntityStatesCurrent(_entityController);
processEntityStatesCurrent(_playerController);
}
_playerController.entities.forEach(function(player) {
playerStates.push({ state: player.state });
});
_entityController.entities.forEach(function(entity) {
entityStates.push({ state: entity.state });
});
stateCallback(playerStates, entityStates);
},
removePlayer = function (id) {
_playerController.remove(id);
},
removeEntity = function (id) {
_entityController.remove(id);
},
update = function (data) {
_stateController.setTime(data.time);
updatePlayers(data);
updateEntities(data);
},
updatePlayers = function (data) {
data.playerStates.forEach(function (playerState) {
updateEntity(_playerController, playerState, data.time);
if (_options.onPlayerUpdate) {
_options.onPlayerUpdate(playerState[1]);
}
});
},
updateEntities = function (data) {
data.entityStates.forEach(function (entityState) {
updateEntity(_entityController, entityState, data.time);
if (_options.onEntityUpdate) {
_options.onEntityUpdate(entityState[1]);
}
});
},
updateEntity = function (entityController, entityState, time) {
var entityFound = false;
entityController.entities.some(function (entity) {
if (entity.id === entityState[0]) {
entityFound = true;
entity.updateState(entityState[1], entityState[2], time);
return true;
}
});
if (!entityFound) {
var newEntity = entityController.add(entityState[0]);
newEntity.addUpdate(entityState[1], entityState[2], time);
}
},
processEntityStatesCurrent = function (entityController) {
entityController.entities.forEach(function (entity) {
if (entity.anyUpdates() && !entity.inputController.any()) {
entity.state = entity.latestUpdate().state;
}
});
},
processEntityStatesInterpolated = function (entityController) {
var positions, amount, newState;
entityController.entities.forEach(function (entity) {
if (entity.anyUpdates() && !entity.inputController.any()) {
positions = entity.surroundingPositions(_stateController.renderTime);
if (positions.previous && positions.target) {
amount = getInterpolatedAmount(positions.previous.time, positions.target.time);
newState = _options.onInterpolation(positions.previous.state, positions.target.state, amount);
entity.state = newState = _options.onInterpolation(entity.state, newState, _stateController.smoothingFactor);
}
}
});
},
getInterpolatedAmount = function (previousTime, targetTime) {
var range = targetTime - previousTime,
difference = _stateController.renderTime - previousTime,
amount = parseFloat((difference / range).toFixed(3));
return amount;
};
return {
initializeGarageServer: initializeGarageServer,
addInput: addInput,
getStates: getStates,
getId: getId
};
}) (io);