From fa7283a6e2ec82c71b05bd9828430708f29fd150 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Mon, 13 Mar 2017 18:33:36 -0700 Subject: [PATCH 1/7] initial new messaging - working but needs cleanup --- scripts/system/makeUserConnection.js | 142 ++++++++++++++++----------- 1 file changed, 87 insertions(+), 55 deletions(-) diff --git a/scripts/system/makeUserConnection.js b/scripts/system/makeUserConnection.js index 1b49ed92cb..7001c2f861 100644 --- a/scripts/system/makeUserConnection.js +++ b/scripts/system/makeUserConnection.js @@ -29,16 +29,15 @@ const FRIENDING_SUCCESS_HAPTIC_STRENGTH = 1.0; const HAPTIC_DURATION = 20; var currentHand; -var isWaiting = false; -var nearbyAvatars = []; var state = STATES.inactive; -var waitingInterval; var friendingInterval; var entity; var makingFriends = false; // really just for visualizations for now var animHandlerId; var entityDimensionMultiplier = 1.0; var friendingId; +var pendingFriendAckFrom; +var latestFriendRequestFrom; function debug() { var stateString = "<" + STATE_STRINGS[state] + ">"; @@ -126,24 +125,23 @@ function updateVisualization() { } } - -// this should find the nearest avatars, returning an array of avatar, hand pairs. Currently -// looking at distance between hands. -function findNearbyAvatars() { - var nearbyAvatars = []; +function findNearbyAvatars(nearestOnly) { var handPos = getHandPosition(MyAvatar, currentHand); + var minDistance = MAX_AVATAR_DISTANCE; + var nearbyAvatars = []; AvatarList.getAvatarIdentifiers().forEach(function (identifier) { if (!identifier) { return; } var avatar = AvatarList.getAvatar(identifier); var distanceR = Vec3.distance(getHandPosition(avatar, Controller.Standard.RightHand), handPos); var distanceL = Vec3.distance(getHandPosition(avatar, Controller.Standard.LeftHand), handPos); var distance = Math.min(distanceL, distanceR); - if (distance < MAX_AVATAR_DISTANCE) { - if (distance == distanceR) { - nearbyAvatars.push({avatar: identifier, hand: Controller.Standard.RightHand}); - } else { - nearbyAvatars.push({avatar: identifier, hand: Controller.Standard.LeftHand}); + if (distance < minDistance) { + if (nearestOnly) { + minDistance = distance; + nearbyAvatars = []; } + var hand = (distance == distanceR ? Controller.Standard.RightHand : Controller.Standard.LeftHand); + nearbyAvatars.push({avatar: identifier, hand: hand}); } }); return nearbyAvatars; @@ -156,24 +154,35 @@ function startHandshake(fromKeyboard) { } debug("starting handshake for", currentHand); state = STATES.waiting; - friendingId = undefined; entityDimensionMultiplier = 1.0; - waitingInterval = Script.setInterval( - function () { + pendingFriendAckFrom = undefined; + // if we have a recent friendRequest, send an ack back + if (latestFriendRequestFrom) { + debug("sending friendAck to", latestFriendRequestFrom); + messageSend({ + key: "friendAck", + id: latestFriendRequestFrom, + hand: handToString(currentHand) + }); + } else { + var nearestAvatar = findNearbyAvatars(true)[0]; + debug("nearest avatar", nearestAvatar); + if (nearestAvatar) { + pendingFriendAckFrom = nearestAvatar.avatar; + debug("sending friendRequest to", pendingFriendAckFrom); messageSend({ - key: "waiting", - hand: handToString(currentHand) + key: "friendRequest", + id: nearestAvatar.avatar, + hand: handToString(nearestAvatar.hand) }); - }, WAITING_INTERVAL); + } + } } function endHandshake() { debug("ending handshake for", currentHand); currentHand = undefined; state = STATES.inactive; - if (waitingInterval) { - waitingInterval = Script.clearInterval(waitingInterval); - } if (friendingInterval) { friendingInterval = Script.clearInterval(friendingInterval); // send done to let friend know you are not making friends now @@ -217,6 +226,7 @@ function messageSend(message) { } function isNearby(id, hand) { + var nearbyAvatars = findNearbyAvatars(); for(var i = 0; i < nearbyAvatars.length; i++) { if (nearbyAvatars[i].avatar == id && handToString(nearbyAvatars[i].hand) == hand) { return true; @@ -246,20 +256,24 @@ function startFriending(id, hand) { var count = 0; debug("friending", id, "hand", hand); friendingId = id; + pendingFriendAckFrom = undefined; + latestFriendRequestFrom = undefined; state = STATES.friending; Controller.triggerHapticPulse(FRIENDING_HAPTIC_STRENGTH, HAPTIC_DURATION, handToHaptic(currentHand)); - if (waitingInterval) { - waitingInterval = Script.clearInterval(waitingInterval); - } + + // send message that we are friending them + messageSend({ + key: "friending", + id: id, + hand: handToString(currentHand) + }); + friendingInterval = Script.setInterval(function () { - nearbyAvatars = findNearbyAvatars(); entityDimensionMultiplier = 1.0 + 2.0 * ++count * FRIENDING_INTERVAL / FRIENDING_TIME; - // insure senderID is still nearby if (state != STATES.friending) { debug("stopping friending interval, state changed"); friendingInterval = Script.clearInterval(friendingInterval); - } - if (!isNearby(id, hand)) { + } else if (!isNearby(id, hand)) { // gotta go back to waiting debug(id, "moved, back to waiting"); friendingInterval = Script.clearInterval(friendingInterval); @@ -276,23 +290,21 @@ A simple sequence diagram: Avatar A Avatar B | | - | <---------(waiting) --- startHandshake - startHandshake -- (waiting) -----> | + | <-----(FriendRequest) -- startHandshake + startHandshake -- (FriendAck) ---> | | | | <-------(friending) -- startFriending startFriending -- (friending) ---> | | | | friends friends | - | ` | + | <--------- (done) ---------- | + | ---------- (done) ---------> | */ function messageHandler(channel, messageString, senderID) { if (channel !== MESSAGE_CHANNEL) { return; } - if (state == STATES.inactive) { - return; - } if (MyAvatar.sessionUUID === senderID) { // ignore my own return; } @@ -302,32 +314,42 @@ function messageHandler(channel, messageString, senderID) { } catch (e) { debug(e); } + debug("message", message); switch (message.key) { - case "waiting": + case "friendRequest": + if (state == STATES.inactive && message.id == MyAvatar.sessionUUID) { + debug("setting latestFriendRequestFrom", senderID); + latestFriendRequestFrom = senderID; + } else if (state == STATES.waiting && !pendingFriendAckFrom) { + // you are waiting for a friend request, so send the ack + pendingFriendAckFrom = senderID; + messageSend({ + key: "friendAck", + id: senderID, + hand: handToString(currentHand) + }); + } + // TODO: ponder keeping this up-to-date during + // other states? + break; + case "friendAck": + if (state == STATES.waiting && message.id == MyAvatar.sessionUUID) { + if (pendingFriendAckFrom && senderID != pendingFriendAckFrom) { + debug("ignoring friendAck from", senderID, ", waiting on", pendingFriendAckFrom); + break; + } + // start friending... + startFriending(senderID, message.hand); + } + break; case "friending": - if (state == STATES.waiting) { - if (message.key == "friending" && message.id != MyAvatar.sessionUUID) { + if (state == STATES.waiting && senderID == latestFriendRequestFrom) { + if (message.id != MyAvatar.sessionUUID) { // for now, just ignore these. Hmm debug("ignoring friending message", message, "from", senderID); break; } - nearbyAvatars = findNearbyAvatars(); - if (isNearby(senderID, message.hand)) { - // if we are responding to a friending message (they didn't send a - // waiting before noticing us and friending), don't bother with sending - // a friending message? - messageSend({ - key: "friending", - id: senderID, - hand: handToString(currentHand) - }); - startFriending(senderID, message.hand); - } else { - // for now, ignore this. Hmm. - if (message.key == "friending") { - debug(senderID, "is friending us, but not close enough??"); - } - } + startFriending(senderID, message.hand); } break; case "done": @@ -343,6 +365,16 @@ function messageHandler(channel, messageString, senderID) { // state anyways (if any) startHandshake(); } + } else { + // if waiting or inactive, lets clear the pending stuff + if (pendingFriendAckFrom == senderID || lastestFriendRequestFrom == senderID) { + if (state == STATES.inactive) { + pendingFriendAckFrom = undefined; + latestFriendRequestFrom = undefined; + } else { + startHandshake(); + } + } } break; default: From 7765daf38223a42e3c4e6843db1817a6a413018b Mon Sep 17 00:00:00 2001 From: David Kelly Date: Mon, 13 Mar 2017 18:57:01 -0700 Subject: [PATCH 2/7] handle cleanup, race, etc..., more to come --- scripts/system/makeUserConnection.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/scripts/system/makeUserConnection.js b/scripts/system/makeUserConnection.js index 7001c2f861..5bf8ba2047 100644 --- a/scripts/system/makeUserConnection.js +++ b/scripts/system/makeUserConnection.js @@ -158,7 +158,6 @@ function startHandshake(fromKeyboard) { pendingFriendAckFrom = undefined; // if we have a recent friendRequest, send an ack back if (latestFriendRequestFrom) { - debug("sending friendAck to", latestFriendRequestFrom); messageSend({ key: "friendAck", id: latestFriendRequestFrom, @@ -166,10 +165,8 @@ function startHandshake(fromKeyboard) { }); } else { var nearestAvatar = findNearbyAvatars(true)[0]; - debug("nearest avatar", nearestAvatar); if (nearestAvatar) { pendingFriendAckFrom = nearestAvatar.avatar; - debug("sending friendRequest to", pendingFriendAckFrom); messageSend({ key: "friendRequest", id: nearestAvatar.avatar, @@ -314,14 +311,13 @@ function messageHandler(channel, messageString, senderID) { } catch (e) { debug(e); } - debug("message", message); switch (message.key) { case "friendRequest": if (state == STATES.inactive && message.id == MyAvatar.sessionUUID) { - debug("setting latestFriendRequestFrom", senderID); latestFriendRequestFrom = senderID; - } else if (state == STATES.waiting && !pendingFriendAckFrom) { - // you are waiting for a friend request, so send the ack + } else if (state == STATES.waiting && (pendingFriendAckFrom == senderID || !pendingFriendAckFrom)) { + // you are waiting for a friend request, so send the ack. Or, you and the other + // guy raced and both send friendRequests. Handle that too pendingFriendAckFrom = senderID; messageSend({ key: "friendAck", @@ -436,5 +432,8 @@ Script.scriptEnding.connect(function () { Controller.keyReleaseEvent.disconnect(keyReleaseEvent); debug("disconnecting updateVisualization"); Script.update.disconnect(updateVisualization); + if (entity) { + entity = Entities.deleteEntity(entity); + } }); From 246d2f4017132e775a91373ee1b9f75ea8af6acd Mon Sep 17 00:00:00 2001 From: David Kelly Date: Tue, 14 Mar 2017 08:58:14 -0700 Subject: [PATCH 3/7] fix comments at top --- scripts/system/makeUserConnection.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/system/makeUserConnection.js b/scripts/system/makeUserConnection.js index 5bf8ba2047..8dc3b156c7 100644 --- a/scripts/system/makeUserConnection.js +++ b/scripts/system/makeUserConnection.js @@ -1,7 +1,7 @@ "use strict"; // -// friends.js -// scripts/developer/tests/performance/ +// makeUserConnetion.js +// scripts/system // // Created by David Kelly on 3/7/2017. // Copyright 2017 High Fidelity, Inc. From f821ccc8c347dad96187e1ec454de72dc342a191 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Tue, 14 Mar 2017 10:00:19 -0700 Subject: [PATCH 4/7] fix typo - doh! --- scripts/system/makeUserConnection.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/makeUserConnection.js b/scripts/system/makeUserConnection.js index 8dc3b156c7..526d01706f 100644 --- a/scripts/system/makeUserConnection.js +++ b/scripts/system/makeUserConnection.js @@ -363,7 +363,7 @@ function messageHandler(channel, messageString, senderID) { } } else { // if waiting or inactive, lets clear the pending stuff - if (pendingFriendAckFrom == senderID || lastestFriendRequestFrom == senderID) { + if (pendingFriendAckFrom == senderID || latestFriendRequestFrom == senderID) { if (state == STATES.inactive) { pendingFriendAckFrom = undefined; latestFriendRequestFrom = undefined; From 0a35fa34f9036be84d98b68aa8f9301920be8e73 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Tue, 14 Mar 2017 14:22:44 -0700 Subject: [PATCH 5/7] some reworking, cleaning, bug fixing... --- scripts/system/makeUserConnection.js | 107 +++++++++++++++------------ 1 file changed, 60 insertions(+), 47 deletions(-) diff --git a/scripts/system/makeUserConnection.js b/scripts/system/makeUserConnection.js index 526d01706f..881a828a17 100644 --- a/scripts/system/makeUserConnection.js +++ b/scripts/system/makeUserConnection.js @@ -9,21 +9,23 @@ // 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 const version = 0.1; -const label = "Friends"; -const MAX_AVATAR_DISTANCE = 1.0; +const label = "makeUserConnection"; +const MAX_AVATAR_DISTANCE = 1.25; const GRIP_MIN = 0.05; -const MESSAGE_CHANNEL = "io.highfidelity.friends"; +const MESSAGE_CHANNEL = "io.highfidelity.makeUserConnection"; const STATES = { inactive : 0, waiting: 1, friending: 2, + makingFriends: 3 }; -const STATE_STRINGS = ["inactive", "waiting", "friending"]; +const STATE_STRINGS = ["inactive", "waiting", "friending", "makingFriends"]; const WAITING_INTERVAL = 100; // ms const FRIENDING_INTERVAL = 100; // ms const FRIENDING_TIME = 3000; // ms -const OVERLAY_COLORS = [{red: 0x00, green: 0xFF, blue: 0x00}, {red: 0x00, green: 0x00, blue: 0xFF}]; +const ENTITY_COLORS = [{red: 0x00, green: 0xFF, blue: 0x00}, {red: 0x00, green: 0x00, blue: 0xFF}, {red: 0xFF, green: 0x00, blue: 0x00}]; const FRIENDING_HAPTIC_STRENGTH = 0.5; const FRIENDING_SUCCESS_HAPTIC_STRENGTH = 1.0; const HAPTIC_DURATION = 20; @@ -32,17 +34,14 @@ var currentHand; var state = STATES.inactive; var friendingInterval; var entity; -var makingFriends = false; // really just for visualizations for now var animHandlerId; var entityDimensionMultiplier = 1.0; var friendingId; -var pendingFriendAckFrom; -var latestFriendRequestFrom; function debug() { var stateString = "<" + STATE_STRINGS[state] + ">"; var versionString = "v" + version; - print.apply(null, [].concat.apply([label, versionString, stateString], [].map.call(arguments, JSON.stringify))); + print.apply(null, [].concat.apply([label, versionString, stateString, friendingId], [].map.call(arguments, JSON.stringify))); } function handToString(hand) { @@ -99,14 +98,9 @@ function updateVisualization() { return; } - var color = state == STATES.waiting ? OVERLAY_COLORS[0] : OVERLAY_COLORS[1]; + var color = ENTITY_COLORS[state-1]; var position = getHandPosition(MyAvatar, currentHand); - // temp code, though all of this stuff really is temp... - if (makingFriends) { - color = { red: 0xFF, green: 0x00, blue: 0x00 }; - } - // TODO: make the size scale with avatar, up to // the actual size of MAX_AVATAR_DISTANCE var wrist = MyAvatar.getJointPosition(MyAvatar.getJointIndex(handToString(currentHand))); @@ -150,26 +144,32 @@ function findNearbyAvatars(nearestOnly) { 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, []); } debug("starting handshake for", currentHand); state = STATES.waiting; entityDimensionMultiplier = 1.0; - pendingFriendAckFrom = undefined; - // if we have a recent friendRequest, send an ack back - if (latestFriendRequestFrom) { + // if we have a recent friendRequest, send an ack back. + // TODO: be sure the friendingId resets when we get the done message + if (friendingId) { + debug("sending friendAck to", friendingId); messageSend({ key: "friendAck", - id: latestFriendRequestFrom, + id: friendingId, hand: handToString(currentHand) }); } else { var nearestAvatar = findNearbyAvatars(true)[0]; if (nearestAvatar) { - pendingFriendAckFrom = nearestAvatar.avatar; + friendingId = nearestAvatar.avatar; + debug("sending friendRequest to", friendingId); messageSend({ key: "friendRequest", - id: nearestAvatar.avatar, + id: friendingId, hand: handToString(nearestAvatar.hand) }); } @@ -181,6 +181,7 @@ function endHandshake() { currentHand = undefined; state = STATES.inactive; if (friendingInterval) { + friendingId = undefined; friendingInterval = Script.clearInterval(friendingInterval); // send done to let friend know you are not making friends now messageSend({ @@ -235,15 +236,19 @@ function isNearby(id, hand) { // this should be where we make the appropriate friend call. For now just make the // visualization change. function makeFriends(id) { - // temp code to just flash the visualization really (for now!) - makingFriends = true; // send done to let the friend know you have made friends. messageSend({ key: "done", friendId: id }); Controller.triggerHapticPulse(FRIENDING_SUCCESS_HAPTIC_STRENGTH, HAPTIC_DURATION, handToHaptic(currentHand)); - Script.setTimeout(function () { makingFriends = false; entityDimensionMultiplier = 1.0; }, 1000); + state = STATES.makingFriends; + // now that we made friends, reset everything + Script.setTimeout(function () { + state = STATES.waiting; + friendingId = undefined; + entityDimensionMultiplier = 1.0; + }, 1000); } // we change states, start the friendingInterval where we check // to be sure the hand is still close enough. If not, we terminate @@ -252,9 +257,8 @@ function makeFriends(id) { function startFriending(id, hand) { var count = 0; debug("friending", id, "hand", hand); + // do we need to do this? friendingId = id; - pendingFriendAckFrom = undefined; - latestFriendRequestFrom = undefined; state = STATES.friending; Controller.triggerHapticPulse(FRIENDING_HAPTIC_STRENGTH, HAPTIC_DURATION, handToHaptic(currentHand)); @@ -274,6 +278,10 @@ function startFriending(id, hand) { // gotta go back to waiting debug(id, "moved, back to waiting"); friendingInterval = Script.clearInterval(friendingInterval); + messageSend({ + key: "done" + }); + friendingId = undefined; startHandshake(); } else if (count > FRIENDING_TIME/FRIENDING_INTERVAL) { debug("made friends with " + id); @@ -311,38 +319,41 @@ function messageHandler(channel, messageString, senderID) { } catch (e) { debug(e); } + debug("recv'd message:", message); switch (message.key) { case "friendRequest": if (state == STATES.inactive && message.id == MyAvatar.sessionUUID) { - latestFriendRequestFrom = senderID; - } else if (state == STATES.waiting && (pendingFriendAckFrom == senderID || !pendingFriendAckFrom)) { - // you are waiting for a friend request, so send the ack. Or, you and the other + friendingId = senderID; + } else if (state == STATES.waiting && message.id == MyAvatar.sessionUUID && (!friendingId || friendingId == senderID)) { + // you were waiting for a friend request, so send the ack. Or, you and the other // guy raced and both send friendRequests. Handle that too - pendingFriendAckFrom = senderID; + if (!friendingId) { + friendingId = senderID; + } messageSend({ key: "friendAck", id: senderID, hand: handToString(currentHand) }); } - // TODO: ponder keeping this up-to-date during - // other states? + // TODO: check to see if the person we are trying to friend sent this to someone else, + // and try again break; case "friendAck": - if (state == STATES.waiting && message.id == MyAvatar.sessionUUID) { - if (pendingFriendAckFrom && senderID != pendingFriendAckFrom) { - debug("ignoring friendAck from", senderID, ", waiting on", pendingFriendAckFrom); - break; - } + if (state == STATES.waiting && message.id == MyAvatar.sessionUUID && (!friendingId || friendingId == senderID)) { // start friending... + friendingId = senderID; startFriending(senderID, message.hand); } + // TODO: check to see if we are waiting for this but the person we are friending sent it to + // someone else, and try again break; case "friending": - if (state == STATES.waiting && senderID == latestFriendRequestFrom) { + if (state == STATES.waiting && senderID == friendingId) { if (message.id != MyAvatar.sessionUUID) { - // for now, just ignore these. Hmm - debug("ignoring friending message", message, "from", senderID); + // the person we were trying to friend is friending someone else + // so try again + startHandshake(); break; } startFriending(senderID, message.hand); @@ -352,22 +363,22 @@ function messageHandler(channel, messageString, senderID) { if (state == STATES.friending && friendingId == senderID) { // if they are done, and didn't friend us, terminate our // friending - if (message.friendId !== friendingId) { + if (message.friendId !== MyAvatar.sessionUUID) { if (friendingInterval) { friendingInterval = Script.clearInterval(friendingInterval); } // 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) + friendingId = undefined; startHandshake(); } } else { - // if waiting or inactive, lets clear the pending stuff - if (pendingFriendAckFrom == senderID || latestFriendRequestFrom == senderID) { - if (state == STATES.inactive) { - pendingFriendAckFrom = undefined; - latestFriendRequestFrom = undefined; - } else { + // if waiting or inactive, lets clear the friending id. If in makingFriends, + // do nothing (so you see the red for a bit) + if (state != STATES.makingFriends && friendingId == senderID) { + friendingId = undefined; + if (state != STATES.inactive) { startHandshake(); } } @@ -437,3 +448,5 @@ Script.scriptEnding.connect(function () { } }); +}()); // END LOCAL_SCOPE + From d8719ac3a9b4a7db1b9ae695c6b579da61818c93 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Wed, 15 Mar 2017 11:15:58 -0700 Subject: [PATCH 6/7] cleanup, switch to overlays with texture swapping, move overlay to between hands --- scripts/system/makeUserConnection.js | 93 ++++++++++++++++++++++------ 1 file changed, 73 insertions(+), 20 deletions(-) diff --git a/scripts/system/makeUserConnection.js b/scripts/system/makeUserConnection.js index 881a828a17..e97d1074e3 100644 --- a/scripts/system/makeUserConnection.js +++ b/scripts/system/makeUserConnection.js @@ -25,23 +25,30 @@ const STATE_STRINGS = ["inactive", "waiting", "friending", "makingFriends"]; const WAITING_INTERVAL = 100; // ms const FRIENDING_INTERVAL = 100; // ms const FRIENDING_TIME = 3000; // ms -const ENTITY_COLORS = [{red: 0x00, green: 0xFF, blue: 0x00}, {red: 0x00, green: 0x00, blue: 0xFF}, {red: 0xFF, green: 0x00, blue: 0x00}]; const FRIENDING_HAPTIC_STRENGTH = 0.5; const FRIENDING_SUCCESS_HAPTIC_STRENGTH = 1.0; const HAPTIC_DURATION = 20; +const MODEL_URL = "http://hifi-content.s3.amazonaws.com/alan/dev/Test/sphere-3-color.fbx"; +const TEXTURES = [ + {"Texture": "http://hifi-content.s3.amazonaws.com/alan/dev/Test/sphere-3-color.fbx/sphere-3-color.fbm/green-50pct-opaque-64.png"}, + {"Texture": "http://hifi-content.s3.amazonaws.com/alan/dev/Test/sphere-3-color.fbx/sphere-3-color.fbm/blue-50pct-opaque-64.png"}, + {"Texture": "http://hifi-content.s3.amazonaws.com/alan/dev/Test/sphere-3-color.fbx/sphere-3-color.fbm/red-50pct-opaque-64.png"} +]; var currentHand; var state = STATES.inactive; var friendingInterval; -var entity; +var overlay; var animHandlerId; var entityDimensionMultiplier = 1.0; var friendingId; +var friendingHand; function debug() { var stateString = "<" + STATE_STRINGS[state] + ">"; var versionString = "v" + version; - print.apply(null, [].concat.apply([label, versionString, stateString, friendingId], [].map.call(arguments, JSON.stringify))); + var friending = "[" + friendingId + "/" + friendingHand + "]"; + print.apply(null, [].concat.apply([label, versionString, stateString, friending], [].map.call(arguments, JSON.stringify))); } function handToString(hand) { @@ -53,6 +60,16 @@ function handToString(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; @@ -62,11 +79,13 @@ function handToHaptic(hand) { return -1; } - +// 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 +// of the avatar? function getHandPosition(avatar, hand) { if (!hand) { - debug("calling getHandPosition with no hand!"); - return; + debug("calling getHandPosition with no hand! (returning avatar position but this is a BUG)"); + return avatar.position; } var jointName = handToString(hand) + "Middle1"; return avatar.getJointPosition(avatar.getJointIndex(jointName)); @@ -92,33 +111,43 @@ function shakeHandsAnimation(animationProperties) { // this is called frequently, but usually does nothing function updateVisualization() { if (state == STATES.inactive) { - if (entity) { - entity = Entities.deleteEntity(entity); + if (overlay) { + overlay = Overlays.deleteOverlay(overlay); } return; } - var color = ENTITY_COLORS[state-1]; + var textures = TEXTURES[state-1]; var position = getHandPosition(MyAvatar, currentHand); // TODO: make the size scale with avatar, up to // the actual size of MAX_AVATAR_DISTANCE var wrist = MyAvatar.getJointPosition(MyAvatar.getJointIndex(handToString(currentHand))); var d = entityDimensionMultiplier * Vec3.distance(wrist, position); - var dimension = {x: d, y: d, z: d}; - if (!entity) { - var props = { - type: "Sphere", - color: color, - position: position, - dimensions: dimension + if (friendingId) { + // put the position between the 2 hands, if we have a friendingId + var other = AvatarList.getAvatar(friendingId); + if (other) { + var otherHand = getHandPosition(other, stringToHand(friendingHand)); + position = Vec3.sum(position, Vec3.multiply(0.5, Vec3.subtract(otherHand, position))); } - entity = Entities.addEntity(props); + } + var dimension = {x: d, y: d, z: d}; + if (!overlay) { + var props = { + url: MODEL_URL, + position: position, + dimensions: dimension, + textures: textures + }; + overlay = Overlays.addOverlay("model", props); } else { - Entities.editEntity(entity, {dimensions: dimension, position: position, color: color}); + Overlays.editOverlay(overlay, {textures: textures}); + Overlays.editOverlay(overlay, {dimensions: dimension, position: position}); } } + function findNearbyAvatars(nearestOnly) { var handPos = getHandPosition(MyAvatar, currentHand); var minDistance = MAX_AVATAR_DISTANCE; @@ -141,6 +170,15 @@ function findNearbyAvatars(nearestOnly) { return nearbyAvatars; } +// As currently implemented, we select the closest avatar (if close enough) and send +// them a friendRequest, or if someone already has sent us one, we just send the friendAck +// back to them. If nobody is close enough or has sent us a friendRequest, we just wait +// transition to waiting and wait for a friendRequest. If the 2 people who want to connect +// are both somewhat out of range when they initiate the shake, then neither gets a message +// and they both just stand there with their hands out. +// Ideally we'd either show that (so they ungrip/regrip and adjust position), or do what I +// initially did and start an interval to look for nearby avatars. The issue with the latter +// is this introduces some race condition we may need to handle (hence I didn't do it yet). function startHandshake(fromKeyboard) { if (fromKeyboard) { debug("adding animation"); @@ -170,7 +208,7 @@ function startHandshake(fromKeyboard) { messageSend({ key: "friendRequest", id: friendingId, - hand: handToString(nearestAvatar.hand) + hand: handToString(currentHand) }); } } @@ -179,9 +217,14 @@ function startHandshake(fromKeyboard) { function endHandshake() { debug("ending handshake for", currentHand); currentHand = undefined; + // note that setting the state to inactive should really + // only be done here, unless we change how the triggering works, + // as we ignore the key release event when inactive. See updateTriggers + // below. state = STATES.inactive; if (friendingInterval) { friendingId = undefined; + friendingHand = undefined; friendingInterval = Script.clearInterval(friendingInterval); // send done to let friend know you are not making friends now messageSend({ @@ -211,6 +254,7 @@ function updateTriggers(value, fromKeyboard, hand) { startHandshake(fromKeyboard); } } else { + // TODO: should we end handshake even when inactive? Ponder if (state != STATES.inactive) { endHandshake(); } else { @@ -247,6 +291,7 @@ function makeFriends(id) { Script.setTimeout(function () { state = STATES.waiting; friendingId = undefined; + friendingHand = undefined; entityDimensionMultiplier = 1.0; }, 1000); } @@ -270,7 +315,8 @@ function startFriending(id, hand) { }); friendingInterval = Script.setInterval(function () { - entityDimensionMultiplier = 1.0 + 2.0 * ++count * FRIENDING_INTERVAL / FRIENDING_TIME; + count += 1; + entityDimensionMultiplier = 1.0 + 2.0 * count * FRIENDING_INTERVAL / FRIENDING_TIME; if (state != STATES.friending) { debug("stopping friending interval, state changed"); friendingInterval = Script.clearInterval(friendingInterval); @@ -282,6 +328,7 @@ function startFriending(id, hand) { key: "done" }); friendingId = undefined; + friendingHand = undefined; startHandshake(); } else if (count > FRIENDING_TIME/FRIENDING_INTERVAL) { debug("made friends with " + id); @@ -324,11 +371,13 @@ function messageHandler(channel, messageString, senderID) { case "friendRequest": if (state == STATES.inactive && message.id == MyAvatar.sessionUUID) { friendingId = senderID; + friendingHand = message.hand; } else if (state == STATES.waiting && message.id == MyAvatar.sessionUUID && (!friendingId || friendingId == senderID)) { // you were waiting for a friend request, so send the ack. Or, you and the other // guy raced and both send friendRequests. Handle that too if (!friendingId) { friendingId = senderID; + friendingHand = message.hand; } messageSend({ key: "friendAck", @@ -343,6 +392,7 @@ function messageHandler(channel, messageString, senderID) { if (state == STATES.waiting && message.id == MyAvatar.sessionUUID && (!friendingId || friendingId == senderID)) { // start friending... friendingId = senderID; + friendingHand = message.hand; startFriending(senderID, message.hand); } // TODO: check to see if we are waiting for this but the person we are friending sent it to @@ -371,6 +421,7 @@ function messageHandler(channel, messageString, senderID) { // value for isKeyboard, as we should not change the animation // state anyways (if any) friendingId = undefined; + friendingHand = undefined; startHandshake(); } } else { @@ -378,6 +429,7 @@ function messageHandler(channel, messageString, senderID) { // do nothing (so you see the red for a bit) if (state != STATES.makingFriends && friendingId == senderID) { friendingId = undefined; + friendingHand = undefined; if (state != STATES.inactive) { startHandshake(); } @@ -386,6 +438,7 @@ function messageHandler(channel, messageString, senderID) { break; default: debug("unknown message", message); + break; } } From 4f6123ee294a561d1ddaf07f9d83f79a92cedd20 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Wed, 15 Mar 2017 16:41:07 -0700 Subject: [PATCH 7/7] more messaging rework - dealing with edges --- scripts/system/makeUserConnection.js | 189 +++++++++++++++++---------- 1 file changed, 120 insertions(+), 69 deletions(-) diff --git a/scripts/system/makeUserConnection.js b/scripts/system/makeUserConnection.js index e97d1074e3..d73eaedddb 100644 --- a/scripts/system/makeUserConnection.js +++ b/scripts/system/makeUserConnection.js @@ -38,17 +38,19 @@ const TEXTURES = [ var currentHand; var state = STATES.inactive; var friendingInterval; +var waitingInterval; var overlay; var animHandlerId; var entityDimensionMultiplier = 1.0; var friendingId; var friendingHand; +var waitingList = {}; function debug() { var stateString = "<" + STATE_STRINGS[state] + ">"; var versionString = "v" + version; var friending = "[" + friendingId + "/" + friendingHand + "]"; - print.apply(null, [].concat.apply([label, versionString, stateString, friending], [].map.call(arguments, JSON.stringify))); + print.apply(null, [].concat.apply([label, versionString, stateString, JSON.stringify(waitingList), friending], [].map.call(arguments, JSON.stringify))); } function handToString(hand) { @@ -85,6 +87,7 @@ function handToHaptic(hand) { function getHandPosition(avatar, hand) { if (!hand) { debug("calling getHandPosition with no hand! (returning avatar position but this is a BUG)"); + debug(new Error().stack); return avatar.position; } var jointName = handToString(hand) + "Middle1"; @@ -148,33 +151,42 @@ function updateVisualization() { } -function findNearbyAvatars(nearestOnly) { - var handPos = getHandPosition(MyAvatar, currentHand); - var minDistance = MAX_AVATAR_DISTANCE; - var nearbyAvatars = []; - AvatarList.getAvatarIdentifiers().forEach(function (identifier) { - if (!identifier) { return; } - var avatar = AvatarList.getAvatar(identifier); - var distanceR = Vec3.distance(getHandPosition(avatar, Controller.Standard.RightHand), handPos); - var distanceL = Vec3.distance(getHandPosition(avatar, Controller.Standard.LeftHand), handPos); - var distance = Math.min(distanceL, distanceR); - if (distance < minDistance) { - if (nearestOnly) { - minDistance = distance; - nearbyAvatars = []; - } - var hand = (distance == distanceR ? Controller.Standard.RightHand : Controller.Standard.LeftHand); - nearbyAvatars.push({avatar: identifier, hand: hand}); +function isNearby(id, hand) { + if (currentHand) { + var handPos = getHandPosition(MyAvatar, currentHand); + var avatar = AvatarList.getAvatar(id); + if (avatar) { + var otherHand = stringToHand(hand); + var distance = Vec3.distance(getHandPosition(avatar, otherHand), handPos); + return (distance < MAX_AVATAR_DISTANCE); } - }); - return nearbyAvatars; + } + return false; } -// As currently implemented, we select the closest avatar (if close enough) and send -// them a friendRequest, or if someone already has sent us one, we just send the friendAck -// back to them. If nobody is close enough or has sent us a friendRequest, we just wait -// transition to waiting and wait for a friendRequest. If the 2 people who want to connect -// are both somewhat out of range when they initiate the shake, then neither gets a message +function findNearestWaitingAvatar() { + var handPos = getHandPosition(MyAvatar, currentHand); + 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 distance = Vec3.distance(getHandPosition(avatar, hand), handPos); + if (distance < minDistance) { + minDistance = distance; + nearestAvatar = {avatar: identifier, hand: hand}; + } + } + }); + return nearestAvatar; +} + + +// As currently implemented, we select the closest waiting avatar (if close enough) and send +// them a friendRequest. If nobody is close enough we just wait for a friendRequest. If the +// 2 people who want to connect are both somewhat out of range when they initiate the shake, +// then neither gets a message // and they both just stand there with their hands out. // Ideally we'd either show that (so they ungrip/regrip and adjust position), or do what I // initially did and start an interval to look for nearby avatars. The issue with the latter @@ -191,26 +203,26 @@ function startHandshake(fromKeyboard) { debug("starting handshake for", currentHand); state = STATES.waiting; entityDimensionMultiplier = 1.0; - // if we have a recent friendRequest, send an ack back. - // TODO: be sure the friendingId resets when we get the done message - if (friendingId) { - debug("sending friendAck to", friendingId); + friendingId = undefined; + friendingHand = undefined; + var nearestAvatar = findNearestWaitingAvatar(); + if (nearestAvatar.avatar) { + friendingId = nearestAvatar.avatar; + friendingHand = handToString(nearestAvatar.hand); + debug("sending friendRequest to", friendingId); messageSend({ - key: "friendAck", + key: "friendRequest", id: friendingId, hand: handToString(currentHand) }); } else { - var nearestAvatar = findNearbyAvatars(true)[0]; - if (nearestAvatar) { - friendingId = nearestAvatar.avatar; - debug("sending friendRequest to", friendingId); - messageSend({ - key: "friendRequest", - id: friendingId, - hand: handToString(currentHand) - }); - } + // send waiting message + debug("sending waiting message"); + messageSend({ + key: "waiting", + hand: handToString(currentHand) + }); + lookForWaitingAvatar(); } } @@ -222,9 +234,9 @@ function endHandshake() { // as we ignore the key release event when inactive. See updateTriggers // below. state = STATES.inactive; + friendingId = undefined; + friendingHand = undefined; if (friendingInterval) { - friendingId = undefined; - friendingHand = undefined; friendingInterval = Script.clearInterval(friendingInterval); // send done to let friend know you are not making friends now messageSend({ @@ -267,14 +279,39 @@ function messageSend(message) { Messages.sendMessage(MESSAGE_CHANNEL, JSON.stringify(message)); } -function isNearby(id, hand) { - var nearbyAvatars = findNearbyAvatars(); - for(var i = 0; i < nearbyAvatars.length; i++) { - if (nearbyAvatars[i].avatar == id && handToString(nearbyAvatars[i].hand) == hand) { - return true; - } +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 friendingId (which means you got their + // message before noticing they were in range in this loop) + + // just in case we reenter before stopping + if (waitingInterval) { + waitingInterval = Script.clearInterval(waitingInterval); } - return false; + debug("started looking for waiting avatars"); + waitingInterval = Script.setInterval(function () { + if (state == STATES.waiting && !friendingId) { + // find the closest in-range avatar, and send friend request + // TODO: this is same code as in startHandshake - get this + // cleaned up. + var nearestAvatar = findNearestWaitingAvatar(); + if (nearestAvatar.avatar) { + friendingId = nearestAvatar.avatar; + friendingHand = handToString(nearestAvatar.hand); + debug("sending friendRequest to", friendingId); + messageSend({ + key: "friendRequest", + id: friendingId, + hand: handToString(currentHand) + }); + } + } else { + // something happened, stop looking for avatars to friend + waitingInterval = Script.clearInterval(waitingInterval); + debug("stopped looking for waiting avatars"); + } + }, WAITING_INTERVAL); } // this should be where we make the appropriate friend call. For now just make the @@ -289,12 +326,12 @@ function makeFriends(id) { state = STATES.makingFriends; // now that we made friends, reset everything Script.setTimeout(function () { - state = STATES.waiting; friendingId = undefined; friendingHand = undefined; entityDimensionMultiplier = 1.0; }, 1000); } + // we change states, start the friendingInterval 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 @@ -304,6 +341,7 @@ function startFriending(id, hand) { debug("friending", id, "hand", hand); // do we need to do this? friendingId = id; + friendingHand = hand; state = STATES.friending; Controller.triggerHapticPulse(FRIENDING_HAPTIC_STRENGTH, HAPTIC_DURATION, handToHaptic(currentHand)); @@ -327,8 +365,6 @@ function startFriending(id, hand) { messageSend({ key: "done" }); - friendingId = undefined; - friendingHand = undefined; startHandshake(); } else if (count > FRIENDING_TIME/FRIENDING_INTERVAL) { debug("made friends with " + id); @@ -368,17 +404,18 @@ function messageHandler(channel, messageString, senderID) { } debug("recv'd message:", message); 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 "friendRequest": - if (state == STATES.inactive && message.id == MyAvatar.sessionUUID) { - friendingId = senderID; - friendingHand = message.hand; - } else if (state == STATES.waiting && message.id == MyAvatar.sessionUUID && (!friendingId || friendingId == senderID)) { + delete waitingList[senderID]; + if (state == STATES.waiting && message.id == MyAvatar.sessionUUID && (!friendingId || friendingId == senderID)) { // you were waiting for a friend request, so send the ack. Or, you and the other // guy raced and both send friendRequests. Handle that too - if (!friendingId) { - friendingId = senderID; - friendingHand = message.hand; - } + friendingId = senderID; + friendingHand = message.hand; messageSend({ key: "friendAck", id: senderID, @@ -389,17 +426,32 @@ function messageHandler(channel, messageString, senderID) { // and try again break; case "friendAck": - if (state == STATES.waiting && message.id == MyAvatar.sessionUUID && (!friendingId || friendingId == senderID)) { - // start friending... - friendingId = senderID; - friendingHand = message.hand; - startFriending(senderID, message.hand); + delete waitingList[senderID]; + if (state == STATES.waiting && (!friendingId || friendingId == senderID)) { + if (message.id == MyAvatar.sessionUUID) { + // start friending... + friendingId = senderID; + friendingHand = message.hand; + startFriending(senderID, message.hand); + } else { + if (friendingId) { + // this is for someone else (we lost race in friendRequest), + // so lets start over + startHandshake(); + } + } } // TODO: check to see if we are waiting for this but the person we are friending sent it to // someone else, and try again break; case "friending": + delete waitingList[senderID]; if (state == STATES.waiting && senderID == friendingId) { + // temporary logging + if (friendingHand != message.hand) { + debug("friending hand", friendingHand, "not same as friending hand in message", message.hand); + } + friendingHand = message.hand; if (message.id != MyAvatar.sessionUUID) { // the person we were trying to friend is friending someone else // so try again @@ -410,6 +462,7 @@ function messageHandler(channel, messageString, senderID) { } break; case "done": + delete waitingList[senderID]; if (state == STATES.friending && friendingId == senderID) { // if they are done, and didn't friend us, terminate our // friending @@ -420,8 +473,6 @@ function messageHandler(channel, messageString, senderID) { // 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) - friendingId = undefined; - friendingHand = undefined; startHandshake(); } } else { @@ -496,8 +547,8 @@ Script.scriptEnding.connect(function () { Controller.keyReleaseEvent.disconnect(keyReleaseEvent); debug("disconnecting updateVisualization"); Script.update.disconnect(updateVisualization); - if (entity) { - entity = Entities.deleteEntity(entity); + if (overlay) { + overlay = Overlays.deleteOverlay(overlay); } });