diff --git a/examples/acScripts/AgentPoolController.js b/examples/acScripts/AgentPoolController.js new file mode 100644 index 0000000000..830a8fe1e3 --- /dev/null +++ b/examples/acScripts/AgentPoolController.js @@ -0,0 +1,416 @@ +// +// 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; +})(); + + + diff --git a/examples/acScripts/playbackAgents.js b/examples/acScripts/playbackAgents.js index 16dd469a89..cf805623de 100644 --- a/examples/acScripts/playbackAgents.js +++ b/examples/acScripts/playbackAgents.js @@ -9,18 +9,15 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +Script.include("./AgentPoolController.js"); +var agentController = new AgentController(); + // Set the following variables to the values needed -var commandChannel = "com.highfidelity.PlaybackChannel1"; -var clip_url = null; var playFromCurrentLocation = true; var useDisplayName = true; var useAttachments = true; var useAvatarModel = true; -// ID of the agent. Two agents can't have the same ID. -var announceIDChannel = "com.highfidelity.playbackAgent.announceID"; -var UNKNOWN_AGENT_ID = -2; -var id = UNKNOWN_AGENT_ID; // unknown until aknowledged // Set position/orientation/scale here if playFromCurrentLocation is true Avatar.position = { x:0, y: 0, z: 0 }; @@ -28,16 +25,12 @@ Avatar.orientation = Quat.fromPitchYawRollDegrees(0, 0, 0); Avatar.scale = 1.0; var totalTime = 0; -var subscribed = false; var WAIT_FOR_AUDIO_MIXER = 1; // Script. DO NOT MODIFY BEYOND THIS LINE. -var DO_NOTHING = 0; var PLAY = 1; var PLAY_LOOP = 2; var STOP = 3; -var SHOW = 4; -var HIDE = 5; var LOAD = 6; Recording.setPlayFromCurrentLocation(playFromCurrentLocation); @@ -46,29 +39,15 @@ Recording.setPlayerUseAttachments(useAttachments); Recording.setPlayerUseHeadModel(false); Recording.setPlayerUseSkeletonModel(useAvatarModel); -function getAction(channel, message, senderID) { - if(subscribed) { - var command = JSON.parse(message); - print("I'm the agent " + id + " and I received this: ID: " + command.id_key + " Action: " + command.action_key + " URL: " + command.clip_url_key); - - if (command.id_key == id || command.id_key == -1) { - if (command.action_key === 6) { - clip_url = command.clip_url_key; - } - - action = command.action_key; - print("That command was for me!"); - print("My clip is: " + clip_url); - } else { - action = DO_NOTHING; - } - - switch(action) { +function agentCommand(command) { + if(true) { + + // var command = JSON.parse(message); + print("I'm the agent " + this.agentUUID + " and I received this: Dest: " + command.dest_key + " Action: " + command.action_key + " URL: " + command.argument_key); + + switch(command.action_key) { case PLAY: print("Play"); - if (!Agent.isAvatar) { - Agent.isAvatar = true; - } if (!Recording.isPlaying()) { Recording.startPlaying(); } @@ -76,9 +55,6 @@ function getAction(channel, message, senderID) { break; case PLAY_LOOP: print("Play loop"); - if (!Agent.isAvatar) { - Agent.isAvatar = true; - } if (!Recording.isPlaying()) { Recording.startPlaying(); } @@ -90,75 +66,60 @@ function getAction(channel, message, senderID) { Recording.stopPlaying(); } break; - case SHOW: - print("Show"); - if (!Agent.isAvatar) { - Agent.isAvatar = true; - } - break; - case HIDE: - print("Hide"); - if (Recording.isPlaying()) { - Recording.stopPlaying(); - } - Agent.isAvatar = false; - break; case LOAD: - print("Load"); - if(clip_url !== null) { - Recording.loadRecording(clip_url); - } - break; - case DO_NOTHING: + { + print("Load" + command.argument_key); + print("Agent #" + command.dest_key + " loading clip URL: " + command.argument_key); + Recording.loadRecording(command.argument_key); + print("After Load" + command.argument_key); + Recording.setPlayerTime(0); + } break; default: - print("Unknown action: " + action); + print("Unknown action: " + command.action_key); break; } - - if (Recording.isPlaying()) { - Recording.play(); - } } } +function agentHired() { + print("Agent Hired from playbackAgents.js"); + Agent.isAvatar = true; + Recording.stopPlaying(); + Recording.setPlayerLoop(false); + Recording.setPlayerTime(0); +} + +function agentFired() { + print("Agent Fired from playbackAgents.js"); + Recording.stopPlaying(); + Recording.setPlayerTime(0); + Recording.setPlayerLoop(false); + Agent.isAvatar = false; +} + function update(deltaTime) { - totalTime += deltaTime; - if (totalTime > WAIT_FOR_AUDIO_MIXER) { - if (!subscribed) { - Messages.subscribe(commandChannel); // command channel - Messages.subscribe(announceIDChannel); // id announce channel - subscribed = true; - print("I'm the agent and I am ready to receive!"); - } - if (subscribed && id == UNKNOWN_AGENT_ID) { - print("sending ready, id:" + id); - Messages.sendMessage(announceIDChannel, "ready"); + if (!agentController.subscribed) { + agentController.reset(); + agentController.onCommand = agentCommand; + agentController.onHired = agentHired; + agentController.onFired = agentFired; } } + agentController.update(deltaTime); +} + + +function scriptEnding() { + + agentController.destroy(); } -Messages.messageReceived.connect(function (channel, message, senderID) { - if (channel == announceIDChannel && message != "ready") { - // If I don't yet know if my ID has been recieved, then check to see if the master has acknowledged me - if (id == UNKNOWN_AGENT_ID) { - var parts = message.split("."); - var agentID = parts[0]; - var agentIndex = parts[1]; - if (agentID == Agent.sessionUUID) { - id = agentIndex; - Messages.unsubscribe(announceIDChannel); // id announce channel - } - } - } - if (channel == commandChannel) { - getAction(channel, message, senderID); - } -}); Script.update.connect(update); +Script.scriptEnding.connect(scriptEnding); diff --git a/examples/acScripts/playbackMaster.js b/examples/acScripts/playbackMaster.js index 4703f0e4fd..dff0b1d852 100644 --- a/examples/acScripts/playbackMaster.js +++ b/examples/acScripts/playbackMaster.js @@ -8,31 +8,24 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +Script.include("./AgentPoolController.js"); HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; +var masterController = new MasterController(); -var ac_number = 1; // This is the default number of ACs. Their ID need to be unique and between 0 (included) and ac_number (excluded) -var names = new Array(); // It is possible to specify the name of the ACs in this array. ACs names ordered by IDs (Default name is "ACx", x = ID + 1)) -var channel = "com.highfidelity.PlaybackChannel1"; -var subscribed = false; -var clip_url = null; var input_text = null; -var knownAgents = new Array; // We will add our known agents here when we discover them - -// available playbackAgents will announce their sessionID here. -var announceIDChannel = "com.highfidelity.playbackAgent.announceID"; - // Script. DO NOT MODIFY BEYOND THIS LINE. -Script.include("../libraries/toolBars.js"); +//Script.include("../libraries/toolBars.js"); +Script.include(HIFI_PUBLIC_BUCKET + "scripts/libraries/toolBars.js"); +// We want small icons +Tool.IMAGE_HEIGHT /= 2; +Tool.IMAGE_WIDTH /= 2; -var DO_NOTHING = 0; var PLAY = 1; var PLAY_LOOP = 2; var STOP = 3; -var SHOW = 4; -var HIDE = 5; var LOAD = 6; var windowDimensions = Controller.getViewportDimensions(); @@ -44,241 +37,381 @@ var COLOR_MASTER = { red: 0, green: 0, blue: 0 }; var TEXT_HEIGHT = 12; var TEXT_MARGIN = 3; -var toolBars = new Array(); -var nameOverlays = new Array(); -var onOffIcon = new Array(); -var playIcon = new Array(); -var playLoopIcon = new Array(); -var stopIcon = new Array(); -var loadIcon = new Array(); - -setupPlayback(); - -function setupPlayback() { - ac_number = Window.prompt("Insert number of agents: ","1"); - if (ac_number === "" || ac_number === null) { - ac_number = 1; - } - Messages.subscribe(channel); - subscribed = true; - setupToolBars(); +// Add new features to Actor class: +Actor.prototype.destroy = function() { + print("Actor.prototype.destroy"); + print("Need to fire myself" + this.agentID); + masterController.fireAgent(this); } -function setupToolBars() { - if (toolBars.length > 0) { - print("Multiple calls to Recorder.js:setupToolBars()"); - return; - } - Tool.IMAGE_HEIGHT /= 2; - Tool.IMAGE_WIDTH /= 2; - - for (i = 0; i <= ac_number; i++) { - toolBars.push(new ToolBar(0, 0, ToolBar.HORIZONTAL)); - toolBars[i].setBack((i == ac_number) ? COLOR_MASTER : COLOR_TOOL_BAR, ALPHA_OFF); - - onOffIcon.push(toolBars[i].addTool({ - imageURL: TOOL_ICON_URL + "ac-on-off.svg", - subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, - x: 0, y: 0, - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT, - alpha: ALPHA_ON, - visible: true - }, true, true)); - - playIcon[i] = toolBars[i].addTool({ - imageURL: TOOL_ICON_URL + "play.svg", - subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT, - alpha: ALPHA_OFF, - visible: true - }, false); - - var playLoopWidthFactor = 1.65; - playLoopIcon[i] = toolBars[i].addTool({ - imageURL: TOOL_ICON_URL + "play-and-loop.svg", - subImage: { x: 0, y: 0, width: playLoopWidthFactor * Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, - width: playLoopWidthFactor * Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT, - alpha: ALPHA_OFF, - visible: true - }, false); - - stopIcon[i] = toolBars[i].addTool({ - imageURL: TOOL_ICON_URL + "recording-stop.svg", - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT, - alpha: ALPHA_OFF, - visible: true - }, false); - - loadIcon[i] = toolBars[i].addTool({ - imageURL: TOOL_ICON_URL + "recording-upload.svg", - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT, - alpha: ALPHA_OFF, - visible: true - }, false); - - nameOverlays.push(Overlays.addOverlay("text", { - backgroundColor: { red: 0, green: 0, blue: 0 }, - font: { size: TEXT_HEIGHT }, - text: (i == ac_number) ? "Master" : i + ". " + - ((i < names.length) ? names[i] : - "AC" + i), - x: 0, y: 0, - width: toolBars[i].width + ToolBar.SPACING, - height: TEXT_HEIGHT + TEXT_MARGIN, - leftMargin: TEXT_MARGIN, - topMargin: TEXT_MARGIN, - alpha: ALPHA_OFF, - backgroundAlpha: ALPHA_OFF, - visible: true - })); + +Actor.prototype.resetClip = function(clipURL, onLoadClip) { + this.clipURL = clipURL; + this.onLoadClip = onLoadClip; + if (this.isConnected()) { + this.onLoadClip(this); } } -function sendCommand(id, action) { - if (action === SHOW) { - toolBars[id].selectTool(onOffIcon[id], false); - toolBars[id].setAlpha(ALPHA_ON, playIcon[id]); - toolBars[id].setAlpha(ALPHA_ON, playLoopIcon[id]); - toolBars[id].setAlpha(ALPHA_ON, stopIcon[id]); - toolBars[id].setAlpha(ALPHA_ON, loadIcon[id]); - } else if (action === HIDE) { - toolBars[id].selectTool(onOffIcon[id], true); - toolBars[id].setAlpha(ALPHA_OFF, playIcon[id]); - toolBars[id].setAlpha(ALPHA_OFF, playLoopIcon[id]); - toolBars[id].setAlpha(ALPHA_OFF, stopIcon[id]); - toolBars[id].setAlpha(ALPHA_OFF, loadIcon[id]); - } else if (toolBars[id].toolSelected(onOffIcon[id])) { - return; - } - - if (id == (toolBars.length - 1)) { - id = -1; // Master command becomes broadcast. - } - - var message = { - id_key: id, - action_key: action, - clip_url_key: clip_url - }; - - if(subscribed){ - Messages.sendMessage(channel, JSON.stringify(message)); - print("Message sent!"); - clip_url = null; + +Actor.prototype.onMousePressEvent = function(clickedOverlay) { + if (this.playIcon === this.toolbar.clicked(clickedOverlay, false)) { + masterController.sendCommand(this.agentID, PLAY); + } else if (this.playLoopIcon === this.toolbar.clicked(clickedOverlay, false)) { + masterController.sendCommand(this.agentID, PLAY_LOOP); + } else if (this.stopIcon === this.toolbar.clicked(clickedOverlay, false)) { + masterController.sendCommand(this.agentID, STOP); + } else if (this.nameOverlay === clickedOverlay) { + print("Actor: " + JSON.stringify(this)); + } else { + return false; } + + return true; } -function mousePressEvent(event) { - clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); +Actor.prototype._buildUI = function() { + print("Actor.prototype._buildUI = " + JSON.stringify(this)); - // Check master control - var i = toolBars.length - 1; - if (onOffIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { - if (toolBars[i].toolSelected(onOffIcon[i])) { - sendCommand(i, SHOW); - } else { - sendCommand(i, HIDE); - } - } else if (playIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { - sendCommand(i, PLAY); - } else if (playLoopIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { - sendCommand(i, PLAY_LOOP); - } else if (stopIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { - sendCommand(i, STOP); - } else if (loadIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { + this.toolbar = new ToolBar(0, 0, ToolBar.HORIZONTAL); + + this.toolbar.setBack(COLOR_TOOL_BAR, ALPHA_OFF); + + this.playIcon = this.toolbar.addTool({ + imageURL: TOOL_ICON_URL + "play.svg", + subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: Tool.IMAGE_WIDTH, + height: Tool.IMAGE_HEIGHT, + alpha: ALPHA_OFF, + visible: true + }, false); + + var playLoopWidthFthis = 1.65; + this.playLoopIcon = this.toolbar.addTool({ + imageURL: TOOL_ICON_URL + "play-and-loop.svg", + subImage: { x: 0, y: 0, width: playLoopWidthFthis * Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: playLoopWidthFthis * Tool.IMAGE_WIDTH, + height: Tool.IMAGE_HEIGHT, + alpha: ALPHA_OFF, + visible: true + }, false); + + this.stopIcon = this.toolbar.addTool({ + imageURL: TOOL_ICON_URL + "recording-stop.svg", + width: Tool.IMAGE_WIDTH, + height: Tool.IMAGE_HEIGHT, + alpha: ALPHA_OFF, + visible: true + }, false); + + this.nameOverlay = Overlays.addOverlay("text", { + backgroundColor: { red: 0, green: 0, blue: 0 }, + font: { size: TEXT_HEIGHT }, + text: "AC offline", + x: 0, y: 0, + width: this.toolbar.width + ToolBar.SPACING, + height: TEXT_HEIGHT + TEXT_MARGIN, + leftMargin: TEXT_MARGIN, + topMargin: TEXT_MARGIN, + alpha: ALPHA_OFF, + backgroundAlpha: ALPHA_OFF, + visible: true + }); +} + +Actor.prototype._destroyUI = function() { + this.toolbar.cleanup(); + Overlays.deleteOverlay(this.nameOverlay); +} + +Actor.prototype.moveUI = function(pos) { + var textSize = TEXT_HEIGHT + 2 * TEXT_MARGIN; + this.toolbar.move(pos.x, pos.y); + + Overlays.editOverlay(this.nameOverlay, { + x: this.toolbar.x - ToolBar.SPACING, + y: this.toolbar.y - textSize + }); +} + +Director = function() { + this.actors = new Array(); + this.toolbar = null; + this._buildUI(); + this.requestPerformanceLoad = false; + this.performanceURL = ""; +}; + +Director.prototype.destroy = function () { + print("Director.prototype.destroy") + this.clearActors(); + this.toolbar.cleanup(); + Overlays.deleteOverlay(this.nameOverlay); +} + +Director.prototype.clearActors = function () { + print("Director.prototype.clearActors") + while (this.actors.length > 0) { + this.actors.pop().destroy(); + } + + this.actors = new Array();// Brand new actors +} + +Director.prototype._buildUI = function () { + this.toolbar = new ToolBar(0, 0, ToolBar.HORIZONTAL); + + this.toolbar.setBack(COLOR_MASTER, ALPHA_OFF); + + this.onOffIcon = this.toolbar.addTool({ + imageURL: TOOL_ICON_URL + "ac-on-off.svg", + subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + x: 0, y: 0, + width: Tool.IMAGE_WIDTH, + height: Tool.IMAGE_HEIGHT, + alpha: ALPHA_ON, + visible: true + }, true, true); + + this.playIcon = this.toolbar.addTool({ + imageURL: TOOL_ICON_URL + "play.svg", + subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: Tool.IMAGE_WIDTH, + height: Tool.IMAGE_HEIGHT, + alpha: ALPHA_OFF, + visible: true + }, false); + + var playLoopWidthFthis = 1.65; + this.playLoopIcon = this.toolbar.addTool({ + imageURL: TOOL_ICON_URL + "play-and-loop.svg", + subImage: { x: 0, y: 0, width: playLoopWidthFthis * Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: playLoopWidthFthis * Tool.IMAGE_WIDTH, + height: Tool.IMAGE_HEIGHT, + alpha: ALPHA_OFF, + visible: true + }, false); + + this.stopIcon = this.toolbar.addTool({ + imageURL: TOOL_ICON_URL + "recording-stop.svg", + width: Tool.IMAGE_WIDTH, + height: Tool.IMAGE_HEIGHT, + alpha: ALPHA_OFF, + visible: true + }, false); + + this.loadIcon = this.toolbar.addTool({ + imageURL: TOOL_ICON_URL + "recording-upload.svg", + width: Tool.IMAGE_WIDTH, + height: Tool.IMAGE_HEIGHT, + alpha: ALPHA_OFF, + visible: true + }, false); + + this.nameOverlay = Overlays.addOverlay("text", { + backgroundColor: { red: 0, green: 0, blue: 0 }, + font: { size: TEXT_HEIGHT }, + text: "Master", + x: 0, y: 0, + width: this.toolbar.width + ToolBar.SPACING, + height: TEXT_HEIGHT + TEXT_MARGIN, + leftMargin: TEXT_MARGIN, + topMargin: TEXT_MARGIN, + alpha: ALPHA_OFF, + backgroundAlpha: ALPHA_OFF, + visible: true + }); +} + +Director.prototype.onMousePressEvent = function(clickedOverlay) { + if (this.playIcon === this.toolbar.clicked(clickedOverlay, false)) { + print("master play"); + masterController.sendCommand(BROADCAST_AGENTS, PLAY); + } else if (this.onOffIcon === this.toolbar.clicked(clickedOverlay, false)) { + this.clearActors(); + return true; + } else if (this.playLoopIcon === this.toolbar.clicked(clickedOverlay, false)) { + masterController.sendCommand(BROADCAST_AGENTS, PLAY_LOOP); + } else if (this.stopIcon === this.toolbar.clicked(clickedOverlay, false)) { + masterController.sendCommand(BROADCAST_AGENTS, STOP); + } else if (this.loadIcon === this.toolbar.clicked(clickedOverlay, false)) { input_text = Window.prompt("Insert the url of the clip: ",""); if (!(input_text === "" || input_text === null)) { - clip_url = input_text; - sendCommand(i, LOAD); - } + print("Performance file ready to be loaded url = " + input_text); + this.requestPerformanceLoad = true; + this.performanceURL = input_text; + } + } else if (this.nameOverlay === clickedOverlay) { + print("Director: " + JSON.stringify(this)); } else { // Check individual controls - for (i = 0; i < ac_number; i++) { - if (onOffIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { - if (toolBars[i].toolSelected(onOffIcon[i], false)) { - sendCommand(i, SHOW); - } else { - sendCommand(i, HIDE); - } - } else if (playIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { - sendCommand(i, PLAY); - } else if (playLoopIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { - sendCommand(i, PLAY_LOOP); - } else if (stopIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { - sendCommand(i, STOP); - } else if (loadIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { - input_text = Window.prompt("Insert the url of the clip: ",""); - if (!(input_text === "" || input_text === null)) { - clip_url = input_text; - sendCommand(i, LOAD); - } - } else { - + for (var i = 0; i < this.actors.length; i++) { + if (this.actors[i].onMousePressEvent(clickedOverlay)) { + return true; } } + + return false; // nothing clicked from our known overlays } + + return true; +} + +Director.prototype.moveUI = function(pos) { + var textSize = TEXT_HEIGHT + 2 * TEXT_MARGIN; + var relative = { x: pos.x, y: pos.y + (this.actors.length + 1) * (Tool.IMAGE_HEIGHT + ToolBar.SPACING + textSize) }; + + this.toolbar.move(relative.x, windowDimensions.y - relative.y); + Overlays.editOverlay(this.nameOverlay, { + x: this.toolbar.x - ToolBar.SPACING, + y: this.toolbar.y - textSize + }); + + for (var i = 0; i < this.actors.length; i++) { + this.actors[i].moveUI({x: relative.x, y: windowDimensions.y - relative.y + + (i + 1) * (Tool.IMAGE_HEIGHT + ToolBar.SPACING + textSize)}); + } +} + + +Director.prototype.reloadPerformance = function() { + this.requestPerformanceLoad = false; + + if (this.performanceURL[0] == '{') { + var jsonPerformance = JSON.parse(this.performanceURL); + this.onPerformanceLoaded(jsonPerformance); + } else { + + var urlpartition = this.performanceURL.split("."); + print(urlpartition[0]); + print(urlpartition[1]); + + if ((urlpartition.length > 1) && (urlpartition[urlpartition.length - 1] === "hfr")) { + print("detected a unique clip url"); + var oneClipPerformance = new Object(); + oneClipPerformance.avatarClips = new Array(); + oneClipPerformance.avatarClips[0] = input_text; + + print(JSON.stringify(oneClipPerformance)); + + // we make a local simple performance file with a single clip and pipe in directly + this.onPerformanceLoaded(oneClipPerformance); + return true; + } else { + // FIXME: I cannot pass directly this.onPerformanceLoaded, is that exepected ? + var localThis = this; + Assets.downloadData(input_text, function(data) { localThis.onPerformanceLoaded(JSON.parse(data)); }); + } + } +} + +Director.prototype.onPerformanceLoaded = function(performanceJSON) { + // First fire all the current actors + this.clearActors(); + + print("Director.prototype.onPerformanceLoaded = " + JSON.stringify(performanceJSON)); + if (performanceJSON.avatarClips != null) { + var numClips = performanceJSON.avatarClips.length; + print("Found " + numClips + "in the performance file, and currently using " + this.actors.length + " actor(s)"); + + for (var i = 0; i < numClips; i++) { + this.hireActor(performanceJSON.avatarClips[i]); + } + + } +} + + +Director.prototype.hireActor = function(clipURL) { + print("new actor = " + this.actors.length ); + var newActor = new Actor(); + newActor.clipURL = null; + newActor.onLoadClip = function(clip) {}; + + var localThis = this; + newActor.onHired = function(actor) { + print("agent hired from Director! " + actor.agentID) + Overlays.editOverlay(actor.nameOverlay, { + text: "AC " + actor.agentID, + backgroundColor: { red: 0, green: 255, blue: 0 } + }); + + if (actor.clipURL != null) { + print("agent hired, calling load clip for url " + actor.clipURL); + actor.onLoadClip(actor); + } + }; + + newActor.onFired = function(actor) { + print("agent fired from playbackMaster! " + actor.agentID); + var index = localThis.actors.indexOf(actor); + if (index >= 0) { + localThis.actors.splice(index, 1); + } + + actor._destroyUI(); + + actor.destroy(); + moveUI(); + } + + newActor.resetClip(clipURL, function(actor) { + print("Load clip for agent" + actor.agentID + " calling load clip for url " + actor.clipURL); + masterController.sendCommand(actor.agentID, LOAD, actor.clipURL); + }); + + + masterController.hireAgent(newActor); + newActor._buildUI(); + + this.actors.push(newActor); + + moveUI(); +} + + +masterController.reset(); +var director = new Director(); + +moveUI(); + + +function mousePressEvent(event) { + print("mousePressEvent"); + clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); + + // Check director and actors + director.onMousePressEvent(clickedOverlay); } function moveUI() { - var textSize = TEXT_HEIGHT + 2 * TEXT_MARGIN; - var relative = { x: 70, y: 75 + (ac_number) * (Tool.IMAGE_HEIGHT + ToolBar.SPACING + textSize) }; - - for (i = 0; i <= ac_number; i++) { - toolBars[i].move(relative.x, - windowDimensions.y - relative.y + - i * (Tool.IMAGE_HEIGHT + ToolBar.SPACING + textSize)); - - Overlays.editOverlay(nameOverlays[i], { - x: toolBars[i].x - ToolBar.SPACING, - y: toolBars[i].y - textSize - }); - } + director.moveUI({ x: 70, y: 75}); + } -function update() { +function update(deltaTime) { var newDimensions = Controller.getViewportDimensions(); if (windowDimensions.x != newDimensions.x || windowDimensions.y != newDimensions.y) { windowDimensions = newDimensions; moveUI(); } + + if (director.requestPerformanceLoad) { + print("reloadPerformance " + director.performanceURL); + director.reloadPerformance(); + } + + masterController.update(deltaTime); } function scriptEnding() { - for (i = 0; i <= ac_number; i++) { - toolBars[i].cleanup(); - Overlays.deleteOverlay(nameOverlays[i]); - } - - if (subscribed) { - Messages.unsubscribe(channel); - } - Messages.unsubscribe(announceIDChannel); + print("cleanup") + director.destroy(); + masterController.destroy(); } Controller.mousePressEvent.connect(mousePressEvent); Script.update.connect(update); Script.scriptEnding.connect(scriptEnding); - - -Messages.subscribe(announceIDChannel); -Messages.messageReceived.connect(function (channel, message, senderID) { - if (channel == announceIDChannel && message == "ready") { - // check to see if we know about this agent - if (knownAgents.indexOf(senderID) < 0) { - var indexOfNewAgent = knownAgents.length; - knownAgents[indexOfNewAgent] = senderID; - var acknowledgeMessage = senderID + "." + indexOfNewAgent; - Messages.sendMessage(announceIDChannel, acknowledgeMessage); - } - - } -}); - -moveUI(); \ No newline at end of file