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(); } qint64 getCurrentSessionRuntime() const { return _sessionRunTimer.elapsed(); }
bool isAboutToQuit() const { return _aboutToQuit; } bool isAboutToQuit() const override { return _aboutToQuit; }
bool isPhysicsEnabled() const { return _physicsEnabled; } bool isPhysicsEnabled() const { return _physicsEnabled; }
// the isHMDMode is true whenever we use the interface from an HMD and not a standard flat display // 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) { auto deleter = [](OffscreenQmlSurface* webSurface) {
AbstractViewStateInterface::instance()->postLambdaEvent([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); _webSurface = QSharedPointer<OffscreenQmlSurface>(new OffscreenQmlSurface(), deleter);
@ -333,6 +339,7 @@ void RenderableWebEntityItem::destroyWebSurface() {
if (rootItem) { if (rootItem) {
QObject* obj = rootItem->findChild<QObject*>("webEngineView"); QObject* obj = rootItem->findChild<QObject*>("webEngineView");
if (obj) { if (obj) {
// stop loading
QMetaObject::invokeMethod(obj, "stop"); QMetaObject::invokeMethod(obj, "stop");
} }
} }

View file

@ -24,8 +24,11 @@ void UserActivityLoggerScriptingInterface::toggledAway(bool isAway) {
logAction("toggled_away", { { "is_away", 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", { logAction("tutorial_progress", {
{ "tutorial_run_id", tutorialRunID },
{ "tutorial_version", tutorialVersion },
{ "step", stepName }, { "step", stepName },
{ "step_number", stepNumber }, { "step_number", stepNumber },
{ "seconds_to_complete", secondsToComplete }, { "seconds_to_complete", secondsToComplete },

View file

@ -23,7 +23,8 @@ public:
Q_INVOKABLE void enabledEdit(); Q_INVOKABLE void enabledEdit();
Q_INVOKABLE void openedMarketplace(); Q_INVOKABLE void openedMarketplace();
Q_INVOKABLE void toggledAway(bool isAway); 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: private:
void logAction(QString action, QJsonObject details = {}); 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); 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 // 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); connect(_readyReadBackupTimer, &QTimer::timeout, this, &Socket::checkForReadyReadBackup);
_readyReadBackupTimer->start(READY_READ_BACKUP_CHECK_MSECS); _readyReadBackupTimer->start(READY_READ_BACKUP_CHECK_MSECS);
} }

View file

@ -40,7 +40,9 @@ public:
virtual glm::vec3 getAvatarPosition() const = 0; virtual glm::vec3 getAvatarPosition() const = 0;
virtual bool isAboutToQuit() const = 0;
virtual void postLambdaEvent(std::function<void()> f) = 0; virtual void postLambdaEvent(std::function<void()> f) = 0;
virtual qreal getDevicePixelRatio() = 0; virtual qreal getDevicePixelRatio() = 0;
virtual render::ScenePointer getMain3DScene() = 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 // If BatchLoader is deleted before the callback is called, the subsequent "emit" call will not do
// anything. // anything.
ScriptCacheSignalProxy* proxy = new ScriptCacheSignalProxy(scriptCache.data()); 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) { connect(proxy, &ScriptCacheSignalProxy::contentAvailable, this, [this](const QString& url, const QString& contents, bool isURL, bool success) {
if (isURL && success) { if (isURL && success) {
@ -75,6 +71,11 @@ void BatchLoader::start() {
emit finished(_data); 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 void requestGarbageCollection() { collectGarbage(); }
Q_INVOKABLE QUuid generateUUID() { return QUuid::createUuid(); }
bool isFinished() const { return _isFinished; } // used by Application and ScriptWidget bool isFinished() const { return _isFinished; } // used by Application and ScriptWidget
bool isRunning() const { return _isRunning; } // used by ScriptWidget bool isRunning() const { return _isRunning; } // used by ScriptWidget

View file

@ -16,7 +16,7 @@
var MESSAGE_CHANNEL = "io.highfidelity.summon-crowd"; var MESSAGE_CHANNEL = "io.highfidelity.summon-crowd";
print('crowd-agent version 3'); print('crowd-agent version 4');
/* Observations: /* Observations:
- File urls for AC scripts silently fail. Use a local server (e.g., python SimpleHTTPServer) for development. - 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) { if (sound.downloaded) {
return callback(sound); return callback(sound);
} }
sound.ready.connect(function () { callback(sound); }); function onDownloaded() {
sound.ready.disconnect(onDownloaded);
callback(sound);
}
sound.ready.connect(onDownloaded);
} }
function onFinishedPlaying() { function onFinishedPlaying() {
messageSend({key: 'finishedSound'}); messageSend({key: 'finishedSound'});
} }
var attachment; 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; var MILLISECONDS_IN_SECOND = 1000;
function startAgent(parameters) { // Can also be used to update. function startAgent(parameters) { // Can also be used to update.
print('crowd-agent starting params', JSON.stringify(parameters), JSON.stringify(Agent)); print('crowd-agent starting params', JSON.stringify(parameters), JSON.stringify(Agent));
clearStopper();
var wasOff = !Agent.isAvatar;
Agent.isAvatar = true; Agent.isAvatar = true;
Agent.isListeningToAudioStream = true; // Send silence when not chattering.
if (parameters.position) { if (parameters.position) {
Avatar.position = parameters.position; Avatar.position = parameters.position;
} }
@ -56,6 +93,11 @@ function startAgent(parameters) { // Can also be used to update.
if (parameters.skeletonModelURL) { if (parameters.skeletonModelURL) {
Avatar.skeletonModelURL = 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) { if (parameters.soundData) {
getSound(parameters.soundData, function (sound) { getSound(parameters.soundData, function (sound) {
Script.setTimeout(onFinishedPlaying, sound.duration * MILLISECONDS_IN_SECOND); 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'); 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) { function messageHandler(channel, messageString, senderID) {
if (channel !== MESSAGE_CHANNEL) { 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 MAXIMUM_LOAD_TIME = NOMINAL_LOAD_TIME * 2;
var MINIMUM_AVATARS = 25; // changeable by prompt 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 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 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. 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 "loopFlag": true
}; };
var version = 3; var version = 4;
function debug() { function debug() {
print.apply(null, [].concat.apply(['hrs fixme', version], [].map.call(arguments, JSON.stringify))); 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 summonedAgents = [];
var chattering = []; var chattering = [];
var accumulatedDelay = 0;
var MESSAGE_CHANNEL = "io.highfidelity.summon-crowd"; var MESSAGE_CHANNEL = "io.highfidelity.summon-crowd";
function messageSend(message) { function messageSend(message) {
Messages.sendMessage(MESSAGE_CHANNEL, JSON.stringify(message)); Messages.sendMessage(MESSAGE_CHANNEL, JSON.stringify(message));
@ -106,25 +111,29 @@ function messageHandler(channel, messageString, senderID) {
} }
switch (message.key) { switch (message.key) {
case "hello": case "hello":
// There can be avatars we've summoned that do not yet appear in the AvatarList. Script.setTimeout(function () {
avatarIdentifiers = without(AvatarList.getAvatarIdentifiers(), summonedAgents); // There can be avatars we've summoned that do not yet appear in the AvatarList.
debug('present', avatarIdentifiers, summonedAgents); avatarIdentifiers = without(AvatarList.getAvatarIdentifiers(), summonedAgents);
if ((summonedAgents.length + avatarIdentifiers.length) < MINIMUM_AVATARS) { debug('present', avatarIdentifiers, summonedAgents);
var chatter = chattering.length < AVATARS_CHATTERING_AT_ONCE; if ((summonedAgents.length + avatarIdentifiers.length) < MINIMUM_AVATARS) {
if (chatter) { var chatter = chattering.length < AVATARS_CHATTERING_AT_ONCE;
chattering.push(senderID); 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); }, accumulatedDelay);
messageSend({ accumulatedDelay += SPREAD_TIME_MS; // assume we'll get all the hello respsponses more or less together.
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
});
}
break; break;
case "finishedSound": // Give someone else a chance. case "finishedSound": // Give someone else a chance.
chattering = without(chattering, [senderID]); chattering = without(chattering, [senderID]);
@ -147,13 +156,15 @@ Messages.subscribe(MESSAGE_CHANNEL);
Messages.messageReceived.connect(messageHandler); Messages.messageReceived.connect(messageHandler);
Script.scriptEnding.connect(function () { Script.scriptEnding.connect(function () {
debug('stopping agents', summonedAgents); 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'); debug('agents stopped');
Script.setTimeout(function () { Messages.unsubscribe(MESSAGE_CHANNEL);
Messages.messageReceived.disconnect(messageHandler); debug('unsubscribed');
Messages.unsubscribe(MESSAGE_CHANNEL);
debug('unsubscribed');
}, 500);
}); });
var fail = false, results = ""; var fail = false, results = "";
@ -257,7 +268,7 @@ function doRender(continuation) {
} }
config.newStats.connect(onNewStats); config.newStats.connect(onNewStats);
startTwirl(720, 1, 15, 0.08, function () { startTwirl(720, 1, 20, 0.08, function () {
var end = Date.now(); var end = Date.now();
config.newStats.disconnect(onNewStats); config.newStats.disconnect(onNewStats);
addResult('frame rate', 1000 * frames / (end - start), 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\ http://hifi-content.s3.amazonaws.com/howard/scripts/tests/performance/crowd-agent.js?v=3\n\
on your domain server."; on your domain server.";
} else if (total < MINIMUM_AVATARS) { } 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(); continuation();

View file

@ -13,19 +13,26 @@
// //
// See crowd-agent.js // See crowd-agent.js
var version = 1; var version = 2;
var label = "summon"; var label = "summon";
function debug() { function debug() {
print.apply(null, [].concat.apply([label, version], [].map.call(arguments, JSON.stringify))); 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 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 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 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.
var NEXT_SOUND_SPREAD = 500; // millisecond range of how long to wait after one sound finishes, before playing the next var NEXT_SOUND_SPREAD = 500; // millisecond range of how long to wait after one sound finishes, before playing the next
var ANIMATION_DATA = { var ANIMATION_DATA = {
"url": "http://howard-stearns.github.io/models/resources/avatar/animations/idle.fbx", "url": "http://hifi-content.s3.amazonaws.com/howard/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/walk_fwd.fbx", // alternative example
"startFrame": 0.0, "startFrame": 0.0,
"endFrame": 300.0, "endFrame": 300.0,
"timeScale": 1.0, "timeScale": 1.0,
@ -45,6 +52,8 @@ function nextAfter(array, id) { // Wrapping next element in array after id.
var summonedAgents = []; var summonedAgents = [];
var chattering = []; var chattering = [];
var nListening = 0;
var accumulatedDelay = 0;
var MESSAGE_CHANNEL = "io.highfidelity.summon-crowd"; var MESSAGE_CHANNEL = "io.highfidelity.summon-crowd";
function messageSend(message) { function messageSend(message) {
Messages.sendMessage(MESSAGE_CHANNEL, JSON.stringify(message)); Messages.sendMessage(MESSAGE_CHANNEL, JSON.stringify(message));
@ -65,25 +74,33 @@ function messageHandler(channel, messageString, senderID) {
} }
switch (message.key) { switch (message.key) {
case "hello": case "hello":
// There can be avatars we've summoned that do not yet appear in the AvatarList. Script.setTimeout(function () {
avatarIdentifiers = without(AvatarList.getAvatarIdentifiers(), summonedAgents); // There can be avatars we've summoned that do not yet appear in the AvatarList.
debug('present', avatarIdentifiers, summonedAgents); avatarIdentifiers = without(AvatarList.getAvatarIdentifiers(), summonedAgents);
if ((summonedAgents.length + avatarIdentifiers.length) < MINIMUM_AVATARS ) { debug('present', avatarIdentifiers, summonedAgents);
var chatter = chattering.length < AVATARS_CHATTERING_AT_ONCE; if ((summonedAgents.length + avatarIdentifiers.length) < MINIMUM_AVATARS ) {
if (chatter) { var chatter = chattering.length < AVATARS_CHATTERING_AT_ONCE;
chattering.push(senderID); 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); }, accumulatedDelay);
messageSend({ accumulatedDelay += SPREAD_TIME_MS; // assume we'll get all the hello respsponses more or less together.
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
});
}
break; break;
case "finishedSound": // Give someone else a chance. case "finishedSound": // Give someone else a chance.
chattering = without(chattering, [senderID]); chattering = without(chattering, [senderID]);
@ -99,20 +116,22 @@ function messageHandler(channel, messageString, senderID) {
Window.alert("Someone else is summoning avatars."); Window.alert("Someone else is summoning avatars.");
break; break;
default: default:
print("crowd-agent received unrecognized message:", messageString); print("crowd summon.js received unrecognized message:", messageString);
} }
} }
Messages.subscribe(MESSAGE_CHANNEL); Messages.subscribe(MESSAGE_CHANNEL);
Messages.messageReceived.connect(messageHandler); Messages.messageReceived.connect(messageHandler);
Script.scriptEnding.connect(function () { Script.scriptEnding.connect(function () {
debug('stopping agents', summonedAgents); 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'); debug('agents stopped');
Script.setTimeout(function () { Messages.unsubscribe(MESSAGE_CHANNEL);
Messages.messageReceived.disconnect(messageHandler); debug('unsubscribed');
Messages.unsubscribe(MESSAGE_CHANNEL);
debug('unsubscribed');
}, 500);
}); });
messageSend({key: 'HELO'}); // Ask agents to report in now. messageSend({key: 'HELO'}); // Ask agents to report in now.
@ -120,9 +139,9 @@ Script.setTimeout(function () {
var total = AvatarList.getAvatarIdentifiers().length; var total = AvatarList.getAvatarIdentifiers().length;
if (0 === summonedAgents.length) { if (0 === summonedAgents.length) {
Window.alert("No agents reported.\n\Please run " + MINIMUM_AVATARS + " instances of\n\ 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."); on your domain server.");
} else if (total < MINIMUM_AVATARS) { } 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_HEIGHT = 3, // Display height of SVG
BAR_DESKTOP_2K_URL = Script.resolvePath("assets/images/progress-bar-2k.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_REPEAT = 640, // Length of repeat in bar = 2240 / 7.
BAR_DESKTOP_4K_HEIGHT = 6, // Display height of SVG BAR_DESKTOP_4K_HEIGHT = 6, // Display height of SVG
BAR_DESKTOP_4K_URL = Script.resolvePath("assets/images/progress-bar-4k.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 (homeServer.state == ProcessGroupStates.STOPPED) {
// if the home server is already down, take down the server console now // if the home server is already down, take down the server console now
log.debug("Quitting."); log.debug("Quitting.");
app.quit(); app.exit(0);
} else { } else {
// if the home server is still running, wait until we get a state change or timeout // if the home server is still running, wait until we get a state change or timeout
// before quitting the app // before quitting the app
log.debug("Server still shutting down. Waiting"); 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) { homeServer.on('state-update', function(processGroup) {
if (processGroup.state == ProcessGroupStates.STOPPED) { if (processGroup.state == ProcessGroupStates.STOPPED) {
clearTimeout(timeoutID); clearTimeout(timeoutID);
log.debug("Quitting."); log.debug("Quitting.");
app.quit(); app.exit(0);
} }
}); });
} }
@ -240,7 +242,7 @@ var shouldQuit = app.makeSingleInstance(function(commandLine, workingDirectory)
if (shouldQuit) { if (shouldQuit) {
log.warn("Another instance of the Sandbox is already running - this instance will quit."); log.warn("Another instance of the Sandbox is already running - this instance will quit.");
app.quit(); app.exit(0);
return; return;
} }
@ -288,12 +290,12 @@ function binaryMissingMessage(displayName, executableName, required) {
if (!dsPath) { if (!dsPath) {
dialog.showErrorBox("Domain Server Not Found", binaryMissingMessage("domain-server", "domain-server", true)); dialog.showErrorBox("Domain Server Not Found", binaryMissingMessage("domain-server", "domain-server", true));
app.quit(); app.exit(0);
} }
if (!acPath) { if (!acPath) {
dialog.showErrorBox("Assignment Client Not Found", binaryMissingMessage("assignment-client", "assignment-client", true)); dialog.showErrorBox("Assignment Client Not Found", binaryMissingMessage("assignment-client", "assignment-client", true));
app.quit(); app.exit(0);
} }
function openFileBrowser(path) { function openFileBrowser(path) {

View file

@ -425,6 +425,7 @@ protected:
return vec3(); return vec3();
} }
bool isAboutToQuit() const override { return false; }
void postLambdaEvent(std::function<void()> f) override {} void postLambdaEvent(std::function<void()> f) override {}
qreal getDevicePixelRatio() 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 startedTutorialAt = 0;
var startedLastStepAt = 0; var startedLastStepAt = 0;
var wentToEntryStepNum;
var VERSION = 1;
var tutorialID;
var self = this; var self = this;
this.startTutorial = function() { this.startTutorial = function() {
currentStepNum = -1; currentStepNum = -1;
currentStep = null; currentStep = null;
startedTutorialAt = Date.now(); 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 = [ STEPS = [
new stepStart("start"), new stepStart("start"),
new stepOrient("orient"), new stepOrient("orient"),
//new stepRaiseAboveHead("raiseHands"),
new stepNearGrab("nearGrab"), new stepNearGrab("nearGrab"),
new stepFarGrab("farGrab"), new stepFarGrab("farGrab"),
new stepEquip("equip"), new stepEquip("equip"),
@ -1045,6 +997,7 @@ TutorialManager = function() {
new stepFinish("finish"), new stepFinish("finish"),
new stepEnableControllers("enableControllers"), new stepEnableControllers("enableControllers"),
]; ];
wentToEntryStepNum = STEPS.length;
for (var i = 0; i < STEPS.length; ++i) { for (var i = 0; i < STEPS.length; ++i) {
STEPS[i].cleanup(); STEPS[i].cleanup();
} }
@ -1055,10 +1008,7 @@ TutorialManager = function() {
this.onFinish = function() { this.onFinish = function() {
debug("onFinish", currentStepNum); debug("onFinish", currentStepNum);
if (currentStep && currentStep.shouldLog !== false) { if (currentStep && currentStep.shouldLog !== false) {
var timeToFinishStep = (Date.now() - startedLastStepAt) / 1000; self.trackStep(currentStep.tag, currentStepNum);
var tutorialTimeElapsed = (Date.now() - startedTutorialAt) / 1000;
UserActivityLogger.tutorialProgress(
currentStep.tag, currentStepNum, timeToFinishStep, tutorialTimeElapsed);
} }
self.startNextStep(); self.startNextStep();
@ -1071,6 +1021,12 @@ TutorialManager = function() {
++currentStepNum; ++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) { if (currentStepNum >= STEPS.length) {
// Done // Done
info("DONE WITH TUTORIAL"); info("DONE WITH TUTORIAL");
@ -1080,7 +1036,6 @@ TutorialManager = function() {
} else { } else {
info("Starting step", currentStepNum); info("Starting step", currentStepNum);
currentStep = STEPS[currentStepNum]; currentStep = STEPS[currentStepNum];
startedLastStepAt = Date.now();
currentStep.start(this.onFinish); currentStep.start(this.onFinish);
return true; return true;
} }
@ -1102,6 +1057,21 @@ TutorialManager = function() {
currentStepNum = -1; currentStepNum = -1;
currentStep = null; 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: // 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() { enterEntity: function() {
print("TutorialZone | ENTERED THE TUTORIAL AREA"); print("TutorialZone | ENTERED THE TUTORIAL AREA");
}, },
@ -125,7 +133,7 @@ if (!Function.prototype.bind) {
} }
if (this.tutorialManager) { if (this.tutorialManager) {
this.tutorialManager.stopTutorial(); this.tutorialManager.stopTutorial();
this.tutorialManager = null; //this.tutorialManager = null;
} }
} }
}; };