Merge branch 'master' of github.com:highfidelity/hifi into possible-fix-for-2068

This commit is contained in:
Seth Alves 2016-10-28 16:41:39 -07:00
commit 3ad748cfe2
18 changed files with 210 additions and 149 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 82 KiB

View file

@ -229,7 +229,7 @@ public:
qint64 getCurrentSessionRuntime() const { return _sessionRunTimer.elapsed(); }
bool isAboutToQuit() const { return _aboutToQuit; }
bool isAboutToQuit() const override { return _aboutToQuit; }
bool isPhysicsEnabled() const { return _physicsEnabled; }
// the isHMDMode is true whenever we use the interface from an HMD and not a standard flat display

View file

@ -95,7 +95,13 @@ bool RenderableWebEntityItem::buildWebSurface(QSharedPointer<EntityTreeRenderer>
auto deleter = [](OffscreenQmlSurface* webSurface) {
AbstractViewStateInterface::instance()->postLambdaEvent([webSurface] {
webSurface->deleteLater();
if (AbstractViewStateInterface::instance()->isAboutToQuit()) {
// WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown
// if the application has already stopped its event loop, delete must be explicit
delete webSurface;
} else {
webSurface->deleteLater();
}
});
};
_webSurface = QSharedPointer<OffscreenQmlSurface>(new OffscreenQmlSurface(), deleter);
@ -333,6 +339,7 @@ void RenderableWebEntityItem::destroyWebSurface() {
if (rootItem) {
QObject* obj = rootItem->findChild<QObject*>("webEngineView");
if (obj) {
// stop loading
QMetaObject::invokeMethod(obj, "stop");
}
}

View file

@ -24,8 +24,11 @@ void UserActivityLoggerScriptingInterface::toggledAway(bool isAway) {
logAction("toggled_away", { { "is_away", isAway } });
}
void UserActivityLoggerScriptingInterface::tutorialProgress(QString stepName, int stepNumber, float secondsToComplete, float tutorialElapsedTime) {
void UserActivityLoggerScriptingInterface::tutorialProgress( QString stepName, int stepNumber, float secondsToComplete,
float tutorialElapsedTime, QString tutorialRunID, int tutorialVersion) {
logAction("tutorial_progress", {
{ "tutorial_run_id", tutorialRunID },
{ "tutorial_version", tutorialVersion },
{ "step", stepName },
{ "step_number", stepNumber },
{ "seconds_to_complete", secondsToComplete },

View file

@ -23,7 +23,8 @@ public:
Q_INVOKABLE void enabledEdit();
Q_INVOKABLE void openedMarketplace();
Q_INVOKABLE void toggledAway(bool isAway);
Q_INVOKABLE void tutorialProgress(QString stepName, int stepNumber, float secondsToComplete, float tutorialElapsedTime);
Q_INVOKABLE void tutorialProgress(QString stepName, int stepNumber, float secondsToComplete,
float tutorialElapsedTime, QString tutorialRunID = "", int tutorialVersion = 0);
private:
void logAction(QString action, QJsonObject details = {});

View file

@ -49,7 +49,7 @@ Socket::Socket(QObject* parent, bool shouldChangeSocketOptions) :
connect(&_udpSocket, &QAbstractSocket::stateChanged, this, &Socket::handleStateChanged);
// in order to help track down the zombie server bug, add a timer to check if we missed a readyRead
const int READY_READ_BACKUP_CHECK_MSECS = 10 * 1000;
const int READY_READ_BACKUP_CHECK_MSECS = 2 * 1000;
connect(_readyReadBackupTimer, &QTimer::timeout, this, &Socket::checkForReadyReadBackup);
_readyReadBackupTimer->start(READY_READ_BACKUP_CHECK_MSECS);
}

View file

@ -40,7 +40,9 @@ public:
virtual glm::vec3 getAvatarPosition() const = 0;
virtual bool isAboutToQuit() const = 0;
virtual void postLambdaEvent(std::function<void()> f) = 0;
virtual qreal getDevicePixelRatio() = 0;
virtual render::ScenePointer getMain3DScene() = 0;

View file

@ -56,10 +56,6 @@ void BatchLoader::start() {
// If BatchLoader is deleted before the callback is called, the subsequent "emit" call will not do
// anything.
ScriptCacheSignalProxy* proxy = new ScriptCacheSignalProxy(scriptCache.data());
scriptCache->getScriptContents(url.toString(), [proxy](const QString& url, const QString& contents, bool isURL, bool success) {
proxy->receivedContent(url, contents, isURL, success);
proxy->deleteLater();
}, false);
connect(proxy, &ScriptCacheSignalProxy::contentAvailable, this, [this](const QString& url, const QString& contents, bool isURL, bool success) {
if (isURL && success) {
@ -75,6 +71,11 @@ void BatchLoader::start() {
emit finished(_data);
}
});
scriptCache->getScriptContents(url.toString(), [proxy](const QString& url, const QString& contents, bool isURL, bool success) {
proxy->receivedContent(url, contents, isURL, success);
proxy->deleteLater();
}, false);
}
}

View file

@ -146,6 +146,8 @@ public:
Q_INVOKABLE void requestGarbageCollection() { collectGarbage(); }
Q_INVOKABLE QUuid generateUUID() { return QUuid::createUuid(); }
bool isFinished() const { return _isFinished; } // used by Application and ScriptWidget
bool isRunning() const { return _isRunning; } // used by ScriptWidget

View file

@ -16,7 +16,7 @@
var MESSAGE_CHANNEL = "io.highfidelity.summon-crowd";
print('crowd-agent version 3');
print('crowd-agent version 4');
/* Observations:
- File urls for AC scripts silently fail. Use a local server (e.g., python SimpleHTTPServer) for development.
@ -34,19 +34,56 @@ function getSound(data, callback) { // callback(sound) when downloaded (which ma
if (sound.downloaded) {
return callback(sound);
}
sound.ready.connect(function () { callback(sound); });
function onDownloaded() {
sound.ready.disconnect(onDownloaded);
callback(sound);
}
sound.ready.connect(onDownloaded);
}
function onFinishedPlaying() {
messageSend({key: 'finishedSound'});
}
var attachment;
var stopper;
function clearStopper() {
if (!stopper) {
return;
}
Script.clearTimeout(stopper);
stopper = null;
}
function stopAgent(parameters) {
function stop() {
clearStopper();
if (attachment) {
Avatar.detachOne(attachment.modelURL, attachment.jointName);
attachment = undefined;
}
Agent.isListeningToAudioStream = false;
Agent.isAvatar = false;
print('crowd-agent stopped', JSON.stringify(parameters), JSON.stringify(Agent));
}
// Shutting down lots of agents at once can be hard on other parts of the system. (See fogbugz 2095.)
// For now, accept a parameter to delay for the given number of milliseconds before stopping.
// (We cannot count on summoning scripts to spread out the STOP messages, because they might be doing so
// on scriptEnding, in which case they are not allowed to create new delays.)
if (parameters.delay) {
if (!stopper) { // Let the first stopper do the deed.
stopper = Script.setTimeout(stop, parameters.delay);
}
} else {
stop();
}
}
var MILLISECONDS_IN_SECOND = 1000;
function startAgent(parameters) { // Can also be used to update.
print('crowd-agent starting params', JSON.stringify(parameters), JSON.stringify(Agent));
clearStopper();
var wasOff = !Agent.isAvatar;
Agent.isAvatar = true;
Agent.isListeningToAudioStream = true; // Send silence when not chattering.
if (parameters.position) {
Avatar.position = parameters.position;
}
@ -56,6 +93,11 @@ function startAgent(parameters) { // Can also be used to update.
if (parameters.skeletonModelURL) {
Avatar.skeletonModelURL = parameters.skeletonModelURL;
}
if (parameters.listen != undefined) {
Agent.isListeningToAudioStream = parameters.listen; // Send silence when not chattering.
} else if (wasOff) {
Agent.isListeningToAudioStream = true;
}
if (parameters.soundData) {
getSound(parameters.soundData, function (sound) {
Script.setTimeout(onFinishedPlaying, sound.duration * MILLISECONDS_IN_SECOND);
@ -74,14 +116,6 @@ function startAgent(parameters) { // Can also be used to update.
}
print('crowd-agent avatars started');
}
function stopAgent(parameters) {
if (attachment) {
Avatar.detachOne(attachment.modelURL, attachment.jointName);
attachment = undefined;
}
Agent.isAvatar = false;
print('crowd-agent stopped', JSON.stringify(parameters), JSON.stringify(Agent));
}
function messageHandler(channel, messageString, senderID) {
if (channel !== MESSAGE_CHANNEL) {

View file

@ -21,6 +21,10 @@ var NOMINAL_LOAD_TIME = 30; // seconds
var MAXIMUM_LOAD_TIME = NOMINAL_LOAD_TIME * 2;
var MINIMUM_AVATARS = 25; // changeable by prompt
// If we add or remove things too quickly, we get problems (e.g., audio, fogbugz 2095).
// For now, spread them out this timing apart.
var SPREAD_TIME_MS = 500;
var DENSITY = 0.3; // square meters per person. Some say 10 sq ft is arm's length (0.9m^2), 4.5 is crowd (0.4m^2), 2.5 is mosh pit (0.2m^2).
var SOUND_DATA = {url: "http://hifi-content.s3.amazonaws.com/howard/sounds/piano1.wav"};
var AVATARS_CHATTERING_AT_ONCE = 4; // How many of the agents should we request to play SOUND at once.
@ -34,7 +38,7 @@ var ANIMATION_DATA = {
"loopFlag": true
};
var version = 3;
var version = 4;
function debug() {
print.apply(null, [].concat.apply(['hrs fixme', version], [].map.call(arguments, JSON.stringify)));
}
@ -86,6 +90,7 @@ function nextAfter(array, id) { // Wrapping next element in array after id.
var summonedAgents = [];
var chattering = [];
var accumulatedDelay = 0;
var MESSAGE_CHANNEL = "io.highfidelity.summon-crowd";
function messageSend(message) {
Messages.sendMessage(MESSAGE_CHANNEL, JSON.stringify(message));
@ -106,25 +111,29 @@ function messageHandler(channel, messageString, senderID) {
}
switch (message.key) {
case "hello":
// There can be avatars we've summoned that do not yet appear in the AvatarList.
avatarIdentifiers = without(AvatarList.getAvatarIdentifiers(), summonedAgents);
debug('present', avatarIdentifiers, summonedAgents);
if ((summonedAgents.length + avatarIdentifiers.length) < MINIMUM_AVATARS) {
var chatter = chattering.length < AVATARS_CHATTERING_AT_ONCE;
if (chatter) {
chattering.push(senderID);
Script.setTimeout(function () {
// There can be avatars we've summoned that do not yet appear in the AvatarList.
avatarIdentifiers = without(AvatarList.getAvatarIdentifiers(), summonedAgents);
debug('present', avatarIdentifiers, summonedAgents);
if ((summonedAgents.length + avatarIdentifiers.length) < MINIMUM_AVATARS) {
var chatter = chattering.length < AVATARS_CHATTERING_AT_ONCE;
if (chatter) {
chattering.push(senderID);
}
summonedAgents.push(senderID);
messageSend({
key: 'SUMMON',
rcpt: senderID,
position: Vec3.sum(MyAvatar.position, {x: coord(), y: 0, z: coord()}),
orientation: Quat.fromPitchYawRollDegrees(0, Quat.safeEulerAngles(MyAvatar.orientation).y + (turnSpread * (Math.random() - 0.5)), 0),
soundData: chatter && SOUND_DATA,
listen: true,
skeletonModelURL: "http://hifi-content.s3.amazonaws.com/howard/resources/meshes/defaultAvatar_full.fst",
animationData: ANIMATION_DATA
});
}
summonedAgents.push(senderID);
messageSend({
key: 'SUMMON',
rcpt: senderID,
position: Vec3.sum(MyAvatar.position, {x: coord(), y: 0, z: coord()}),
orientation: Quat.fromPitchYawRollDegrees(0, Quat.safeEulerAngles(MyAvatar.orientation).y + (turnSpread * (Math.random() - 0.5)), 0),
soundData: chatter && SOUND_DATA,
skeletonModelURL: "http://hifi-content.s3.amazonaws.com/howard/resources/meshes/defaultAvatar_full.fst",
animationData: ANIMATION_DATA
});
}
}, accumulatedDelay);
accumulatedDelay += SPREAD_TIME_MS; // assume we'll get all the hello respsponses more or less together.
break;
case "finishedSound": // Give someone else a chance.
chattering = without(chattering, [senderID]);
@ -147,13 +156,15 @@ Messages.subscribe(MESSAGE_CHANNEL);
Messages.messageReceived.connect(messageHandler);
Script.scriptEnding.connect(function () {
debug('stopping agents', summonedAgents);
summonedAgents.forEach(function (id) { messageSend({key: 'STOP', rcpt: id}); });
Messages.messageReceived.disconnect(messageHandler); // don't respond to any messages during shutdown
accumulatedDelay = 0;
summonedAgents.forEach(function (id) {
messageSend({key: 'STOP', rcpt: id, delay: accumulatedDelay});
accumulatedDelay += SPREAD_TIME_MS;
});
debug('agents stopped');
Script.setTimeout(function () {
Messages.messageReceived.disconnect(messageHandler);
Messages.unsubscribe(MESSAGE_CHANNEL);
debug('unsubscribed');
}, 500);
Messages.unsubscribe(MESSAGE_CHANNEL);
debug('unsubscribed');
});
var fail = false, results = "";
@ -257,7 +268,7 @@ function doRender(continuation) {
}
config.newStats.connect(onNewStats);
startTwirl(720, 1, 15, 0.08, function () {
startTwirl(720, 1, 20, 0.08, function () {
var end = Date.now();
config.newStats.disconnect(onNewStats);
addResult('frame rate', 1000 * frames / (end - start),
@ -270,7 +281,7 @@ function doRender(continuation) {
http://hifi-content.s3.amazonaws.com/howard/scripts/tests/performance/crowd-agent.js?v=3\n\
on your domain server.";
} else if (total < MINIMUM_AVATARS) {
fail = "FAIL: Only " + summonedAgents.length + " avatars reported. Missing " + (MINIMUM_AVATARS - total) + ".";
fail = "FAIL: Only " + summonedAgents.length + " agents reported. Now missing " + (MINIMUM_AVATARS - total) + " avatars, total.";
}
}
continuation();

View file

@ -13,19 +13,26 @@
//
// See crowd-agent.js
var version = 1;
var version = 2;
var label = "summon";
function debug() {
print.apply(null, [].concat.apply([label, version], [].map.call(arguments, JSON.stringify)));
}
var MINIMUM_AVATARS = 25; // We will summon agents to produce this many total. (Of course, there might not be enough agents.)
var N_LISTENING = MINIMUM_AVATARS - 1;
var AVATARS_CHATTERING_AT_ONCE = 4; // How many of the agents should we request to play SOUND_DATA at once.
// If we add or remove things too quickly, we get problems (e.g., audio, fogbugz 2095).
// For now, spread them out this timing apart.
var SPREAD_TIME_MS = 500;
var DENSITY = 0.3; // square meters per person. Some say 10 sq ft is arm's length (0.9m^2), 4.5 is crowd (0.4m^2), 2.5 is mosh pit (0.2m^2).
var SOUND_DATA = {url: "http://howard-stearns.github.io/models/sounds/piano1.wav"};
var AVATARS_CHATTERING_AT_ONCE = 4; // How many of the agents should we request to play SOUND at once.
var SOUND_DATA = {url: "http://hifi-content.s3.amazonaws.com/howard/sounds/piano1.wav"};
var NEXT_SOUND_SPREAD = 500; // millisecond range of how long to wait after one sound finishes, before playing the next
var ANIMATION_DATA = {
"url": "http://howard-stearns.github.io/models/resources/avatar/animations/idle.fbx",
// "url": "http://howard-stearns.github.io/models/resources/avatar/animations/walk_fwd.fbx", // alternative example
"url": "http://hifi-content.s3.amazonaws.com/howard/resources/avatar/animations/idle.fbx",
// "url": "http://hifi-content.s3.amazonaws.com/howard/resources/avatar/animations/walk_fwd.fbx", // alternative example
"startFrame": 0.0,
"endFrame": 300.0,
"timeScale": 1.0,
@ -45,6 +52,8 @@ function nextAfter(array, id) { // Wrapping next element in array after id.
var summonedAgents = [];
var chattering = [];
var nListening = 0;
var accumulatedDelay = 0;
var MESSAGE_CHANNEL = "io.highfidelity.summon-crowd";
function messageSend(message) {
Messages.sendMessage(MESSAGE_CHANNEL, JSON.stringify(message));
@ -65,25 +74,33 @@ function messageHandler(channel, messageString, senderID) {
}
switch (message.key) {
case "hello":
// There can be avatars we've summoned that do not yet appear in the AvatarList.
avatarIdentifiers = without(AvatarList.getAvatarIdentifiers(), summonedAgents);
debug('present', avatarIdentifiers, summonedAgents);
if ((summonedAgents.length + avatarIdentifiers.length) < MINIMUM_AVATARS ) {
var chatter = chattering.length < AVATARS_CHATTERING_AT_ONCE;
if (chatter) {
chattering.push(senderID);
Script.setTimeout(function () {
// There can be avatars we've summoned that do not yet appear in the AvatarList.
avatarIdentifiers = without(AvatarList.getAvatarIdentifiers(), summonedAgents);
debug('present', avatarIdentifiers, summonedAgents);
if ((summonedAgents.length + avatarIdentifiers.length) < MINIMUM_AVATARS ) {
var chatter = chattering.length < AVATARS_CHATTERING_AT_ONCE;
var listen = nListening < N_LISTENING;
if (chatter) {
chattering.push(senderID);
}
if (listen) {
nListening++;
}
summonedAgents.push(senderID);
messageSend({
key: 'SUMMON',
rcpt: senderID,
position: Vec3.sum(MyAvatar.position, {x: coord(), y: 0, z: coord()}),
orientation: Quat.fromPitchYawRollDegrees(0, Quat.safeEulerAngles(MyAvatar.orientation).y + (turnSpread * (Math.random() - 0.5)), 0),
soundData: chatter && SOUND_DATA,
listen: listen,
skeletonModelURL: "http://hifi-content.s3.amazonaws.com/howard/resources/meshes/defaultAvatar_full.fst",
animationData: ANIMATION_DATA
});
}
summonedAgents.push(senderID);
messageSend({
key: 'SUMMON',
rcpt: senderID,
position: Vec3.sum(MyAvatar.position, {x: coord(), y: 0, z: coord()}),
orientation: Quat.fromPitchYawRollDegrees(0, Quat.safeEulerAngles(MyAvatar.orientation).y + (turnSpread * (Math.random() - 0.5)), 0),
soundData: chatter && SOUND_DATA,
skeletonModelURL: "http://howard-stearns.github.io/models/resources/meshes/defaultAvatar_full.fst",
animationData: ANIMATION_DATA
});
}
}, accumulatedDelay);
accumulatedDelay += SPREAD_TIME_MS; // assume we'll get all the hello respsponses more or less together.
break;
case "finishedSound": // Give someone else a chance.
chattering = without(chattering, [senderID]);
@ -99,20 +116,22 @@ function messageHandler(channel, messageString, senderID) {
Window.alert("Someone else is summoning avatars.");
break;
default:
print("crowd-agent received unrecognized message:", messageString);
print("crowd summon.js received unrecognized message:", messageString);
}
}
Messages.subscribe(MESSAGE_CHANNEL);
Messages.messageReceived.connect(messageHandler);
Script.scriptEnding.connect(function () {
debug('stopping agents', summonedAgents);
summonedAgents.forEach(function (id) { messageSend({key: 'STOP', rcpt: id}); });
Messages.messageReceived.disconnect(messageHandler); // don't respond to any messages during shutdown
accumulatedDelay = 0;
summonedAgents.forEach(function (id) {
messageSend({key: 'STOP', rcpt: id, delay: accumulatedDelay});
accumulatedDelay += SPREAD_TIME_MS;
});
debug('agents stopped');
Script.setTimeout(function () {
Messages.messageReceived.disconnect(messageHandler);
Messages.unsubscribe(MESSAGE_CHANNEL);
debug('unsubscribed');
}, 500);
Messages.unsubscribe(MESSAGE_CHANNEL);
debug('unsubscribed');
});
messageSend({key: 'HELO'}); // Ask agents to report in now.
@ -120,9 +139,9 @@ Script.setTimeout(function () {
var total = AvatarList.getAvatarIdentifiers().length;
if (0 === summonedAgents.length) {
Window.alert("No agents reported.\n\Please run " + MINIMUM_AVATARS + " instances of\n\
http://cdn.highfidelity.com/davidkelly/production/scripts/tests/performance/crowd-agent.js\n\
http://hifi-content.s3.amazonaws.com/howard/scripts/tests/performance/crowd-agent.js\n\
on your domain server.");
} else if (total < MINIMUM_AVATARS) {
Window.alert("Only " + summonedAgents.length + " of the expected " + (MINIMUM_AVATARS - total) + " agents reported in.");
Window.alert("Only " + summonedAgents.length + " agents reported. Now missing " + (MINIMUM_AVATARS - total) + " avatars, total.");
}
}, 5000);
}, MINIMUM_AVATARS * SPREAD_TIME_MS )

View file

@ -36,7 +36,7 @@
BAR_DESKTOP_2K_HEIGHT = 3, // Display height of SVG
BAR_DESKTOP_2K_URL = Script.resolvePath("assets/images/progress-bar-2k.svg"),
BAR_DESKTOP_4K_WIDTH = 4480, // Width of SVG image in pixels. Sized for 1920 x 1080 display with 6 visible repeats.
BAR_DESKTOP_4K_WIDTH = 4480, // Width of SVG image in pixels. Sized for 4096 x 1920 display with 6 visible repeats.
BAR_DESKTOP_4K_REPEAT = 640, // Length of repeat in bar = 2240 / 7.
BAR_DESKTOP_4K_HEIGHT = 6, // Display height of SVG
BAR_DESKTOP_4K_URL = Script.resolvePath("assets/images/progress-bar-4k.svg"),

View file

@ -163,17 +163,19 @@ function shutdownCallback(idx) {
if (homeServer.state == ProcessGroupStates.STOPPED) {
// if the home server is already down, take down the server console now
log.debug("Quitting.");
app.quit();
app.exit(0);
} else {
// if the home server is still running, wait until we get a state change or timeout
// before quitting the app
log.debug("Server still shutting down. Waiting");
var timeoutID = setTimeout(app.quit, 5000);
var timeoutID = setTimeout(function() {
app.exit(0);
}, 5000);
homeServer.on('state-update', function(processGroup) {
if (processGroup.state == ProcessGroupStates.STOPPED) {
clearTimeout(timeoutID);
log.debug("Quitting.");
app.quit();
app.exit(0);
}
});
}
@ -240,7 +242,7 @@ var shouldQuit = app.makeSingleInstance(function(commandLine, workingDirectory)
if (shouldQuit) {
log.warn("Another instance of the Sandbox is already running - this instance will quit.");
app.quit();
app.exit(0);
return;
}
@ -288,12 +290,12 @@ function binaryMissingMessage(displayName, executableName, required) {
if (!dsPath) {
dialog.showErrorBox("Domain Server Not Found", binaryMissingMessage("domain-server", "domain-server", true));
app.quit();
app.exit(0);
}
if (!acPath) {
dialog.showErrorBox("Assignment Client Not Found", binaryMissingMessage("assignment-client", "assignment-client", true));
app.quit();
app.exit(0);
}
function openFileBrowser(path) {

View file

@ -425,6 +425,7 @@ protected:
return vec3();
}
bool isAboutToQuit() const override { return false; }
void postLambdaEvent(std::function<void()> f) override {}
qreal getDevicePixelRatio() override {

View file

@ -391,61 +391,6 @@ stepOrient.prototype = {
}
};
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// //
// STEP: Raise hands above head //
// //
///////////////////////////////////////////////////////////////////////////////
var stepRaiseAboveHead = function(name) {
this.tag = name;
this.tempTag = name + "-temporary";
}
stepRaiseAboveHead.prototype = {
start: function(onFinish) {
var tag = this.tag;
var STATE_START = 0;
var STATE_HANDS_DOWN = 1;
var STATE_HANDS_UP = 2;
this.state = STATE_START;
editEntitiesWithTag(this.tag, { visible: true });
// Wait 2 seconds before starting to check for hands
this.checkIntervalID = null;
function checkForHandsAboveHead() {
debug("RaiseAboveHead | Checking hands");
if (this.state == STATE_START) {
if (MyAvatar.getLeftPalmPosition().y < (MyAvatar.getHeadPosition().y - 0.1)) {
this.state = STATE_HANDS_DOWN;
}
} else if (this.state == STATE_HANDS_DOWN) {
if (MyAvatar.getLeftPalmPosition().y > (MyAvatar.getHeadPosition().y + 0.1)) {
this.state = STATE_HANDS_UP;
Script.clearInterval(this.checkIntervalID);
this.checkIntervalID = null;
playSuccessSound();
onFinish();
}
}
}
this.checkIntervalID = Script.setInterval(checkForHandsAboveHead.bind(this), 500);
},
cleanup: function() {
debug("RaiseAboveHead | Cleanup");
if (this.checkIntervalID) {
Script.clearInterval(this.checkIntervalID);
this.checkIntervalID = null
}
if (this.waitTimeoutID) {
Script.clearTimeout(this.waitTimeoutID);
this.waitTimeoutID = null;
}
editEntitiesWithTag(this.tag, { visible: false, collisionless: 1 });
deleteEntitiesWithTag(this.tempTag);
}
};
///////////////////////////////////////////////////////////////////////////////
@ -1027,16 +972,23 @@ TutorialManager = function() {
var startedTutorialAt = 0;
var startedLastStepAt = 0;
var wentToEntryStepNum;
var VERSION = 1;
var tutorialID;
var self = this;
this.startTutorial = function() {
currentStepNum = -1;
currentStep = null;
startedTutorialAt = Date.now();
// Old versions of interface do not have the Script.generateUUID function.
// If Script.generateUUID is not available, default to an empty string.
tutorialID = Script.generateUUID ? Script.generateUUID() : "";
STEPS = [
new stepStart("start"),
new stepOrient("orient"),
//new stepRaiseAboveHead("raiseHands"),
new stepNearGrab("nearGrab"),
new stepFarGrab("farGrab"),
new stepEquip("equip"),
@ -1045,6 +997,7 @@ TutorialManager = function() {
new stepFinish("finish"),
new stepEnableControllers("enableControllers"),
];
wentToEntryStepNum = STEPS.length;
for (var i = 0; i < STEPS.length; ++i) {
STEPS[i].cleanup();
}
@ -1055,10 +1008,7 @@ TutorialManager = function() {
this.onFinish = function() {
debug("onFinish", currentStepNum);
if (currentStep && currentStep.shouldLog !== false) {
var timeToFinishStep = (Date.now() - startedLastStepAt) / 1000;
var tutorialTimeElapsed = (Date.now() - startedTutorialAt) / 1000;
UserActivityLogger.tutorialProgress(
currentStep.tag, currentStepNum, timeToFinishStep, tutorialTimeElapsed);
self.trackStep(currentStep.tag, currentStepNum);
}
self.startNextStep();
@ -1071,6 +1021,12 @@ TutorialManager = function() {
++currentStepNum;
// This always needs to be set because we use this value when
// tracking that the user has gone through the entry portal. When the
// tutorial finishes, there is a last "pseudo" step that the user
// finishes when stepping into the portal.
startedLastStepAt = Date.now();
if (currentStepNum >= STEPS.length) {
// Done
info("DONE WITH TUTORIAL");
@ -1080,7 +1036,6 @@ TutorialManager = function() {
} else {
info("Starting step", currentStepNum);
currentStep = STEPS[currentStepNum];
startedLastStepAt = Date.now();
currentStep.start(this.onFinish);
return true;
}
@ -1102,6 +1057,21 @@ TutorialManager = function() {
currentStepNum = -1;
currentStep = null;
}
this.trackStep = function(name, stepNum) {
var timeToFinishStep = (Date.now() - startedLastStepAt) / 1000;
var tutorialTimeElapsed = (Date.now() - startedTutorialAt) / 1000;
UserActivityLogger.tutorialProgress(
name, stepNum, timeToFinishStep, tutorialTimeElapsed,
tutorialID, VERSION);
}
// This is a message sent from the "entry" portal in the courtyard,
// after the tutorial has finished.
this.enteredEntryPortal = function() {
info("Got enteredEntryPortal, tracking");
this.trackStep("wentToEntry", wentToEntryStepNum);
}
}
// To run the tutorial:

View file

@ -113,6 +113,14 @@ if (!Function.prototype.bind) {
}
},
onEnteredEntryPortal: function() {
print("TutorialZone | Got onEnteredEntryPortal");
if (this.tutorialManager) {
print("TutorialZone | Calling enteredEntryPortal");
this.tutorialManager.enteredEntryPortal();
}
},
enterEntity: function() {
print("TutorialZone | ENTERED THE TUTORIAL AREA");
},
@ -125,7 +133,7 @@ if (!Function.prototype.bind) {
}
if (this.tutorialManager) {
this.tutorialManager.stopTutorial();
this.tutorialManager = null;
//this.tutorialManager = null;
}
}
};