diff --git a/scripts/system/makeUserConnection.js b/scripts/system/makeUserConnection.js index 7a3215ca89..47082e882f 100644 --- a/scripts/system/makeUserConnection.js +++ b/scripts/system/makeUserConnection.js @@ -1,4 +1,6 @@ "use strict"; +/*jslint vars:true, plusplus:true, forin:true*/ +/*global Window, Script, Controller, MyAvatar, AvatarList, Entities, Messages, Audio, SoundCache, Account, UserActivityLogger, Vec3, Quat, XMLHttpRequest, location, print*/ // // makeUserConnection.js // scripts/system @@ -9,7 +11,7 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -(function() { // BEGIN LOCAL_SCOPE +(function () { // BEGIN LOCAL_SCOPE var LABEL = "makeUserConnection"; var MAX_AVATAR_DISTANCE = 0.2; // m @@ -22,12 +24,13 @@ MAKING_CONNECTION: 3 }; var STATE_STRINGS = ["inactive", "waiting", "connecting", "makingConnection"]; + var HAND_STRING_PROPERTY = 'hand'; // Used in our message protocol. IWBNI we changed it to handString, but that would break compatability. var WAITING_INTERVAL = 100; // ms var CONNECTING_INTERVAL = 100; // ms var MAKING_CONNECTION_TIMEOUT = 800; // ms var CONNECTING_TIME = 1600; // ms var PARTICLE_RADIUS = 0.15; // m - var PARTICLE_ANGLE_INCREMENT = 360/45; // 1hz + var PARTICLE_ANGLE_INCREMENT = 360 / 45; // 1hz var HANDSHAKE_SOUND_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/davidkelly/production/audio/4beat_sweep.wav"; var SUCCESSFUL_HANDSHAKE_SOUND_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/davidkelly/production/audio/3rdbeat_success_bell.wav"; var PREFERRER_HAND_JOINT_POSTFIX_ORDER = ['Middle1', 'Index1', '']; @@ -39,7 +42,7 @@ var PARTICLE_EFFECT_PROPS = { "alpha": 0.8, "azimuthFinish": Math.PI, - "azimuthStart": -1*Math.PI, + "azimuthStart": -1 * Math.PI, "emitRate": 500, "emitSpeed": 0.0, "emitterShouldTrail": 1, @@ -56,10 +59,10 @@ "color": {"red": 255, "green": 255, "blue": 255}, "colorFinish": {"red": 0, "green": 164, "blue": 255}, "colorStart": {"red": 255, "green": 255, "blue": 255}, - "emitOrientation": {"w": -0.71, "x":0.0, "y":0.0, "z": 0.71}, + "emitOrientation": {"w": -0.71, "x": 0.0, "y": 0.0, "z": 0.71}, "emitAcceleration": {"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" }; var MAKING_CONNECTION_PARTICLE_PROPS = { @@ -68,7 +71,7 @@ "alphaSpread": 0, "alphaFinish": 0, "azimuthFinish": Math.PI, - "azimuthStart": -1*Math.PI, + "azimuthStart": -1 * Math.PI, "emitRate": 2000, "emitSpeed": 0.0, "emitterShouldTrail": 1, @@ -86,14 +89,14 @@ "color": {"red": 200, "green": 170, "blue": 255}, "colorFinish": {"red": 0, "green": 134, "blue": 255}, "colorStart": {"red": 185, "green": 222, "blue": 255}, - "emitOrientation": {"w": -0.71, "x":0.0, "y":0.0, "z": 0.71}, + "emitOrientation": {"w": -0.71, "x": 0.0, "y": 0.0, "z": 0.71}, "emitAcceleration": {"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" }; - var currentHand = undefined; + var currentHand; var currentHandJointIndex = -1; var state = STATES.INACTIVE; var connectingInterval; @@ -101,7 +104,6 @@ var makingConnectionTimeout; var animHandlerId; var connectingId; - var connectingHandString; var connectingHandJointIndex = -1; var waitingList = {}; var particleEffect; @@ -116,7 +118,7 @@ function debug() { var stateString = "<" + STATE_STRINGS[state] + ">"; - var connecting = "[" + connectingId + "/" + connectingHandString + "]"; + var connecting = "[" + connectingId + "/" + connectingHandJointIndex + "]"; print.apply(null, [].concat.apply([LABEL, stateString, JSON.stringify(waitingList), connecting], [].map.call(arguments, JSON.stringify))); } @@ -183,27 +185,19 @@ function handToString(hand) { if (hand === Controller.Standard.RightHand) { return "RightHand"; - } else if (hand === Controller.Standard.LeftHand) { + } + if (hand === Controller.Standard.LeftHand) { return "LeftHand"; } debug("handToString called without valid hand! value: ", hand); return ""; } - function stringToHand(hand) { - if (hand === "RightHand") { - return Controller.Standard.RightHand; - } else if (hand === "LeftHand") { - return Controller.Standard.LeftHand; - } - debug("stringToHand called with bad hand string:", hand); - return 0; - } - function handToHaptic(hand) { if (hand === Controller.Standard.RightHand) { return 1; - } else if (hand === Controller.Standard.LeftHand) { + } + if (hand === Controller.Standard.LeftHand) { return 0; } debug("handToHaptic called without a valid hand!"); @@ -229,13 +223,13 @@ } // This returns the ideal hand joint index for the avatar. - // [hand]middle1 -> [hand]index1 -> [hand] - function getIdealHandJointIndex(avatar, hand) { - debug("got hand " + hand + " for avatar " + avatar.sessionUUID); - var handString = handToString(hand); - for (var i = 0; i < PREFERRER_HAND_JOINT_POSTFIX_ORDER.length; i++) { - var jointName = handString + PREFERRER_HAND_JOINT_POSTFIX_ORDER[i]; - var jointIndex = avatar.getJointIndex(jointName); + // [handString]middle1 -> [handString]index1 -> [handString] + function getIdealHandJointIndex(avatar, handString) { + debug("get hand " + handString + " for avatar " + (avatar && avatar.sessionUUID)); + var suffixIndex, jointName, jointIndex; + for (suffixIndex = 0; suffixIndex < (avatar ? PREFERRER_HAND_JOINT_POSTFIX_ORDER.length : 0); suffixIndex++) { + jointName = handString + PREFERRER_HAND_JOINT_POSTFIX_ORDER[suffixIndex]; + jointIndex = avatar.getJointIndex(jointName); if (jointIndex !== -1) { debug('found joint ' + jointName + ' (' + jointIndex + ')'); return jointIndex; @@ -249,26 +243,39 @@ function getHandPosition(avatar, handJointIndex) { if (handJointIndex === -1) { debug("calling getHandPosition with no hand joint index! (returning avatar position but this is a BUG)"); - debug(new Error().stack); return avatar.position; } return avatar.getJointPosition(handJointIndex); } - function shakeHandsAnimation(animationProperties) { + var animationData = {}; + function updateAnimationData() { // 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 // far in front as scaling with the avatar's height (say hips // to head distance) var headIndex = MyAvatar.getJointIndex("Head"); var offset = 0.5; // default distance of hand in front of you - var result = {}; if (headIndex) { offset = 0.8 * MyAvatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y; } - result.rightHandPosition = Vec3.multiply(offset, {x: -0.25, y: 0.8, z: 1.3}); - result.rightHandRotation = Quat.fromPitchYawRollDegrees(90, 0, 90); - return result; + animationData.rightHandPosition = Vec3.multiply(offset, {x: -0.25, y: 0.8, z: 1.3}); + animationData.rightHandRotation = Quat.fromPitchYawRollDegrees(90, 0, 90); + } + function shakeHandsAnimation() { + return animationData; + } + function endHandshakeAnimation() { + if (animHandlerId) { + debug("removing animation"); + animHandlerId = MyAvatar.removeAnimationStateHandler(animHandlerId); + } + } + function startHandshakeAnimation() { + endHandshakeAnimation(); // just in case order of press/unpress is broken + debug("adding animation"); + updateAnimationData(); + animHandlerId = MyAvatar.addAnimationStateHandler(shakeHandsAnimation, []); } function positionFractionallyTowards(posA, posB, frac) { @@ -294,11 +301,11 @@ } } - function calcParticlePos(myHand, otherHand, otherOrientation, reset) { + function calcParticlePos(myHandPosition, otherHandPosition, otherOrientation, reset) { if (reset) { particleRotationAngle = 0.0; } - var position = positionFractionallyTowards(myHand, otherHand, 0.5); + var position = positionFractionallyTowards(myHandPosition, otherHandPosition, 0.5); particleRotationAngle += PARTICLE_ANGLE_INCREMENT; // about 0.5 hz 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); @@ -314,80 +321,78 @@ } var myHandPosition = getHandPosition(MyAvatar, currentHandJointIndex); - var otherHand; + var otherHandPosition; var otherOrientation; if (connectingId) { var other = AvatarList.getAvatar(connectingId); if (other) { otherOrientation = other.orientation; - otherHand = getHandPosition(other, connectingHandJointIndex); + otherHandPosition = getHandPosition(other, connectingHandJointIndex); } } switch (state) { - case STATES.WAITING: - // no visualization while waiting - deleteParticleEffect(); - deleteMakeConnectionParticleEffect(); - stopHandshakeSound(); - break; - case STATES.CONNECTING: - var particleProps = {}; - // put the position between the 2 hands, if we have a connectingId. This - // helps define the plane in which the particles move. - positionFractionallyTowards(myHandPosition, otherHand, 0.5); - // now manage the rest of the entity - if (!particleEffect) { - particleRotationAngle = 0.0; - particleEmitRate = 500; - particleProps = PARTICLE_EFFECT_PROPS; - particleProps.isEmitting = 0; - particleProps.position = calcParticlePos(myHandPosition, otherHand, otherOrientation); - particleProps.parentID = MyAvatar.sessionUUID; - particleEffect = Entities.addEntity(particleProps, true); - } else { - particleProps.position = calcParticlePos(myHandPosition, otherHand, otherOrientation); - particleProps.isEmitting = 1; - Entities.editEntity(particleEffect, particleProps); - } - if (!makingConnectionParticleEffect) { - var props = MAKING_CONNECTION_PARTICLE_PROPS; - props.parentID = MyAvatar.sessionUUID; - makingConnectionEmitRate = 2000; - props.emitRate = makingConnectionEmitRate; - props.position = myHandPosition; - makingConnectionParticleEffect = Entities.addEntity(props, true); - } else { - makingConnectionEmitRate *= 0.5; - Entities.editEntity(makingConnectionParticleEffect, { - emitRate: makingConnectionEmitRate, - position: myHandPosition, - isEmitting: true - }); - } - break; - case STATES.MAKING_CONNECTION: - particleEmitRate = Math.max(50, particleEmitRate * 0.5); - Entities.editEntity(makingConnectionParticleEffect, {emitRate: 0, isEmitting: 0, position: myHandPosition}); - Entities.editEntity(particleEffect, { - position: calcParticlePos(myHandPosition, otherHand, otherOrientation), - emitRate: particleEmitRate + case STATES.WAITING: + // no visualization while waiting + deleteParticleEffect(); + deleteMakeConnectionParticleEffect(); + stopHandshakeSound(); + break; + case STATES.CONNECTING: + var particleProps = {}; + // put the position between the 2 hands, if we have a connectingId. This + // helps define the plane in which the particles move. + positionFractionallyTowards(myHandPosition, otherHandPosition, 0.5); + // now manage the rest of the entity + if (!particleEffect) { + particleRotationAngle = 0.0; + particleEmitRate = 500; + particleProps = PARTICLE_EFFECT_PROPS; + particleProps.isEmitting = 0; + particleProps.position = calcParticlePos(myHandPosition, otherHandPosition, otherOrientation); + particleProps.parentID = MyAvatar.sessionUUID; + particleEffect = Entities.addEntity(particleProps, true); + } else { + particleProps.position = calcParticlePos(myHandPosition, otherHandPosition, otherOrientation); + particleProps.isEmitting = 1; + Entities.editEntity(particleEffect, particleProps); + } + if (!makingConnectionParticleEffect) { + var props = MAKING_CONNECTION_PARTICLE_PROPS; + props.parentID = MyAvatar.sessionUUID; + makingConnectionEmitRate = 2000; + props.emitRate = makingConnectionEmitRate; + props.position = myHandPosition; + makingConnectionParticleEffect = Entities.addEntity(props, true); + } else { + makingConnectionEmitRate *= 0.5; + Entities.editEntity(makingConnectionParticleEffect, { + emitRate: makingConnectionEmitRate, + position: myHandPosition, + isEmitting: true }); - break; - default: - debug("unexpected state", state); - break; + } + break; + case STATES.MAKING_CONNECTION: + particleEmitRate = Math.max(50, particleEmitRate * 0.5); + Entities.editEntity(makingConnectionParticleEffect, {emitRate: 0, isEmitting: 0, position: myHandPosition}); + Entities.editEntity(particleEffect, { + position: calcParticlePos(myHandPosition, otherHandPosition, otherOrientation), + emitRate: particleEmitRate + }); + break; + default: + debug("unexpected state", state); + break; } } - function isNearby(id, hand) { + function isNearby() { if (currentHand) { - var handPos = getHandPosition(MyAvatar, currentHandJointIndex); - var avatar = AvatarList.getAvatar(id); + var handPosition = getHandPosition(MyAvatar, currentHandJointIndex); + var avatar = AvatarList.getAvatar(connectingId); if (avatar) { - var otherHand = stringToHand(hand); - var otherHandJointIndex = getIdealHandJointIndex(avatar, otherHand); - var distance = Vec3.distance(getHandPosition(avatar, otherHandJointIndex), handPos); + var distance = Vec3.distance(getHandPosition(avatar, connectingHandJointIndex), handPosition); return (distance < MAX_AVATAR_DISTANCE); } } @@ -395,68 +400,90 @@ } function findNearestWaitingAvatar() { - var handPos = getHandPosition(MyAvatar, currentHandJointIndex); + var handPosition = getHandPosition(MyAvatar, currentHandJointIndex); var minDistance = MAX_AVATAR_DISTANCE; var nearestAvatar = {}; Object.keys(waitingList).forEach(function (identifier) { var avatar = AvatarList.getAvatar(identifier); if (avatar) { - var hand = stringToHand(waitingList[identifier]); - var handJointIndex = getIdealHandJointIndex(avatar, hand); - var distance = Vec3.distance(getHandPosition(avatar, handJointIndex), handPos); + var handJointIndex = waitingList[identifier]; + var distance = Vec3.distance(getHandPosition(avatar, handJointIndex), handPosition); if (distance < minDistance) { minDistance = distance; - nearestAvatar = {avatar: identifier, hand: hand, avatarObject: avatar}; + nearestAvatar = {avatarId: identifier, jointIndex: handJointIndex}; } } }); return nearestAvatar; } + function messageSend(message) { + Messages.sendMessage(MESSAGE_CHANNEL, JSON.stringify(message)); + } + function handStringMessageSend(message) { + message[HAND_STRING_PROPERTY] = handToString(currentHand); + messageSend(message); + } + function setupCandidate() { // find the closest in-range avatar, send connection request, and return true. (Otherwise falsey) + var nearestAvatar = findNearestWaitingAvatar(); + if (nearestAvatar.avatarId) { + connectingId = nearestAvatar.avatarId; + connectingHandJointIndex = nearestAvatar.jointIndex; + debug("sending connectionRequest to", connectingId); + handStringMessageSend({ + key: "connectionRequest", + id: connectingId + }); + return true; + } + } + function clearConnecting() { + connectingId = undefined; + connectingHandJointIndex = -1; + } + function lookForWaitingAvatar() { + // we started with nobody close enough, but maybe I've moved + // or they did. Note that 2 people doing this race, so stop + // as soon as you have a connectingId (which means you got their + // message before noticing they were in range in this loop) + // just in case we re-enter before stopping + stopWaiting(); + debug("started looking for waiting avatars"); + waitingInterval = Script.setInterval(function () { + if (state === STATES.WAITING && !connectingId) { + setupCandidate(); + } else { + // something happened, stop looking for avatars to connect + stopWaiting(); + debug("stopped looking for waiting avatars"); + } + }, WAITING_INTERVAL); + } + + var pollCount = 0, requestUrl = location.metaverseServerUrl + '/api/v1/user/connection_request'; // 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 // 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 - // 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) { if (fromKeyboard) { - debug("adding animation"); - // just in case order of press/unpress is broken - if (animHandlerId) { - animHandlerId = MyAvatar.removeAnimationStateHandler(animHandlerId); - } - animHandlerId = MyAvatar.addAnimationStateHandler(shakeHandsAnimation, []); + startHandshakeAnimation(); } debug("starting handshake for", currentHand); pollCount = 0; state = STATES.WAITING; - connectingId = undefined; - connectingHandString = undefined; - connectingHandJointIndex = -1; + clearConnecting(); // just in case stopWaiting(); stopConnecting(); stopMakingConnection(); - - var nearestAvatar = findNearestWaitingAvatar(); - if (nearestAvatar.avatar) { - connectingId = nearestAvatar.avatar; - connectingHandString = handToString(nearestAvatar.hand); - connectingHandJointIndex = getIdealHandJointIndex(nearestAvatar.avatarObject, nearestAvatar.hand); - currentHandJointIndex = getIdealHandJointIndex(MyAvatar, currentHand); - debug("sending connectionRequest to", connectingId); - messageSend({ - key: "connectionRequest", - id: connectingId, - hand: handToString(currentHand) - }); - } else { + if (!setupCandidate()) { // send waiting message debug("sending waiting message"); - messageSend({ + handStringMessageSend({ key: "waiting", - hand: handToString(currentHand) }); lookForWaitingAvatar(); } @@ -474,9 +501,7 @@ // as we ignore the key release event when inactive. See updateTriggers // below. state = STATES.INACTIVE; - connectingId = undefined; - connectingHandString = undefined; - connectingHandJointIndex = -1; + clearConnecting(); stopWaiting(); stopConnecting(); stopMakingConnection(); @@ -486,10 +511,7 @@ key: "done" }); - if (animHandlerId) { - debug("removing animation"); - MyAvatar.removeAnimationStateHandler(animHandlerId); - } + endHandshakeAnimation(); // 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); @@ -500,19 +522,16 @@ debug("currentHand", currentHand, "ignoring messages from", hand); return; } - if (!currentHand) { - currentHand = hand; - currentHandJointIndex = getIdealHandJointIndex(MyAvatar, currentHand); - } + currentHand = hand; + currentHandJointIndex = getIdealHandJointIndex(MyAvatar, handToString(currentHand)); // Always, in case of changed skeleton. // ok now, we are either initiating or quitting... var isGripping = value > GRIP_MIN; if (isGripping) { debug("updateTriggers called - gripping", handToString(hand)); if (state !== STATES.INACTIVE) { return; - } else { - startHandshake(fromKeyboard); } + startHandshake(fromKeyboard); } else { // TODO: should we end handshake even when inactive? Ponder debug("updateTriggers called -- no longer gripping", handToString(hand)); @@ -524,47 +543,12 @@ } } - function messageSend(message) { - Messages.sendMessage(MESSAGE_CHANNEL, JSON.stringify(message)); - } - - function lookForWaitingAvatar() { - // we started with nobody close enough, but maybe I've moved - // or they did. Note that 2 people doing this race, so stop - // as soon as you have a connectingId (which means you got their - // message before noticing they were in range in this loop) - - // just in case we re-enter before stopping - stopWaiting(); - debug("started looking for waiting avatars"); - waitingInterval = Script.setInterval(function () { - if (state === STATES.WAITING && !connectingId) { - // find the closest in-range avatar, and send connection request - var nearestAvatar = findNearestWaitingAvatar(); - if (nearestAvatar.avatar) { - connectingId = nearestAvatar.avatar; - connectingHandString = handToString(nearestAvatar.hand); - debug("sending connectionRequest to", connectingId); - messageSend({ - key: "connectionRequest", - id: connectingId, - hand: handToString(currentHand) - }); - } - } else { - // something happened, stop looking for avatars to connect - stopWaiting(); - debug("stopped looking for waiting avatars"); - } - }, WAITING_INTERVAL); - } - /* There is a mini-state machine after entering STATES.makingConnection. We make a request (which might immediately succeed, fail, or neither. If we immediately fail, we tell the user. 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; function connectionRequestCompleted() { // Final result is in. Do effects. if (result.status === 'success') { // set earlier if (!successfulHandshakeInjector) { @@ -580,16 +564,37 @@ handToHaptic(currentHand)); // don't change state (so animation continues while gripped) // 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); - UserActivityLogger.makeUserConnection(connectingId, true, result.connection.new_connection ? - "new connection" : "already connected"); + Window.makeConnection(true, + result.connection.new_connection ? + "You and " + result.connection.username + " are now connected!" : + result.connection.username); + UserActivityLogger.makeUserConnection(connectingId, + true, + result.connection.new_connection ? + "new connection" : + "already connected"); return; } // failed endHandshake(); debug("failing with result data", result); // IWBNI we also did some fail sound/visual effect. Window.makeConnection(false, result.connection); + if (Account.isLoggedIn()) { // Give extra failure info + request(location.metaverseServerUrl + '/api/v1/users/' + Account.username + '/location', function (error, response) { + var message = ''; + if (error || response.status !== 'success') { + message = 'Unable to get location.'; + } else if (!response.data || !response.data.location) { + message = "Unexpected location value: " + JSON.stringify(response); + } else if (response.data.location.node_id !== cleanId(MyAvatar.sessionUUID)) { + message = 'Session identification does not match database. Maybe you are logged in on another machine? That would prevent handshakes.' + JSON.stringify(response) + MyAvatar.sessionUUID; + } + if (message) { + Window.makeConnection(false, message); + } + debug("account location:", message || 'ok'); + }); + } UserActivityLogger.makeUserConnection(connectingId, false, result.connection); } // This is a bit fragile - but to account for skew in when people actually create the @@ -606,7 +611,7 @@ debug(response, 'pollCount', pollCount); if (pollCount++ >= POLL_LIMIT) { // server will expire, but let's not wait that long. debug('POLL LIMIT REACHED; TIMEOUT: expired message generated by CLIENT'); - result = {status: 'error', connection: 'expired'}; + result = {status: 'error', connection: 'No logged-in partner found.'}; connectionRequestCompleted(); } else { // poll Script.setTimeout(function () { @@ -636,8 +641,6 @@ } } - // this should be where we make the appropriate connection call. For now just make the - // visualization change. function makeConnection(id) { // send done to let the connection know you have made connection. messageSend({ @@ -647,8 +650,7 @@ state = STATES.MAKING_CONNECTION; - // continue the haptic background until the timeout fires. When we make calls, we will have an interval - // probably, in which we do this. + // continue the haptic background until the timeout fires. 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 @@ -662,26 +664,27 @@ // This will immediately set response if successful (e.g., the other guy got his request in first), // or immediate failure, and will otherwise poll (using the requestBody we just set). - request({ // + request({ uri: requestUrl, method: 'POST', json: true, body: {'user_connection_request': requestBody} }, handleConnectionResponseAndMaybeRepeat); } + function setupConnecting(id, jointIndex) { + connectingId = id; + connectingHandJointIndex = jointIndex; + } // we change states, start the connectionInterval where we check // 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 entire CONNECTING_TIME, we make the connection. - function startConnecting(id, hand) { + function startConnecting(id, jointIndex) { var count = 0; - debug("connecting", id, "hand", hand); + debug("connecting", id, "hand", jointIndex); // do we need to do this? - connectingId = id; - connectingHandString = hand; - connectingHandJointIndex = AvatarList.getAvatarIdentifiers().indexOf(connectingId) !== -1 ? - getIdealHandJointIndex(AvatarList.getAvatar(connectingId), stringToHand(connectingHandString)) : -1; + setupConnecting(id, jointIndex); state = STATES.CONNECTING; // play sound @@ -696,10 +699,9 @@ } // send message that we are connecting with them - messageSend({ + handStringMessageSend({ key: "connecting", - id: id, - hand: handToString(currentHand) + id: id }); Controller.triggerHapticPulse(HAPTIC_DATA.initial.strength, HAPTIC_DATA.initial.duration, handToHaptic(currentHand)); @@ -710,7 +712,7 @@ if (state !== STATES.CONNECTING) { debug("stopping connecting interval, state changed"); stopConnecting(); - } else if (!isNearby(id, hand)) { + } else if (!isNearby()) { // gotta go back to waiting debug(id, "moved, back to waiting"); stopConnecting(); @@ -718,7 +720,7 @@ key: "done" }); startHandshake(); - } else if (count > CONNECTING_TIME/CONNECTING_INTERVAL) { + } else if (count > CONNECTING_TIME / CONNECTING_INTERVAL) { debug("made connection with " + id); makeConnection(id); stopConnecting(); @@ -744,140 +746,120 @@ | ---------- (done) ---------> | */ function messageHandler(channel, messageString, senderID) { + var message = {}; + function exisitingOrSearchedJointIndex() { // If this is a new connectingId, we'll need to find the jointIndex + return connectingId ? connectingHandJointIndex : getIdealHandJointIndex(AvatarList.getAvatar(senderID), message[HAND_STRING_PROPERTY]); + } if (channel !== MESSAGE_CHANNEL) { return; } if (MyAvatar.sessionUUID === senderID) { // ignore my own return; } - var message = {}; try { message = JSON.parse(messageString); } catch (e) { debug(e); } switch (message.key) { - case "waiting": - // add this guy to waiting object. Any other message from this person will - // remove it from the list - waitingList[senderID] = message.hand; - break; - case "connectionRequest": - delete waitingList[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 - // guy raced and both send connectionRequests. Handle that too - connectingId = senderID; - connectingHandString = message.hand; - connectingHandJointIndex = AvatarList.getAvatarIdentifiers().indexOf(connectingId) !== -1 ? - getIdealHandJointIndex(AvatarList.getAvatar(connectingId), stringToHand(connectingHandString)) : -1; - messageSend({ - key: "connectionAck", - id: senderID, - hand: handToString(currentHand) - }); - } else if (state === STATES.WAITING && connectingId === senderID) { - // 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 + case "waiting": + // add this guy to waiting object. Any other message from this person will remove it from the list + waitingList[senderID] = getIdealHandJointIndex(AvatarList.getAvatar(senderID), message[HAND_STRING_PROPERTY]); + break; + case "connectionRequest": + delete waitingList[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 + // guy raced and both send connectionRequests. Handle that too + setupConnecting(senderID, exisitingOrSearchedJointIndex()); + handStringMessageSend({ + key: "connectionAck", + id: senderID, + }); + } else if (state === STATES.WAITING && connectingId === senderID) { + // 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 + startHandshake(); + } + break; + case "connectionAck": + delete waitingList[senderID]; + if (state === STATES.WAITING && (!connectingId || connectingId === senderID)) { + if (message.id === MyAvatar.sessionUUID) { + stopWaiting(); + startConnecting(senderID, exisitingOrSearchedJointIndex()); + } else if (connectingId) { + // this is for someone else (we lost race in connectionRequest), + // so lets start over startHandshake(); } - break; - case "connectionAck": - delete waitingList[senderID]; - if (state === STATES.WAITING && (!connectingId || connectingId === senderID)) { - if (message.id === MyAvatar.sessionUUID) { - // start connecting... - connectingId = senderID; - connectingHandString = message.hand; - connectingHandJointIndex = AvatarList.getAvatarIdentifiers().indexOf(connectingId) !== -1 ? - getIdealHandJointIndex(AvatarList.getAvatar(connectingId), stringToHand(connectingHandString)) : -1; - stopWaiting(); - startConnecting(senderID, connectingHandString); - } else if (connectingId) { - // this is for someone else (we lost race in connectionRequest), - // so lets start over + } + // TODO: check to see if we are waiting for this but the person we are connecting sent it to + // someone else, and try again + break; + case "connecting": + delete waitingList[senderID]; + if (state === STATES.WAITING && senderID === connectingId) { + if (message.id !== MyAvatar.sessionUUID) { + // the person we were trying to connect is connecting to someone else + // so try again + startHandshake(); + break; + } + startConnecting(senderID, connectingHandJointIndex); + } + break; + case "done": + delete waitingList[senderID]; + if (state === STATES.CONNECTING && connectingId === senderID) { + // if they are done, and didn't connect us, terminate our + // connecting + if (message.connectionId !== MyAvatar.sessionUUID) { + stopConnecting(); + // now just call startHandshake. Should be ok to do so without a + // value for isKeyboard, as we should not change the animation + // state anyways (if any) + startHandshake(); + } + } else { + // if waiting or inactive, lets clear the connecting id. If in makingConnection, + // do nothing + if (state !== STATES.MAKING_CONNECTION && connectingId === senderID) { + clearConnecting(); + if (state !== STATES.INACTIVE) { startHandshake(); } } - // TODO: check to see if we are waiting for this but the person we are connecting sent it to - // someone else, and try again - break; - case "connecting": - delete waitingList[senderID]; - if (state === STATES.WAITING && senderID === connectingId) { - // temporary logging - if (connectingHandString !== message.hand) { - debug("connecting hand", connectingHandString, "not same as connecting hand in message", message.hand); - } - connectingHandString = message.hand; - if (message.id !== MyAvatar.sessionUUID) { - // the person we were trying to connect is connecting to someone else - // so try again - startHandshake(); - break; - } - startConnecting(senderID, message.hand); - } - break; - case "done": - delete waitingList[senderID]; - if (state === STATES.CONNECTING && connectingId === senderID) { - // if they are done, and didn't connect us, terminate our - // connecting - if (message.connectionId !== MyAvatar.sessionUUID) { - stopConnecting(); - // now just call startHandshake. Should be ok to do so without a - // value for isKeyboard, as we should not change the animation - // state anyways (if any) - startHandshake(); - } - } else { - // if waiting or inactive, lets clear the connecting id. If in makingConnection, - // do nothing - if (state !== STATES.MAKING_CONNECTION && connectingId === senderID) { - connectingId = undefined; - connectingHandString = undefined; - connectingHandJointIndex = -1; - if (state !== STATES.INACTIVE) { - startHandshake(); - } - } - } - break; - default: - debug("unknown message", message); - break; + } + break; + default: + debug("unknown message", message); + break; } } Messages.subscribe(MESSAGE_CHANNEL); Messages.messageReceived.connect(messageHandler); - function makeGripHandler(hand, animate) { // determine if we are gripping or un-gripping if (animate) { - return function(value) { + return function (value) { updateTriggers(value, true, hand); }; - - } else { - return function (value) { - updateTriggers(value, false, hand); - }; } + return function (value) { + updateTriggers(value, false, hand); + }; } 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); } } 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); } }