ESLINT, style and spelling fixes

This commit is contained in:
Thijs Wenker 2017-04-26 18:51:55 +02:00
parent c7ab4994ea
commit a25584c90f

View file

@ -1,6 +1,6 @@
"use strict"; "use strict";
// //
// makeUserConnetion.js // makeUserConnection.js
// scripts/system // scripts/system
// //
// Created by David Kelly on 3/7/2017. // Created by David Kelly on 3/7/2017.
@ -11,31 +11,31 @@
// //
(function() { // BEGIN LOCAL_SCOPE (function() { // BEGIN LOCAL_SCOPE
const label = "makeUserConnection"; var LABEL = "makeUserConnection";
const MAX_AVATAR_DISTANCE = 0.2; // m var MAX_AVATAR_DISTANCE = 0.2; // m
const GRIP_MIN = 0.05; // goes from 0-1, so 5% pressed is pressed var GRIP_MIN = 0.05; // goes from 0-1, so 5% pressed is pressed
const MESSAGE_CHANNEL = "io.highfidelity.makeUserConnection"; var MESSAGE_CHANNEL = "io.highfidelity.makeUserConnection";
const STATES = { var STATES = {
inactive : 0, INACTIVE: 0,
waiting: 1, WAITING: 1,
connecting: 2, CONNECTING: 2,
makingConnection: 3 MAKING_CONNECTION: 3
}; };
const STATE_STRINGS = ["inactive", "waiting", "connecting", "makingConnection"]; var STATE_STRINGS = ["inactive", "waiting", "connecting", "makingConnection"];
const WAITING_INTERVAL = 100; // ms var WAITING_INTERVAL = 100; // ms
const CONNECTING_INTERVAL = 100; // ms var CONNECTING_INTERVAL = 100; // ms
const MAKING_CONNECTION_TIMEOUT = 800; // ms var MAKING_CONNECTION_TIMEOUT = 800; // ms
const CONNECTING_TIME = 1600; // ms var CONNECTING_TIME = 1600; // ms
const PARTICLE_RADIUS = 0.15; // m var PARTICLE_RADIUS = 0.15; // m
const PARTICLE_ANGLE_INCREMENT = 360/45; // 1hz var PARTICLE_ANGLE_INCREMENT = 360/45; // 1hz
const HANDSHAKE_SOUND_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/davidkelly/production/audio/4beat_sweep.wav"; var HANDSHAKE_SOUND_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/davidkelly/production/audio/4beat_sweep.wav";
const SUCCESSFUL_HANDSHAKE_SOUND_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/davidkelly/production/audio/3rdbeat_success_bell.wav"; var SUCCESSFUL_HANDSHAKE_SOUND_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/davidkelly/production/audio/3rdbeat_success_bell.wav";
const HAPTIC_DATA = { var HAPTIC_DATA = {
initial: { duration: 20, strength: 0.6}, // duration is in ms initial: { duration: 20, strength: 0.6 }, // duration is in ms
background: { duration: 100, strength: 0.3 }, // duration is in ms background: { duration: 100, strength: 0.3 }, // duration is in ms
success: { duration: 60, strength: 1.0} // duration is in ms success: { duration: 60, strength: 1.0 } // duration is in ms
}; };
const PARTICLE_EFFECT_PROPS = { var PARTICLE_EFFECT_PROPS = {
"alpha": 0.8, "alpha": 0.8,
"azimuthFinish": Math.PI, "azimuthFinish": Math.PI,
"azimuthStart": -1*Math.PI, "azimuthStart": -1*Math.PI,
@ -60,10 +60,10 @@ const PARTICLE_EFFECT_PROPS = {
"accelerationSpread": {"x": 0.0, "y": 0.0, "z": 0.0}, "accelerationSpread": {"x": 0.0, "y": 0.0, "z": 0.0},
"dimensions": {"x":0.05, "y": 0.05, "z": 0.05}, "dimensions": {"x":0.05, "y": 0.05, "z": 0.05},
"type": "ParticleEffect" "type": "ParticleEffect"
}; };
const MAKING_CONNECTION_PARTICLE_PROPS = { var MAKING_CONNECTION_PARTICLE_PROPS = {
"alpha": 0.07, "alpha": 0.07,
"alphaStart":0.011, "alphaStart": 0.011,
"alphaSpread": 0, "alphaSpread": 0,
"alphaFinish": 0, "alphaFinish": 0,
"azimuthFinish": Math.PI, "azimuthFinish": Math.PI,
@ -90,38 +90,38 @@ const MAKING_CONNECTION_PARTICLE_PROPS = {
"accelerationSpread": {"x": 0.0, "y": 0.0, "z": 0.0}, "accelerationSpread": {"x": 0.0, "y": 0.0, "z": 0.0},
"dimensions": {"x":0.05, "y": 0.05, "z": 0.05}, "dimensions": {"x":0.05, "y": 0.05, "z": 0.05},
"type": "ParticleEffect" "type": "ParticleEffect"
}; };
var currentHand; var currentHand;
var state = STATES.inactive; var state = STATES.INACTIVE;
var connectingInterval; var connectingInterval;
var waitingInterval; var waitingInterval;
var makingConnectionTimeout; var makingConnectionTimeout;
var animHandlerId; var animHandlerId;
var connectingId; var connectingId;
var connectingHand; var connectingHand;
var waitingList = {}; var waitingList = {};
var particleEffect; var particleEffect;
var waitingBallScale; var particleRotationAngle = 0.0;
var particleRotationAngle = 0.0; var makingConnectionParticleEffect;
var makingConnectionParticleEffect; var makingConnectionEmitRate = 2000;
var makingConnectionEmitRate = 2000; var particleEmitRate = 500;
var particleEmitRate = 500; var handshakeInjector;
var handshakeInjector; var successfulHandshakeInjector;
var successfulHandshakeInjector; var handshakeSound;
var handshakeSound; var successfulHandshakeSound;
var successfulHandshakeSound;
function debug() { function debug() {
var stateString = "<" + STATE_STRINGS[state] + ">"; var stateString = "<" + STATE_STRINGS[state] + ">";
var connecting = "[" + connectingId + "/" + connectingHand + "]"; var connecting = "[" + connectingId + "/" + connectingHand + "]";
print.apply(null, [].concat.apply([label, stateString, JSON.stringify(waitingList), connecting], [].map.call(arguments, JSON.stringify))); print.apply(null, [].concat.apply([LABEL, stateString, JSON.stringify(waitingList), connecting],
} [].map.call(arguments, JSON.stringify)));
}
function cleanId(guidWithCurlyBraces) { function cleanId(guidWithCurlyBraces) {
return guidWithCurlyBraces.slice(1, -1); return guidWithCurlyBraces.slice(1, -1);
} }
function request(options, callback) { // cb(error, responseOfCorrectContentType) of url. A subset of npm request. function request(options, callback) { // cb(error, responseOfCorrectContentType) of url. A subset of npm request.
var httpRequest = new XMLHttpRequest(), key; var httpRequest = new XMLHttpRequest(), key;
// QT bug: apparently doesn't handle onload. Workaround using readyState. // QT bug: apparently doesn't handle onload. Workaround using readyState.
httpRequest.onreadystatechange = function () { httpRequest.onreadystatechange = function () {
@ -156,8 +156,10 @@ function request(options, callback) { // cb(error, responseOfCorrectContentType)
if (options.body && (options.method === 'GET')) { // add query parameters if (options.body && (options.method === 'GET')) { // add query parameters
var params = [], appender = (-1 === options.uri.search('?')) ? '?' : '&'; var params = [], appender = (-1 === options.uri.search('?')) ? '?' : '&';
for (key in options.body) { for (key in options.body) {
if (options.body.hasOwnProperty(key)) {
params.push(key + '=' + options.body[key]); params.push(key + '=' + options.body[key]);
} }
}
options.uri += appender + params.join('&'); options.uri += appender + params.join('&');
delete options.body; delete options.body;
} }
@ -167,13 +169,15 @@ function request(options, callback) { // cb(error, responseOfCorrectContentType)
options.body = JSON.stringify(options.body); options.body = JSON.stringify(options.body);
} }
for (key in options.headers || {}) { for (key in options.headers || {}) {
if (options.headers.hasOwnProperty(key)) {
httpRequest.setRequestHeader(key, options.headers[key]); httpRequest.setRequestHeader(key, options.headers[key]);
} }
}
httpRequest.open(options.method, options.uri, true); httpRequest.open(options.method, options.uri, true);
httpRequest.send(options.body); httpRequest.send(options.body);
} }
function handToString(hand) { function handToString(hand) {
if (hand === Controller.Standard.RightHand) { if (hand === Controller.Standard.RightHand) {
return "RightHand"; return "RightHand";
} else if (hand === Controller.Standard.LeftHand) { } else if (hand === Controller.Standard.LeftHand) {
@ -181,19 +185,19 @@ function handToString(hand) {
} }
debug("handToString called without valid hand!"); debug("handToString called without valid hand!");
return ""; return "";
} }
function stringToHand(hand) { function stringToHand(hand) {
if (hand == "RightHand") { if (hand === "RightHand") {
return Controller.Standard.RightHand; return Controller.Standard.RightHand;
} else if (hand == "LeftHand") { } else if (hand === "LeftHand") {
return Controller.Standard.LeftHand; return Controller.Standard.LeftHand;
} }
debug("stringToHand called with bad hand string:", hand); debug("stringToHand called with bad hand string:", hand);
return 0; return 0;
} }
function handToHaptic(hand) { function handToHaptic(hand) {
if (hand === Controller.Standard.RightHand) { if (hand === Controller.Standard.RightHand) {
return 1; return 1;
} else if (hand === Controller.Standard.LeftHand) { } else if (hand === Controller.Standard.LeftHand) {
@ -201,30 +205,30 @@ function handToHaptic(hand) {
} }
debug("handToHaptic called without a valid hand!"); debug("handToHaptic called without a valid hand!");
return -1; return -1;
} }
function stopWaiting() { function stopWaiting() {
if (waitingInterval) { if (waitingInterval) {
waitingInterval = Script.clearInterval(waitingInterval); waitingInterval = Script.clearInterval(waitingInterval);
} }
} }
function stopConnecting() { function stopConnecting() {
if (connectingInterval) { if (connectingInterval) {
connectingInterval = Script.clearInterval(connectingInterval); connectingInterval = Script.clearInterval(connectingInterval);
} }
} }
function stopMakingConnection() { function stopMakingConnection() {
if (makingConnectionTimeout) { if (makingConnectionTimeout) {
makingConnectionTimeout = Script.clearTimeout(makingConnectionTimeout); makingConnectionTimeout = Script.clearTimeout(makingConnectionTimeout);
} }
} }
// This returns the position of the palm, really. Which relies on the avatar // This returns the position of the palm, really. Which relies on the avatar
// having the expected middle1 joint. TODO: fallback for when this isn't part // having the expected middle1 joint. TODO: fallback for when this isn't part
// of the avatar? // of the avatar?
function getHandPosition(avatar, hand) { function getHandPosition(avatar, hand) {
if (!hand) { if (!hand) {
debug("calling getHandPosition with no hand! (returning avatar position but this is a BUG)"); debug("calling getHandPosition with no hand! (returning avatar position but this is a BUG)");
debug(new Error().stack); debug(new Error().stack);
@ -232,9 +236,9 @@ function getHandPosition(avatar, hand) {
} }
var jointName = handToString(hand) + "Middle1"; var jointName = handToString(hand) + "Middle1";
return avatar.getJointPosition(avatar.getJointIndex(jointName)); return avatar.getJointPosition(avatar.getJointIndex(jointName));
} }
function shakeHandsAnimation(animationProperties) { function shakeHandsAnimation(animationProperties) {
// all we are doing here is moving the right hand to a spot // all we are doing here is moving the right hand to a spot
// that is in front of and a bit above the hips. Basing how // that is in front of and a bit above the hips. Basing how
// far in front as scaling with the avatar's height (say hips // far in front as scaling with the avatar's height (say hips
@ -245,36 +249,35 @@ function shakeHandsAnimation(animationProperties) {
if (headIndex) { if (headIndex) {
offset = 0.8 * MyAvatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y; offset = 0.8 * MyAvatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y;
} }
var handPos = Vec3.multiply(offset, {x: -0.25, y: 0.8, z: 1.3}); result.rightHandPosition = Vec3.multiply(offset, {x: -0.25, y: 0.8, z: 1.3});
result.rightHandPosition = handPos;
result.rightHandRotation = Quat.fromPitchYawRollDegrees(90, 0, 90); result.rightHandRotation = Quat.fromPitchYawRollDegrees(90, 0, 90);
return result; return result;
} }
function positionFractionallyTowards(posA, posB, frac) { function positionFractionallyTowards(posA, posB, frac) {
return Vec3.sum(posA, Vec3.multiply(frac, Vec3.subtract(posB, posA))); return Vec3.sum(posA, Vec3.multiply(frac, Vec3.subtract(posB, posA)));
} }
function deleteParticleEffect() { function deleteParticleEffect() {
if (particleEffect) { if (particleEffect) {
particleEffect = Entities.deleteEntity(particleEffect); particleEffect = Entities.deleteEntity(particleEffect);
} }
} }
function deleteMakeConnectionParticleEffect() { function deleteMakeConnectionParticleEffect() {
if (makingConnectionParticleEffect) { if (makingConnectionParticleEffect) {
makingConnectionParticleEffect = Entities.deleteEntity(makingConnectionParticleEffect); makingConnectionParticleEffect = Entities.deleteEntity(makingConnectionParticleEffect);
} }
} }
function stopHandshakeSound() { function stopHandshakeSound() {
if (handshakeInjector) { if (handshakeInjector) {
handshakeInjector.stop(); handshakeInjector.stop();
handshakeInjector = null; handshakeInjector = null;
} }
} }
function calcParticlePos(myHand, otherHand, otherOrientation, reset) { function calcParticlePos(myHand, otherHand, otherOrientation, reset) {
if (reset) { if (reset) {
particleRotationAngle = 0.0; particleRotationAngle = 0.0;
} }
@ -283,11 +286,11 @@ function calcParticlePos(myHand, otherHand, otherOrientation, reset) {
var radius = Math.min(PARTICLE_RADIUS, PARTICLE_RADIUS * particleRotationAngle / 360); var radius = Math.min(PARTICLE_RADIUS, PARTICLE_RADIUS * particleRotationAngle / 360);
var axis = Vec3.mix(Quat.getFront(MyAvatar.orientation), Quat.inverse(Quat.getFront(otherOrientation)), 0.5); var axis = Vec3.mix(Quat.getFront(MyAvatar.orientation), Quat.inverse(Quat.getFront(otherOrientation)), 0.5);
return Vec3.sum(position, Vec3.multiplyQbyV(Quat.angleAxis(particleRotationAngle, axis), {x: 0, y: radius, z: 0})); return Vec3.sum(position, Vec3.multiplyQbyV(Quat.angleAxis(particleRotationAngle, axis), {x: 0, y: radius, z: 0}));
} }
// this is called frequently, but usually does nothing // this is called frequently, but usually does nothing
function updateVisualization() { function updateVisualization() {
if (state == STATES.inactive) { if (state === STATES.INACTIVE) {
deleteParticleEffect(); deleteParticleEffect();
deleteMakeConnectionParticleEffect(); deleteMakeConnectionParticleEffect();
// this should always be true if inactive, but just in case: // this should always be true if inactive, but just in case:
@ -306,16 +309,14 @@ function updateVisualization() {
} }
} }
var wrist = MyAvatar.getJointPosition(MyAvatar.getJointIndex(handToString(currentHand)));
var d = Math.min(MAX_AVATAR_DISTANCE, Vec3.distance(wrist, myHandPosition));
switch (state) { switch (state) {
case STATES.waiting: case STATES.WAITING:
// no visualization while waiting // no visualization while waiting
deleteParticleEffect(); deleteParticleEffect();
deleteMakeConnectionParticleEffect(); deleteMakeConnectionParticleEffect();
stopHandshakeSound(); stopHandshakeSound();
break; break;
case STATES.connecting: case STATES.CONNECTING:
var particleProps = {}; var particleProps = {};
// put the position between the 2 hands, if we have a connectingId. This // put the position between the 2 hands, if we have a connectingId. This
// helps define the plane in which the particles move. // helps define the plane in which the particles move.
@ -343,21 +344,28 @@ function updateVisualization() {
makingConnectionParticleEffect = Entities.addEntity(props, true); makingConnectionParticleEffect = Entities.addEntity(props, true);
} else { } else {
makingConnectionEmitRate *= 0.5; makingConnectionEmitRate *= 0.5;
Entities.editEntity(makingConnectionParticleEffect, {emitRate: makingConnectionEmitRate, position: myHandPosition, isEmitting: 1}); Entities.editEntity(makingConnectionParticleEffect, {
emitRate: makingConnectionEmitRate,
position: myHandPosition,
isEmitting: true
});
} }
break; break;
case STATES.makingConnection: case STATES.MAKING_CONNECTION:
particleEmitRate = Math.max(50, particleEmitRate * 0.5); particleEmitRate = Math.max(50, particleEmitRate * 0.5);
Entities.editEntity(makingConnectionParticleEffect, {emitRate: 0, isEmitting: 0, position: myHandPosition}); Entities.editEntity(makingConnectionParticleEffect, {emitRate: 0, isEmitting: 0, position: myHandPosition});
Entities.editEntity(particleEffect, {position: calcParticlePos(myHandPosition, otherHand, otherOrientation), emitRate: particleEmitRate}); Entities.editEntity(particleEffect, {
position: calcParticlePos(myHandPosition, otherHand, otherOrientation),
emitRate: particleEmitRate
});
break; break;
default: default:
debug("unexpected state", state); debug("unexpected state", state);
break; break;
} }
} }
function isNearby(id, hand) { function isNearby(id, hand) {
if (currentHand) { if (currentHand) {
var handPos = getHandPosition(MyAvatar, currentHand); var handPos = getHandPosition(MyAvatar, currentHand);
var avatar = AvatarList.getAvatar(id); var avatar = AvatarList.getAvatar(id);
@ -368,9 +376,9 @@ function isNearby(id, hand) {
} }
} }
return false; return false;
} }
function findNearestWaitingAvatar() { function findNearestWaitingAvatar() {
var handPos = getHandPosition(MyAvatar, currentHand); var handPos = getHandPosition(MyAvatar, currentHand);
var minDistance = MAX_AVATAR_DISTANCE; var minDistance = MAX_AVATAR_DISTANCE;
var nearestAvatar = {}; var nearestAvatar = {};
@ -386,18 +394,18 @@ function findNearestWaitingAvatar() {
} }
}); });
return nearestAvatar; return nearestAvatar;
} }
// As currently implemented, we select the closest waiting avatar (if close enough) and send // As currently implemented, we select the closest waiting avatar (if close enough) and send
// them a connectionRequest. If nobody is close enough we send a waiting message, and wait for a // them a connectionRequest. If nobody is close enough we send a waiting message, and wait for a
// connectionRequest. If the 2 people who want to connect are both somewhat out of range when they // connectionRequest. If the 2 people who want to connect are both somewhat out of range when they
// initiate the shake, they will race to see who sends the connectionRequest after noticing the // initiate the shake, they will race to see who sends the connectionRequest after noticing the
// waiting message. Either way, they will start connecting eachother at that point. // waiting message. Either way, they will start connecting each other at that point.
function startHandshake(fromKeyboard) { function startHandshake(fromKeyboard) {
if (fromKeyboard) { if (fromKeyboard) {
debug("adding animation"); debug("adding animation");
// just in case order of press/unpress is broken // just in case order of press/release is broken
if (animHandlerId) { if (animHandlerId) {
animHandlerId = MyAvatar.removeAnimationStateHandler(animHandlerId); animHandlerId = MyAvatar.removeAnimationStateHandler(animHandlerId);
} }
@ -405,7 +413,7 @@ function startHandshake(fromKeyboard) {
} }
debug("starting handshake for", currentHand); debug("starting handshake for", currentHand);
pollCount = 0; pollCount = 0;
state = STATES.waiting; state = STATES.WAITING;
connectingId = undefined; connectingId = undefined;
connectingHand = undefined; connectingHand = undefined;
// just in case // just in case
@ -432,9 +440,9 @@ function startHandshake(fromKeyboard) {
}); });
lookForWaitingAvatar(); lookForWaitingAvatar();
} }
} }
function endHandshake() { function endHandshake() {
debug("ending handshake for", currentHand); debug("ending handshake for", currentHand);
deleteParticleEffect(); deleteParticleEffect();
@ -444,7 +452,7 @@ function endHandshake() {
// only be done here, unless we change how the triggering works, // only be done here, unless we change how the triggering works,
// as we ignore the key release event when inactive. See updateTriggers // as we ignore the key release event when inactive. See updateTriggers
// below. // below.
state = STATES.inactive; state = STATES.INACTIVE;
connectingId = undefined; connectingId = undefined;
connectingHand = undefined; connectingHand = undefined;
stopWaiting(); stopWaiting();
@ -460,11 +468,12 @@ function endHandshake() {
debug("removing animation"); debug("removing animation");
MyAvatar.removeAnimationStateHandler(animHandlerId); MyAvatar.removeAnimationStateHandler(animHandlerId);
} }
// No-op if we were successful, but this way we ensure that failures and abandoned handshakes don't leave us in a weird state. // No-op if we were successful, but this way we ensure that failures and abandoned handshakes don't leave us
// in a weird state.
request({uri: requestUrl, method: 'DELETE'}, debug); request({uri: requestUrl, method: 'DELETE'}, debug);
} }
function updateTriggers(value, fromKeyboard, hand) { function updateTriggers(value, fromKeyboard, hand) {
if (currentHand && hand !== currentHand) { if (currentHand && hand !== currentHand) {
debug("currentHand", currentHand, "ignoring messages from", hand); debug("currentHand", currentHand, "ignoring messages from", hand);
return; return;
@ -476,7 +485,7 @@ function updateTriggers(value, fromKeyboard, hand) {
var isGripping = value > GRIP_MIN; var isGripping = value > GRIP_MIN;
if (isGripping) { if (isGripping) {
debug("updateTriggers called - gripping", handToString(hand)); debug("updateTriggers called - gripping", handToString(hand));
if (state != STATES.inactive) { if (state !== STATES.INACTIVE) {
return; return;
} else { } else {
startHandshake(fromKeyboard); startHandshake(fromKeyboard);
@ -484,29 +493,29 @@ function updateTriggers(value, fromKeyboard, hand) {
} else { } else {
// TODO: should we end handshake even when inactive? Ponder // TODO: should we end handshake even when inactive? Ponder
debug("updateTriggers called -- no longer gripping", handToString(hand)); debug("updateTriggers called -- no longer gripping", handToString(hand));
if (state != STATES.inactive) { if (state !== STATES.INACTIVE) {
endHandshake(); endHandshake();
} else { } else {
return; return;
} }
} }
} }
function messageSend(message) { function messageSend(message) {
Messages.sendMessage(MESSAGE_CHANNEL, JSON.stringify(message)); Messages.sendMessage(MESSAGE_CHANNEL, JSON.stringify(message));
} }
function lookForWaitingAvatar() { function lookForWaitingAvatar() {
// we started with nobody close enough, but maybe I've moved // we started with nobody close enough, but maybe I've moved
// or they did. Note that 2 people doing this race, so stop // or they did. Note that 2 people doing this race, so stop
// as soon as you have a connectingId (which means you got their // as soon as you have a connectingId (which means you got their
// message before noticing they were in range in this loop) // message before noticing they were in range in this loop)
// just in case we reenter before stopping // just in case we re-enter before stopping
stopWaiting(); stopWaiting();
debug("started looking for waiting avatars"); debug("started looking for waiting avatars");
waitingInterval = Script.setInterval(function () { waitingInterval = Script.setInterval(function () {
if (state == STATES.waiting && !connectingId) { if (state === STATES.WAITING && !connectingId) {
// find the closest in-range avatar, and send connection request // find the closest in-range avatar, and send connection request
var nearestAvatar = findNearestWaitingAvatar(); var nearestAvatar = findNearestWaitingAvatar();
if (nearestAvatar.avatar) { if (nearestAvatar.avatar) {
@ -525,26 +534,33 @@ function lookForWaitingAvatar() {
debug("stopped looking for waiting avatars"); debug("stopped looking for waiting avatars");
} }
}, WAITING_INTERVAL); }, WAITING_INTERVAL);
} }
/* There is a mini-state machine after entering STATES.makingConnection. /* There is a mini-state machine after entering STATES.makingConnection.
We make a request (which might immediately succeed, fail, or neither. We make a request (which might immediately succeed, fail, or neither.
If we immediately fail, we tell the user. If we immediately fail, we tell the user.
Otherwise, we wait MAKING_CONNECTION_TIMEOUT. At that time, we poll until success or fail. Otherwise, we wait MAKING_CONNECTION_TIMEOUT. At that time, we poll until success or fail.
*/ */
var result, requestBody, pollCount = 0, requestUrl = location.metaverseServerUrl + '/api/v1/user/connection_request'; var result, requestBody, pollCount = 0, requestUrl = location.metaverseServerUrl + '/api/v1/user/connection_request';
function connectionRequestCompleted() { // Final result is in. Do effects. function connectionRequestCompleted() { // Final result is in. Do effects.
if (result.status === 'success') { // set earlier if (result.status === 'success') { // set earlier
if (!successfulHandshakeInjector) { if (!successfulHandshakeInjector) {
successfulHandshakeInjector = Audio.playSound(successfulHandshakeSound, {position: getHandPosition(MyAvatar, currentHand), volume: 0.5, localOnly: true}); successfulHandshakeInjector = Audio.playSound(successfulHandshakeSound, {
position: getHandPosition(MyAvatar, currentHand),
volume: 0.5,
localOnly: true
});
} else { } else {
successfulHandshakeInjector.restart(); successfulHandshakeInjector.restart();
} }
Controller.triggerHapticPulse(HAPTIC_DATA.success.strength, HAPTIC_DATA.success.duration, handToHaptic(currentHand)); Controller.triggerHapticPulse(HAPTIC_DATA.success.strength, HAPTIC_DATA.success.duration,
handToHaptic(currentHand));
// don't change state (so animation continues while gripped) // don't change state (so animation continues while gripped)
// but do send a notification, by calling the slot that emits the signal for it // but do send a notification, by calling the slot that emits the signal for it
Window.makeConnection(true, result.connection.new_connection ? "You and " + result.connection.username + " are now connected!" : result.connection.username); Window.makeConnection(true, result.connection.new_connection ?
UserActivityLogger.makeUserConnection(connectingId, true, result.connection.new_connection ? "new connection" : "already connected"); "You and " + result.connection.username + " are now connected!" : result.connection.username);
UserActivityLogger.makeUserConnection(connectingId, true, result.connection.new_connection ?
"new connection" : "already connected");
return; return;
} // failed } // failed
endHandshake(); endHandshake();
@ -552,12 +568,13 @@ function connectionRequestCompleted() { // Final result is in. Do effects.
// IWBNI we also did some fail sound/visual effect. // IWBNI we also did some fail sound/visual effect.
Window.makeConnection(false, result.connection); Window.makeConnection(false, result.connection);
UserActivityLogger.makeUserConnection(connectingId, false, result.connection); UserActivityLogger.makeUserConnection(connectingId, false, result.connection);
} }
var POLL_INTERVAL_MS = 200, POLL_LIMIT = 5; var POLL_INTERVAL_MS = 200, POLL_LIMIT = 5;
function handleConnectionResponseAndMaybeRepeat(error, response) { function handleConnectionResponseAndMaybeRepeat(error, response) {
// If response is 'pending', set a short timeout to try again. // If response is 'pending', set a short timeout to try again.
// If we fail other than pending, set result and immediately call connectionRequestCompleted. // If we fail other than pending, set result and immediately call connectionRequestCompleted.
// If we succceed, set result and call connectionRequestCompleted immediately (if we've been polling), and otherwise on a timeout. // If we succeed, set result and call connectionRequestCompleted immediately (if we've been polling),
// and otherwise on a timeout.
if (response && (response.connection === 'pending')) { if (response && (response.connection === 'pending')) {
debug(response, 'pollCount', pollCount); debug(response, 'pollCount', pollCount);
if (pollCount++ >= POLL_LIMIT) { // server will expire, but let's not wait that long. if (pollCount++ >= POLL_LIMIT) { // server will expire, but let's not wait that long.
@ -586,27 +603,27 @@ function handleConnectionResponseAndMaybeRepeat(error, response) {
result = response; result = response;
if (pollCount++) { if (pollCount++) {
connectionRequestCompleted(); connectionRequestCompleted();
} else { // Wait for other guy, so that final succcess is at roughly the same time. } else { // Wait for other guy, so that final success is at roughly the same time.
Script.setTimeout(connectionRequestCompleted, MAKING_CONNECTION_TIMEOUT); Script.setTimeout(connectionRequestCompleted, MAKING_CONNECTION_TIMEOUT);
} }
} }
} }
// this should be where we make the appropriate connection call. For now just make the // this should be where we make the appropriate connection call. For now just make the
// visualization change. // visualization change.
function makeConnection(id) { function makeConnection(id) {
// send done to let the connection know you have made connection. // send done to let the connection know you have made connection.
messageSend({ messageSend({
key: "done", key: "done",
connectionId: id connectionId: id
}); });
state = STATES.makingConnection; state = STATES.MAKING_CONNECTION;
// continue the haptic background until the timeout fires. When we make calls, we will have an interval // continue the haptic background until the timeout fires. When we make calls, we will have an interval
// probably, in which we do this. // probably, in which we do this.
Controller.triggerHapticPulse(HAPTIC_DATA.background.strength, MAKING_CONNECTION_TIMEOUT, handToHaptic(currentHand)); Controller.triggerHapticPulse(HAPTIC_DATA.background.strength, MAKING_CONNECTION_TIMEOUT, handToHaptic(currentHand));
requestBody = {node_id: cleanId(MyAvatar.sessionUUID), proposed_node_id: cleanId(id)}; // for use when repeating requestBody = {'node_id': cleanId(MyAvatar.sessionUUID), 'proposed_node_id': cleanId(id)}; // for use when repeating
// It would be "simpler" to skip this and just look at the response, but: // It would be "simpler" to skip this and just look at the response, but:
// 1. We don't want to bother the metaverse with request that we know will fail. // 1. We don't want to bother the metaverse with request that we know will fail.
@ -616,31 +633,35 @@ function makeConnection(id) {
return; return;
} }
// This will immediately set response if successfull (e.g., the other guy got his request in first), or immediate failure, // This will immediately set response if successful (e.g., the other guy got his request in first),
// and will otherwise poll (using the requestBody we just set). // or immediate failure, and will otherwise poll (using the requestBody we just set).
request({ // request({ //
uri: requestUrl, uri: requestUrl,
method: 'POST', method: 'POST',
json: true, json: true,
body: {user_connection_request: requestBody} body: {'user_connection_request': requestBody}
}, handleConnectionResponseAndMaybeRepeat); }, handleConnectionResponseAndMaybeRepeat);
} }
// we change states, start the connectionInterval where we check // we change states, start the connectionInterval where we check
// to be sure the hand is still close enough. If not, we terminate // to be sure the hand is still close enough. If not, we terminate
// the interval, go back to the waiting state. If we make it // the interval, go back to the waiting state. If we make it
// the entire CONNECTING_TIME, we make the connection. // the entire CONNECTING_TIME, we make the connection.
function startConnecting(id, hand) { function startConnecting(id, hand) {
var count = 0; var count = 0;
debug("connecting", id, "hand", hand); debug("connecting", id, "hand", hand);
// do we need to do this? // do we need to do this?
connectingId = id; connectingId = id;
connectingHand = hand; connectingHand = hand;
state = STATES.connecting; state = STATES.CONNECTING;
// play sound // play sound
if (!handshakeInjector) { if (!handshakeInjector) {
handshakeInjector = Audio.playSound(handshakeSound, {position: getHandPosition(MyAvatar, currentHand), volume: 0.5, localOnly: true}); handshakeInjector = Audio.playSound(handshakeSound, {
position: getHandPosition(MyAvatar, currentHand),
volume: 0.5,
localOnly: true
});
} else { } else {
handshakeInjector.restart(); handshakeInjector.restart();
} }
@ -655,8 +676,9 @@ function startConnecting(id, hand) {
connectingInterval = Script.setInterval(function () { connectingInterval = Script.setInterval(function () {
count += 1; count += 1;
Controller.triggerHapticPulse(HAPTIC_DATA.background.strength, HAPTIC_DATA.background.duration, handToHaptic(currentHand)); Controller.triggerHapticPulse(HAPTIC_DATA.background.strength, HAPTIC_DATA.background.duration,
if (state != STATES.connecting) { handToHaptic(currentHand));
if (state !== STATES.CONNECTING) {
debug("stopping connecting interval, state changed"); debug("stopping connecting interval, state changed");
stopConnecting(); stopConnecting();
} else if (!isNearby(id, hand)) { } else if (!isNearby(id, hand)) {
@ -673,15 +695,15 @@ function startConnecting(id, hand) {
stopConnecting(); stopConnecting();
} }
}, CONNECTING_INTERVAL); }, CONNECTING_INTERVAL);
} }
/* /*
A simple sequence diagram: NOTE that the ConnectionAck is somewhat A simple sequence diagram: NOTE that the ConnectionAck is somewhat
vestigial, and probably should be removed shortly. vestigial, and probably should be removed shortly.
Avatar A Avatar B Avatar A Avatar B
| | | |
| <-----(waiting) ----- startHandshake | <-----(waiting) ----- startHandshake
startHandshake - (connectionRequest) -> | startHandshake - (connectionRequest) -> |
| | | |
| <----(connectionAck) -------- | | <----(connectionAck) -------- |
| <-----(connecting) -- startConnecting | <-----(connecting) -- startConnecting
@ -691,8 +713,8 @@ startHandshake - (connectionRequest) -> |
connected | connected |
| <--------- (done) ---------- | | <--------- (done) ---------- |
| ---------- (done) ---------> | | ---------- (done) ---------> |
*/ */
function messageHandler(channel, messageString, senderID) { function messageHandler(channel, messageString, senderID) {
if (channel !== MESSAGE_CHANNEL) { if (channel !== MESSAGE_CHANNEL) {
return; return;
} }
@ -713,7 +735,8 @@ function messageHandler(channel, messageString, senderID) {
break; break;
case "connectionRequest": case "connectionRequest":
delete waitingList[senderID]; delete waitingList[senderID];
if (state == STATES.waiting && message.id == MyAvatar.sessionUUID && (!connectingId || connectingId == senderID)) { if (state === STATES.WAITING && message.id === MyAvatar.sessionUUID &&
(!connectingId || connectingId === senderID)) {
// you were waiting for a connection request, so send the ack. Or, you and the other // you were waiting for a connection request, so send the ack. Or, you and the other
// guy raced and both send connectionRequests. Handle that too // guy raced and both send connectionRequests. Handle that too
connectingId = senderID; connectingId = senderID;
@ -723,43 +746,39 @@ function messageHandler(channel, messageString, senderID) {
id: senderID, id: senderID,
hand: handToString(currentHand) hand: handToString(currentHand)
}); });
} else { } else if (state === STATES.WAITING && connectingId === senderID) {
if (state == STATES.waiting && connectingId == senderID) {
// the person you are trying to connect sent a request to someone else. See the // the person you are trying to connect sent a request to someone else. See the
// if statement above. So, don't cry, just start the handshake over again // if statement above. So, don't cry, just start the handshake over again
startHandshake(); startHandshake();
} }
}
break; break;
case "connectionAck": case "connectionAck":
delete waitingList[senderID]; delete waitingList[senderID];
if (state == STATES.waiting && (!connectingId || connectingId == senderID)) { if (state === STATES.WAITING && (!connectingId || connectingId === senderID)) {
if (message.id == MyAvatar.sessionUUID) { if (message.id === MyAvatar.sessionUUID) {
// start connecting... // start connecting...
connectingId = senderID; connectingId = senderID;
connectingHand = message.hand; connectingHand = message.hand;
stopWaiting(); stopWaiting();
startConnecting(senderID, message.hand); startConnecting(senderID, message.hand);
} else { } else if (connectingId) {
if (connectingId) {
// this is for someone else (we lost race in connectionRequest), // this is for someone else (we lost race in connectionRequest),
// so lets start over // so lets start over
startHandshake(); startHandshake();
} }
} }
}
// TODO: check to see if we are waiting for this but the person we are connecting sent it to // TODO: check to see if we are waiting for this but the person we are connecting sent it to
// someone else, and try again // someone else, and try again
break; break;
case "connecting": case "connecting":
delete waitingList[senderID]; delete waitingList[senderID];
if (state == STATES.waiting && senderID == connectingId) { if (state === STATES.WAITING && senderID === connectingId) {
// temporary logging // temporary logging
if (connectingHand != message.hand) { if (connectingHand !== message.hand) {
debug("connecting hand", connectingHand, "not same as connecting hand in message", message.hand); debug("connecting hand", connectingHand, "not same as connecting hand in message", message.hand);
} }
connectingHand = message.hand; connectingHand = message.hand;
if (message.id != MyAvatar.sessionUUID) { if (message.id !== MyAvatar.sessionUUID) {
// the person we were trying to connect is connecting to someone else // the person we were trying to connect is connecting to someone else
// so try again // so try again
startHandshake(); startHandshake();
@ -770,7 +789,7 @@ function messageHandler(channel, messageString, senderID) {
break; break;
case "done": case "done":
delete waitingList[senderID]; delete waitingList[senderID];
if (state == STATES.connecting && connectingId == senderID) { if (state === STATES.CONNECTING && connectingId === senderID) {
// if they are done, and didn't connect us, terminate our // if they are done, and didn't connect us, terminate our
// connecting // connecting
if (message.connectionId !== MyAvatar.sessionUUID) { if (message.connectionId !== MyAvatar.sessionUUID) {
@ -783,10 +802,10 @@ function messageHandler(channel, messageString, senderID) {
} else { } else {
// if waiting or inactive, lets clear the connecting id. If in makingConnection, // if waiting or inactive, lets clear the connecting id. If in makingConnection,
// do nothing // do nothing
if (state != STATES.makingConnection && connectingId == senderID) { if (state !== STATES.MAKING_CONNECTION && connectingId === senderID) {
connectingId = undefined; connectingId = undefined;
connectingHand = undefined; connectingHand = undefined;
if (state != STATES.inactive) { if (state !== STATES.INACTIVE) {
startHandshake(); startHandshake();
} }
} }
@ -796,13 +815,13 @@ function messageHandler(channel, messageString, senderID) {
debug("unknown message", message); debug("unknown message", message);
break; break;
} }
} }
Messages.subscribe(MESSAGE_CHANNEL); Messages.subscribe(MESSAGE_CHANNEL);
Messages.messageReceived.connect(messageHandler); Messages.messageReceived.connect(messageHandler);
function makeGripHandler(hand, animate) { function makeGripHandler(hand, animate) {
// determine if we are gripping or un-gripping // determine if we are gripping or un-gripping
if (animate) { if (animate) {
return function(value) { return function(value) {
@ -814,41 +833,43 @@ function makeGripHandler(hand, animate) {
updateTriggers(value, false, hand); updateTriggers(value, false, hand);
}; };
} }
} }
function keyPressEvent(event) { function keyPressEvent(event) {
if ((event.text === "x") && !event.isAutoRepeat && !event.isShifted && !event.isMeta && !event.isControl && !event.isAlt) { if ((event.text === "x") && !event.isAutoRepeat && !event.isShifted && !event.isMeta && !event.isControl &&
!event.isAlt) {
updateTriggers(1.0, true, Controller.Standard.RightHand); updateTriggers(1.0, true, Controller.Standard.RightHand);
} }
} }
function keyReleaseEvent(event) { function keyReleaseEvent(event) {
if ((event.text === "x") && !event.isAutoRepeat && !event.isShifted && !event.isMeta && !event.isControl && !event.isAlt) { if ((event.text === "x") && !event.isAutoRepeat && !event.isShifted && !event.isMeta && !event.isControl &&
!event.isAlt) {
updateTriggers(0.0, true, Controller.Standard.RightHand); updateTriggers(0.0, true, Controller.Standard.RightHand);
} }
} }
// map controller actions // map controller actions
var connectionMapping = Controller.newMapping(Script.resolvePath('') + '-grip'); var connectionMapping = Controller.newMapping(Script.resolvePath('') + '-grip');
connectionMapping.from(Controller.Standard.LeftGrip).peek().to(makeGripHandler(Controller.Standard.LeftHand)); connectionMapping.from(Controller.Standard.LeftGrip).peek().to(makeGripHandler(Controller.Standard.LeftHand));
connectionMapping.from(Controller.Standard.RightGrip).peek().to(makeGripHandler(Controller.Standard.RightHand)); connectionMapping.from(Controller.Standard.RightGrip).peek().to(makeGripHandler(Controller.Standard.RightHand));
// setup keyboard initiation // setup keyboard initiation
Controller.keyPressEvent.connect(keyPressEvent); Controller.keyPressEvent.connect(keyPressEvent);
Controller.keyReleaseEvent.connect(keyReleaseEvent); Controller.keyReleaseEvent.connect(keyReleaseEvent);
// xbox controller cuz that's important // Xbox controller because that is important
connectionMapping.from(Controller.Standard.RB).peek().to(makeGripHandler(Controller.Standard.RightHand, true)); connectionMapping.from(Controller.Standard.RB).peek().to(makeGripHandler(Controller.Standard.RightHand, true));
// it is easy to forget this and waste a lot of time for nothing // it is easy to forget this and waste a lot of time for nothing
connectionMapping.enable(); connectionMapping.enable();
// connect updateVisualization to update frequently // connect updateVisualization to update frequently
Script.update.connect(updateVisualization); Script.update.connect(updateVisualization);
// load the sounds when the script loads // load the sounds when the script loads
handshakeSound = SoundCache.getSound(HANDSHAKE_SOUND_URL); handshakeSound = SoundCache.getSound(HANDSHAKE_SOUND_URL);
successfulHandshakeSound = SoundCache.getSound(SUCCESSFUL_HANDSHAKE_SOUND_URL); successfulHandshakeSound = SoundCache.getSound(SUCCESSFUL_HANDSHAKE_SOUND_URL);
Script.scriptEnding.connect(function () { Script.scriptEnding.connect(function () {
debug("removing controller mappings"); debug("removing controller mappings");
connectionMapping.disable(); connectionMapping.disable();
debug("removing key mappings"); debug("removing key mappings");
@ -858,7 +879,6 @@ Script.scriptEnding.connect(function () {
Script.update.disconnect(updateVisualization); Script.update.disconnect(updateVisualization);
deleteParticleEffect(); deleteParticleEffect();
deleteMakeConnectionParticleEffect(); deleteMakeConnectionParticleEffect();
}); });
}()); // END LOCAL_SCOPE }()); // END LOCAL_SCOPE