overte-lubosz/script-archive/acScripts/AgentPoolController.js
2016-04-26 11:18:22 -07:00

416 lines
14 KiB
JavaScript

//
// AgentPoolController.js
// acScripts
//
// Created by Sam Gateau on 11/23/15.
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
function printDebug(message) {
print(message);
}
(function() {
var SERVICE_CHANNEL = "com.highfidelity.playback.service";
var COMMAND_CHANNEL = "com.highfidelity.playback.command";
// The time between alive messages on the command channel
var ALIVE_PERIOD = 3;
var NUM_CYCLES_BEFORE_RESET = 8;
// Service Actions
var MASTER_ID = -1;
var INVALID_AGENT = -2;
var BROADCAST_AGENTS = -3;
var MASTER_ALIVE = "MASTER_ALIVE";
var AGENT_ALIVE = "AGENT_ALIVE";
var AGENT_READY = "READY";
var MASTER_HIRE_AGENT = "HIRE"
var MASTER_FIRE_AGENT = "FIRE";
var makeUniqueUUID = function(SEUUID) {
//return SEUUID + Math.random();
// forget complexity, just give me a four digit pin
return (Math.random() * 10000).toFixed(0);
}
var packServiceMessage = function(dest, command, src) {
var message = {
dest: dest,
command: command,
src: src
};
return JSON.stringify(message);
};
var unpackServiceMessage = function(message) {
return JSON.parse(message);
};
var packCommandMessage = function(dest, action, argument) {
var message = {
dest_key: dest,
action_key: action,
argument_key: argument
};
return JSON.stringify(message);
};
var unpackCommandMessage = function(message) {
return JSON.parse(message);
};
// Actor
//---------------------------------
var Actor = function() {
this.agentID = INVALID_AGENT;
this.lastAliveCycle = 0;
this.onHired = function(actor) {};
this.onFired = function(actor) {};
};
Actor.prototype.isConnected = function () {
return (this.agentID != INVALID_AGENT);
}
Actor.prototype.alive = function () {
printDebug("Agent UUID =" + this.agentID + " Alive was " + this.lastAliveCycle);
this.lastAliveCycle = 0;
}
Actor.prototype.incrementAliveCycle = function () {
printDebug("Actor.prototype.incrementAliveCycle UUID =" + this.agentID + " Alive pre increment " + this.lastAliveCycle);
if (this.isConnected()) {
this.lastAliveCycle++;
printDebug("Agent UUID =" + this.agentID + " Alive incremented " + this.lastAliveCycle);
}
return this.lastAliveCycle;
}
this.Actor = Actor;
// master side
//---------------------------------
var MasterController = function() {
this.timeSinceLastAlive = 0;
this.knownAgents = new Array;
this.hiredActors = new Array;
this.hiringAgentsQueue = new Array;
this.subscribed = false;
};
MasterController.prototype.destroy = function() {
if (this.subscribed) {
Messages.unsubscribe(SERVICE_CHANNEL);
Messages.unsubscribe(COMMAND_CHANNEL);
this.subscribed = true;
}
};
MasterController.prototype.reset = function() {
this.timeSinceLastAlive = 0;
if (!this.subscribed) {
Messages.subscribe(COMMAND_CHANNEL);
Messages.subscribe(SERVICE_CHANNEL);
var localThis = this;
Messages.messageReceived.connect(function (channel, message, senderID) {
if (channel == SERVICE_CHANNEL) {
localThis._processServiceMessage(message, senderID);
return;
}
});
}
// ready to roll, enable
this.subscribed = true;
printDebug("Master Started");
};
MasterController.prototype._processServiceMessage = function(message, senderID) {
var service = unpackServiceMessage(message);
if (service.dest == MASTER_ID) {
if (service.command == AGENT_READY) {
// check to see if we know about this agent
var agentIndex = this.knownAgents.indexOf(service.src);
if (agentIndex < 0) {
this._onAgentAvailableForHiring(service.src);
} else {
// Master think the agent is hired but not the other way around, forget about it
printDebug("New agent still sending ready ? " + service.src + " " + agentIndex + " Forgeting about it");
// this._removeHiredAgent(agentIndex);
}
} else if (service.command == AGENT_ALIVE) {
// check to see if we know about this agent
var agentIndex = this.knownAgents.indexOf(service.src);
if (agentIndex >= 0) {
// yes so reset its alive beat
this.hiredActors[agentIndex].alive();
return;
} else {
return;
}
}
}
};
MasterController.prototype._onAgentAvailableForHiring = function(agentID) {
if (this.hiringAgentsQueue.length == 0) {
printDebug("No Actor on the hiring queue");
return;
}
printDebug("MasterController.prototype._onAgentAvailableForHiring " + agentID);
var newActor = this.hiringAgentsQueue.pop();
var indexOfNewAgent = this.knownAgents.push(agentID);
newActor.alive();
newActor.agentID = agentID;
this.hiredActors.push(newActor);
printDebug("New agent available to be hired " + agentID + " " + indexOfNewAgent);
var serviceMessage = packServiceMessage(agentID, MASTER_HIRE_AGENT, MASTER_ID);
printDebug("serviceMessage = " + serviceMessage);
Messages.sendMessage(SERVICE_CHANNEL, serviceMessage);
printDebug("message sent calling the actor" + JSON.stringify(newActor) );
newActor.onHired(newActor);
}
MasterController.prototype.sendCommand = function(target, action, argument) {
if (this.subscribed) {
var command = packCommandMessage(target, action, argument);
printDebug(command);
Messages.sendMessage(COMMAND_CHANNEL, command);
}
};
MasterController.prototype.update = function(deltaTime) {
this.timeSinceLastAlive += deltaTime;
if (this.timeSinceLastAlive > ALIVE_PERIOD) {
this.timeSinceLastAlive = 0;
Messages.sendMessage(SERVICE_CHANNEL, packServiceMessage(BROADCAST_AGENTS, MASTER_ALIVE, MASTER_ID));
{
// Check for alive connected agents
var lostAgents = new Array();
for (var i = 0; i < this.hiredActors.length; i++) {
var actor = this.hiredActors[i];
var lastAlive = actor.incrementAliveCycle()
if (lastAlive > NUM_CYCLES_BEFORE_RESET) {
printDebug("Agent Lost, firing Agent #" + i + " ID " + actor.agentID);
lostAgents.push(i);
}
}
// now fire gathered lost agents from end to begin
while (lostAgents.length > 0) {
printDebug("Firing " + lostAgents.length + " agents" + JSON.stringify(lostAgents));
this.fireAgent(this.hiredActors[lostAgents.pop()]);
}
}
}
};
MasterController.prototype.hireAgent = function(actor) {
if (actor == null) {
printDebug("trying to hire an agent with a null actor, abort");
return;
}
if (actor.isConnected()) {
printDebug("trying to hire an agent already connected, abort");
return;
}
this.hiringAgentsQueue.unshift(actor);
};
MasterController.prototype.fireAgent = function(actor) {
// check to see if we know about this agent
printDebug("MasterController.prototype.fireAgent" + actor.agentID);
// Try the waiting list first
var waitingIndex = this.hiringAgentsQueue.indexOf(actor);
if (waitingIndex >= 0) {
printDebug("fireAgent found actor on waiting queue #" + waitingIndex);
var lostActor = this.hiringAgentsQueue.splice(waitingIndex, 1);
if (lostActor.length) {
lostActor[0].onFired(lostActor[0]);
}
return;
}
// then the hired agents
var actorIndex = this.knownAgents.indexOf(actor.agentID);
if (actorIndex >= 0) {
printDebug("fired actor found #" + actorIndex);
this._removeHiredAgent(actorIndex);
}
}
MasterController.prototype._removeHiredAgent = function(actorIndex) {
// check to see if we know about this agent
if (actorIndex >= 0) {
printDebug("MasterController.prototype._removeHiredAgent #" + this.knownAgents[actorIndex])
this.knownAgents.splice(actorIndex, 1);
var lostActor = this.hiredActors[actorIndex];
this.hiredActors.splice(actorIndex, 1);
var lostAgentID = lostActor.agentID;
lostActor.agentID = INVALID_AGENT;
if (lostAgentID != INVALID_AGENT) {
printDebug("fired actor is still connected, send fire command");
Messages.sendMessage(SERVICE_CHANNEL, packServiceMessage(lostAgentID, MASTER_FIRE_AGENT, MASTER_ID));
}
lostActor.onFired(lostActor);
}
}
this.MasterController = MasterController;
// agent side
//---------------------------------
var AgentController = function() {
this.subscribed = false;
this._init();
this.onHired = function() {};
this.onCommand = function(command) {};
this.onFired = function() {};
};
AgentController.prototype._init = function() {
this.isHired= false;
this.timeSinceLastAlive = 0;
this.numCyclesWithoutAlive = 0;
this.agentUUID = makeUniqueUUID(Agent.sessionUUID);
printDebug("this.agentUUID = " + this.agentUUID);
}
AgentController.prototype.destroy = function() {
if (this.subscribed) {
this.fired();
Messages.unsubscribe(SERVICE_CHANNEL);
Messages.unsubscribe(COMMAND_CHANNEL);
this.subscribed = true;
}
};
AgentController.prototype.reset = function() {
// If already hired, fire
this.fired();
if (!this.subscribed) {
Messages.subscribe(COMMAND_CHANNEL);
Messages.subscribe(SERVICE_CHANNEL);
var localThis = this;
Messages.messageReceived.connect(function (channel, message, senderID) {
if (channel == SERVICE_CHANNEL) {
localThis._processServiceMessage(message, senderID);
return;
}
if (channel == COMMAND_CHANNEL) {
localThis._processCommandMessage(message, senderID);
return;
}
});
}
this.subscribed = true;
printDebug("Client Started");
};
AgentController.prototype._processServiceMessage = function(message, senderID) {
var service = unpackServiceMessage(message);
printDebug("Client " + this.agentUUID + " Received message = " + message);
if (service.dest == this.agentUUID) {
if (service.command != AGENT_READY) {
// this is potentially a message to hire me if i m not already
if (!this.isHired && (service.command == MASTER_HIRE_AGENT)) {
printDebug(service.command);
this.isHired = true;
printDebug("Client Hired by master UUID" + service.src);
this.onHired();
this.alive();
return;
}
// Or maybe a message to fire me if i m not hired
if (this.isHired && (service.command == MASTER_FIRE_AGENT)) {
printDebug("Client Fired by master UUID" + senderID);
this.fired();
return;
}
}
} else if ((service.src == MASTER_ID) && (service.command == MASTER_ALIVE)) {
this.numCyclesWithoutAlive = 0;
return;
}
}
AgentController.prototype._processCommandMessage = function(message, senderID) {
// ONly work if hired
if (this.isHired) {
var command = unpackCommandMessage(message);
if ((command.dest_key == this.agentUUID) || (command.dest_key == BROADCAST_AGENTS)) {
printDebug("Command received = " + JSON.stringify(command) + senderID);
this.onCommand(command);
}
}
};
AgentController.prototype.update = function(deltaTime) {
this.timeSinceLastAlive += deltaTime;
if (this.timeSinceLastAlive > ALIVE_PERIOD) {
if (this.subscribed) {
if (!this.isHired) {
Messages.sendMessage(SERVICE_CHANNEL, packServiceMessage(MASTER_ID, AGENT_READY, this.agentUUID));
//printDebug("Client Ready" + SERVICE_CHANNEL + AGENT_READY);
} else {
// Send alive beat
printDebug("beat !");
Messages.sendMessage(SERVICE_CHANNEL, packServiceMessage(MASTER_ID, AGENT_ALIVE, this.agentUUID));
// Listen for master beat
this.numCyclesWithoutAlive++;
if (this.numCyclesWithoutAlive > NUM_CYCLES_BEFORE_RESET) {
printDebug("Master Lost, self firing Agent");
this.fired();
}
}
}
this.timeSinceLastAlive = 0;
}
};
AgentController.prototype.fired = function() {
// clear the state first
var wasHired = this.isHired;
// reset
this._init();
// then custom fire if was hired
if (wasHired) {
this.onFired();
}
}
this.AgentController = AgentController;
this.BROADCAST_AGENTS = BROADCAST_AGENTS;
})();