Creating the master / agent hiring system

This commit is contained in:
samcake 2015-11-25 17:10:38 -08:00
parent 184669762a
commit 3273fbcad4
2 changed files with 292 additions and 212 deletions

View file

@ -10,235 +10,305 @@
// //
function printDebug(message) { function printDebug(message) {
print(message); print(message);
} }
(function() { (function() {
var COMMAND_CHANNEL = "com.highfidelity.PlaybackChannel1"; var COMMAND_CHANNEL = "com.highfidelity.PlaybackChannel1";
var ANNOUNCE_CHANNEL = "com.highfidelity.playbackAgent.announceID"; var ANNOUNCE_CHANNEL = "com.highfidelity.playbackAgent.announceID";
// The time between alive messages on the command channel // The time between alive messages on the command channel
var ALIVE_PERIOD = 3; var ALIVE_PERIOD = 3;
var NUM_CYCLES_BEFORE_RESET = 5; var NUM_CYCLES_BEFORE_RESET = 8;
// Service Actions // Service Actions
var AGENT_READY = "ready"; var MASTER_ID = -1;
var INVALID_ACTOR = -2; var AGENT_READY = "ready";
var AGENT_LOST = "agentLost";
var MASTER_INDEX = -1; var INVALID_ACTOR = -2;
var MASTER_ALIVE = -1;
var MASTER_FIRE_AGENT = -2;
var makeMessage = function(id, action, argument) { var AGENTS_BROADCAST = -1;
var message = { var MASTER_ALIVE = -1;
var MASTER_FIRE_AGENT = -2;
var makeUniqueUUID = function(SEUUID) {
//return SEUUID + Math.random();
// forget complexity, just give me a four digit pin
return (Math.random() * 10000).toFixed(0);
}
var packAnnounceMessage = function(dest, command, src) {
var message = {
dest: dest,
command: command,
src: src
};
return JSON.stringify(message);
};
var unpackAnnounceMessage = function(message) {
return JSON.parse(message);
};
var packCommandMessage = function(id, action, argument) {
var message = {
id_key: id, id_key: id,
action_key: action, action_key: action,
argument_key: argument argument_key: argument
}; };
return message; return JSON.stringify(message);
};
var unpackMessage = function(message) {
return JSON.parse(message);
};
// master side
//---------------------------------
var MasterController = function() {
this.timeSinceLastAlive = 0;
this.knownAgents = new Array;
this.subscribed = false;
}; };
MasterController.prototype.destroy = function() { var unpackCommandMessage = function(message) {
if (this.subscribed) { return JSON.parse(message);
Messages.unsubscribe(ANNOUNCE_CHANNEL); };
Messages.unsubscribe(COMMAND_CHANNEL);
this.subscribed = true;
}
};
MasterController.prototype.reset = function() { // master side
this.timeSinceLastAlive = 0; //---------------------------------
var MasterController = function() {
this.timeSinceLastAlive = 0;
this.knownAgents = new Array;
this.hiringAgentsQueue = new Array;
this.subscribed = false;
};
if (!this.subscribed) { MasterController.prototype.destroy = function() {
Messages.subscribe(COMMAND_CHANNEL); if (this.subscribed) {
Messages.subscribe(ANNOUNCE_CHANNEL); Messages.unsubscribe(ANNOUNCE_CHANNEL);
var localThis = this; Messages.unsubscribe(COMMAND_CHANNEL);
Messages.messageReceived.connect(function (channel, message, senderID) { this.subscribed = true;
if (channel == ANNOUNCE_CHANNEL) { }
localThis._processAnnounceMessage(message, senderID); };
return;
} MasterController.prototype.reset = function() {
}); this.timeSinceLastAlive = 0;
}
// ready to roll, enable if (!this.subscribed) {
this.subscribed = true; Messages.subscribe(COMMAND_CHANNEL);
printDebug("Master Started"); Messages.subscribe(ANNOUNCE_CHANNEL);
var localThis = this;
Messages.messageReceived.connect(function (channel, message, senderID) {
if (channel == ANNOUNCE_CHANNEL) {
localThis._processAnnounceMessage(message, senderID);
return;
}
});
}
// ready to roll, enable
this.subscribed = true;
printDebug("Master Started");
}; };
MasterController.prototype._processAnnounceMessage = function(message, senderID) { MasterController.prototype._processAnnounceMessage = function(message, senderID) {
if (message == AGENT_READY) { var message = unpackAnnounceMessage(message);
if (message.dest == MASTER_ID) {
// check to see if we know about this agent if (message.command == AGENT_READY) {
if (this.knownAgents.indexOf(senderID) < 0) { // check to see if we know about this agent
var agentIndex = this.knownAgents.indexOf(message.src);
var indexOfNewAgent = this.knownAgents.length; if (agentIndex < 0) {
this.knownAgents[indexOfNewAgent] = senderID; if (this.hiringAgentsQueue.length > 0) {
printDebug("New agent available to be hired " + senderID); var hiringHandler = this.hiringAgentsQueue.pop();
hiringHandler(message.src);
var acknowledgeMessage = senderID + "." + indexOfNewAgent; } else {
Messages.sendMessage(ANNOUNCE_CHANNEL, acknowledgeMessage); //No hiring in queue so bail
} else { return;
printDebug("New agent still sending ready ? " + senderID); }
} } else {
} // Master think the agent is hired but not the other way around, forget about it
printDebug("New agent still sending ready ? " + message.src + " " + agentIndex + " Forgeting about it");
this.knownAgents[agentIndex] = INVALID_ACTOR;
}
} else if (message.command == AGENT_LOST) {
// check to see if we know about this agent
var agentIndex = this.knownAgents.indexOf(message.src);
if (agentIndex < 0) {
printDebug("Lost agent " + message.src + " " + agentIndex + " Forgeting about it");
this.knownAgents[agentIndex] = INVALID_ACTOR;
}
}
}
}; };
MasterController.prototype.sendCommand = function(target, action, argument) { MasterController.prototype.sendCommand = function(target, action, argument) {
if (this.subscribed) { if (this.subscribed) {
var messageJSON = JSON.stringify(makeMessage(target, action, argument)); var messageJSON = packCommandMessage(target, action, argument);
Messages.sendMessage(COMMAND_CHANNEL, messageJSON); Messages.sendMessage(COMMAND_CHANNEL, messageJSON);
printDebug("Master sent message: " + messageJSON);
} }
};
MasterController.prototype.update = function(deltaTime) {
this.timeSinceLastAlive += deltaTime;
if (this.timeSinceLastAlive > ALIVE_PERIOD) {
this.timeSinceLastAlive = 0;
printDebug("Master ping alive");
this.sendCommand(MASTER_INDEX, MASTER_ALIVE);
}
};
this.MasterController = MasterController;
// agent side
//---------------------------------
var AgentController = function() {
this.subscribed = false;
this.timeSinceLastAlive = 0;
this.numCyclesWithoutAlive = 0;
this.actorIndex = INVALID_ACTOR;
this.notifyAlive = false;
this.onHired = function() {};
this.onCommand = function(command) {};
this.onFired = function() {};
}; };
AgentController.prototype.destroy = function() { MasterController.prototype.update = function(deltaTime) {
if (this.subscribed) { this.timeSinceLastAlive += deltaTime;
this.fire(); if (this.timeSinceLastAlive > ALIVE_PERIOD) {
Messages.unsubscribe(ANNOUNCE_CHANNEL); this.timeSinceLastAlive = 0;
Messages.unsubscribe(COMMAND_CHANNEL); this.sendCommand(AGENTS_BROADCAST, MASTER_ALIVE);
this.subscribed = true; }
}
};
AgentController.prototype.reset = function() {
// If already hired, fire
this.fired();
if (!this.subscribed) {
Messages.subscribe(COMMAND_CHANNEL);
Messages.subscribe(ANNOUNCE_CHANNEL);
var localThis = this;
Messages.messageReceived.connect(function (channel, message, senderID) {
if (channel == ANNOUNCE_CHANNEL) {
localThis._processAnnounceMessage(message, senderID);
return;
}
if (channel == COMMAND_CHANNEL) {
localThis._processCommandMessage(message, senderID);
return;
}
});
}
this.subscribed = true;
printDebug("Client Started");
}; };
AgentController.prototype._processAnnounceMessage = function(message, senderID) {
//printDebug("Client " + this.actorIndex + " Received Announcement = " + message);
if (message != AGENT_READY) {
// this may be a message to hire me if i m not already
if (this.actorIndex == INVALID_ACTOR) {
var parts = message.split(".");
var agentID = parts[0];
var actorIndex = parts[1];
//printDebug("Client " + Agent.sessionUUID + " - " + agentID + " Hired!");
if (agentID == Agent.sessionUUID) {
this.actorIndex = actorIndex;
printDebug("Client " + this.actorIndex + " Hired!");
this.onHired();
// Messages.unsubscribe(ANNOUNCE_CHANNEL); // id announce channel
}
}
}
}
AgentController.prototype._processCommandMessage = function(message, senderID) { MasterController.prototype.hireAgent = function(onHired) {
var command = unpackMessage(message); var localThis = this;
//printDebug("Received command = " + JSON.stringify(command)); this.hiringAgentsQueue.unshift(function(agentID) {
printDebug("hiring callback with agent " + agentID);
if (command.id_key == localThis.actorIndex || command.id_key == MASTER_INDEX) { var indexOfNewAgent = localThis.knownAgents.length;
if (command.action_key == MASTER_ALIVE) { localThis.knownAgents[indexOfNewAgent] = agentID;
this.notifyAlive = true;
} else if (command.action_key == MASTER_FIRE_AGENT) { printDebug("New agent available to be hired " + agentID + " " + index);
printDebug("Master firing Agent"); var hireMessage = "HIRE." + index;
this.fire(); var answerMessage = packAnnounceMessage(agentID, hireMessage, MASTER_ID);
} else { Messages.sendMessage(ANNOUNCE_CHANNEL, answerMessage);
printDebug("True action received = " + JSON.stringify(command) + senderID);
this.onCommand(command); if (onHired != null) {
} onHired(index);
} else { }
// ignored })
}
}; };
AgentController.prototype.update = function(deltaTime) { this.MasterController = MasterController;
this.timeSinceLastAlive += deltaTime;
if (this.timeSinceLastAlive > ALIVE_PERIOD) {
if (this.subscribed) {
if (this.actorIndex == INVALID_ACTOR) {
Messages.sendMessage(ANNOUNCE_CHANNEL, AGENT_READY);
//printDebug("Client Ready" + ANNOUNCE_CHANNEL + AGENT_READY);
} else {
this.numCyclesWithoutAlive++;
if (this.notifyAlive) {
this.notifyAlive = false;
this.numCyclesWithoutAlive = 0;
printDebug("Master Alive");
} else if (this.numCyclesWithoutAlive > NUM_CYCLES_BEFORE_RESET) {
printDebug("Master Lost, firing Agent");
this.fired();
}
}
}
this.timeSinceLastAlive = 0; // agent side
} //---------------------------------
}; var AgentController = function() {
this.subscribed = false;
AgentController.prototype.fired = function() { this._init();
// clear the state first
var wasHired = (this.actorIndex != INVALID_ACTOR); this.onHired = function() {};
this.actorIndex= INVALID_ACTOR; this.onCommand = function(command) {};
this.onFired = function() {};
};
AgentController.prototype._init = function() {
this.actorIndex= INVALID_ACTOR;
this.notifyAlive = false; this.notifyAlive = false;
this.timeSinceLastAlive = 0; this.timeSinceLastAlive = 0;
this.numCyclesWithoutAlive = 0; this.numCyclesWithoutAlive = 0;
this.actorUUID = makeUniqueUUID(Agent.sessionUUID);
printDebug("this.actorUUID = " + this.actorUUID);
}
AgentController.prototype.destroy = function() {
if (this.subscribed) {
this.fire();
Messages.unsubscribe(ANNOUNCE_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(ANNOUNCE_CHANNEL);
var localThis = this;
Messages.messageReceived.connect(function (channel, message, senderID) {
if (channel == ANNOUNCE_CHANNEL) {
localThis._processAnnounceMessage(message, senderID);
return;
}
if (channel == COMMAND_CHANNEL) {
localThis._processCommandMessage(message, senderID);
return;
}
});
}
this.subscribed = true;
printDebug("Client Started");
};
AgentController.prototype._processAnnounceMessage = function(message, senderID) {
var announce = unpackCommandMessage(message);
//printDebug("Client " + this.actorIndex + " Received Announcement = " + message);
if (announce.dest == this.actorUUID) {
if (announce.command != AGENT_READY) {
// this may be a message to hire me if i m not already
if (this.actorIndex == INVALID_ACTOR) {
printDebug(announce.command);
var parts = announce.command.split(".");
var agentID = parts[0];
var actorIndex = parts[1];
//printDebug("Client " + Agent.sessionUUID + " - " + agentID + " Hired!");
// if (agentID == Agent.sessionUUID) {
this.actorIndex = actorIndex;
printDebug("Client " + this.actorIndex + " Hired!");
this.onHired();
// Messages.unsubscribe(ANNOUNCE_CHANNEL); // id announce channel
// }
}
}
}
}
AgentController.prototype._processCommandMessage = function(message, senderID) {
var command = unpackCommandMessage(message);
//printDebug("Received command = " + JSON.stringify(command));
if ((command.id_key == this.actorUUID) || (command.id_key == AGENTS_BROADCAST)) {
if (command.action_key == MASTER_ALIVE) {
this.notifyAlive = true;
} else if (command.action_key == MASTER_FIRE_AGENT) {
printDebug("Master firing Agent");
this.fire();
} else {
printDebug("True action received = " + JSON.stringify(command) + senderID);
this.onCommand(command);
}
} else {
// ignored
}
};
AgentController.prototype.update = function(deltaTime) {
this.timeSinceLastAlive += deltaTime;
if (this.timeSinceLastAlive > ALIVE_PERIOD) {
if (this.subscribed) {
if (this.actorIndex == INVALID_ACTOR) {
Messages.sendMessage(ANNOUNCE_CHANNEL, packAnnounceMessage(MASTER_ID, AGENT_READY, this.actorUUID));
//printDebug("Client Ready" + ANNOUNCE_CHANNEL + AGENT_READY);
} else {
this.numCyclesWithoutAlive++;
if (this.notifyAlive) {
this.notifyAlive = false;
this.numCyclesWithoutAlive = 0;
printDebug("Master Alive");
} else if (this.numCyclesWithoutAlive > NUM_CYCLES_BEFORE_RESET) {
printDebug("Master Lost, firing Agent");
this.fired();
}
}
}
this.timeSinceLastAlive = 0;
}
};
AgentController.prototype.fired = function() {
// clear the state first
var wasHired = (this.actorIndex != INVALID_ACTOR);
// Post a last message to master in case it still listen to warn that this agent is losing it
if (wasHired) {
Messages.sendMessage(ANNOUNCE_CHANNEL, packAnnounceMessage(MASTER_ID, AGENT_LOST, this.actorUUID));
}
// reset
this._init();
// then custom fire if was hired // then custom fire if was hired
if (wasHired) { if (wasHired) {
this.onFired(); this.onFired();
} }
} }
this.AgentController = AgentController; this.AgentController = AgentController;
})(); })();

View file

@ -54,13 +54,23 @@ var performanceLoadedNeedUpdate = false;
setupPlayback(); setupPlayback();
function onHiring(agentID, index) {
print("agent hired from playbackMaster! " + agentID + " " + index)
}
function setupPlayback() { function setupPlayback() {
ac_number = Window.prompt("Insert number of agents: ","1"); ac_number = Window.prompt("Insert number of agents: ","1");
if (ac_number === "" || ac_number === null) { if (ac_number === "" || ac_number === null) {
ac_number = 1; ac_number = 1;
} }
setupToolBars();
masterController.reset(); masterController.reset();
for (var i = 0; i < ac_number; i++) {
masterController.hireAgent(onHiring);
}
setupToolBars();
} }
function setupToolBars() { function setupToolBars() {
@ -96,13 +106,13 @@ function setupToolBars() {
var playLoopWidthFactor = 1.65; var playLoopWidthFactor = 1.65;
playLoopIcon[i] = toolBars[i].addTool({ playLoopIcon[i] = toolBars[i].addTool({
imageURL: TOOL_ICON_URL + "play-and-loop.svg", imageURL: TOOL_ICON_URL + "play-and-loop.svg",
subImage: { x: 0, y: 0, width: playLoopWidthFactor * Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, subImage: { x: 0, y: 0, width: playLoopWidthFactor * Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
width: playLoopWidthFactor * Tool.IMAGE_WIDTH, width: playLoopWidthFactor * Tool.IMAGE_WIDTH,
height: Tool.IMAGE_HEIGHT, height: Tool.IMAGE_HEIGHT,
alpha: ALPHA_OFF, alpha: ALPHA_OFF,
visible: true visible: true
}, false); }, false);
stopIcon[i] = toolBars[i].addTool({ stopIcon[i] = toolBars[i].addTool({
imageURL: TOOL_ICON_URL + "recording-stop.svg", imageURL: TOOL_ICON_URL + "recording-stop.svg",
@ -121,20 +131,20 @@ function setupToolBars() {
}, false); }, false);
nameOverlays.push(Overlays.addOverlay("text", { nameOverlays.push(Overlays.addOverlay("text", {
backgroundColor: { red: 0, green: 0, blue: 0 }, backgroundColor: { red: 0, green: 0, blue: 0 },
font: { size: TEXT_HEIGHT }, font: { size: TEXT_HEIGHT },
text: (i == ac_number) ? "Master" : i + ". " + text: (i == ac_number) ? "Master" : i + ". " +
((i < names.length) ? names[i] : ((i < names.length) ? names[i] :
"AC" + i), "AC" + i),
x: 0, y: 0, x: 0, y: 0,
width: toolBars[i].width + ToolBar.SPACING, width: toolBars[i].width + ToolBar.SPACING,
height: TEXT_HEIGHT + TEXT_MARGIN, height: TEXT_HEIGHT + TEXT_MARGIN,
leftMargin: TEXT_MARGIN, leftMargin: TEXT_MARGIN,
topMargin: TEXT_MARGIN, topMargin: TEXT_MARGIN,
alpha: ALPHA_OFF, alpha: ALPHA_OFF,
backgroundAlpha: ALPHA_OFF, backgroundAlpha: ALPHA_OFF,
visible: true visible: true
})); }));
} }
} }