Merge branch 'master' into feature/ik-solver-init-config

This commit is contained in:
Anthony J. Thibault 2017-05-10 18:12:39 -07:00
commit 147d067c4e
11 changed files with 319 additions and 176 deletions

View file

@ -941,10 +941,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
// sessionRunTime will be reset soon by loadSettings. Grab it now to get previous session value.
// The value will be 0 if the user blew away settings this session, which is both a feature and a bug.
static const QString TESTER = "HIFI_TESTER";
auto gpuIdent = GPUIdent::getInstance();
auto glContextData = getGLContextData();
QJsonObject properties = {
{ "version", applicationVersion() },
{ "tester", QProcessEnvironment::systemEnvironment().contains(TESTER) },
{ "previousSessionCrashed", _previousSessionCrashed },
{ "previousSessionRuntime", sessionRunTime.get() },
{ "cpu_architecture", QSysInfo::currentCpuArchitecture() },

View file

@ -89,8 +89,7 @@ void Head::simulate(float deltaTime) {
_timeWithoutTalking += deltaTime;
if ((_averageLoudness - _longTermAverageLoudness) > TALKING_LOUDNESS) {
_timeWithoutTalking = 0.0f;
} else if (_timeWithoutTalking < BLINK_AFTER_TALKING && _timeWithoutTalking >= BLINK_AFTER_TALKING) {
} else if (_timeWithoutTalking - deltaTime < BLINK_AFTER_TALKING && _timeWithoutTalking >= BLINK_AFTER_TALKING) {
forceBlink = true;
}

View file

@ -22,20 +22,20 @@
#include <BuildInfo.h>
#include <GLMHelpers.h>
QString SAVE_DIRECTORY = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/" + BuildInfo::MODIFIED_ORGANIZATION + "/" + BuildInfo::INTERFACE_NAME + "/hifi-input-recordings/";
QString FILE_PREFIX_NAME = "input-recording-";
QString COMPRESS_EXTENSION = ".tar.gz";
namespace controller {
QJsonObject poseToJsonObject(const Pose pose) {
QJsonObject newPose;
QJsonArray translation;
translation.append(pose.translation.x);
translation.append(pose.translation.y);
translation.append(pose.translation.z);
QJsonArray rotation;
rotation.append(pose.rotation.x);
rotation.append(pose.rotation.y);
@ -69,7 +69,7 @@ namespace controller {
QJsonArray angularVelocity = object["angularVelocity"].toArray();
pose.valid = object["valid"].toBool();
pose.translation.x = translation[0].toDouble();
pose.translation.y = translation[1].toDouble();
pose.translation.z = translation[2].toDouble();
@ -89,13 +89,13 @@ namespace controller {
return pose;
}
void exportToFile(QJsonObject& object) {
if (!QDir(SAVE_DIRECTORY).exists()) {
QDir().mkdir(SAVE_DIRECTORY);
}
QString timeStamp = QDateTime::currentDateTime().toString(Qt::ISODate);
timeStamp.replace(":", "-");
QString fileName = SAVE_DIRECTORY + FILE_PREFIX_NAME + timeStamp + COMPRESS_EXTENSION;
@ -124,7 +124,7 @@ namespace controller {
status = true;
return object;
}
InputRecorder::InputRecorder() {}
InputRecorder::~InputRecorder() {}
@ -195,16 +195,16 @@ namespace controller {
_framesRecorded = data["frameCount"].toInt();
QJsonArray actionArrayList = data["actionList"].toArray();
QJsonArray poseArrayList = data["poseList"].toArray();
for (int actionIndex = 0; actionIndex < actionArrayList.size(); actionIndex++) {
QJsonArray actionState = actionArrayList[actionIndex].toArray();
for (int index = 0; index < actionState.size(); index++) {
_currentFrameActions[index] = actionState[index].toInt();
_currentFrameActions[index] = actionState[index].toDouble();
}
_actionStateList.push_back(_currentFrameActions);
_currentFrameActions = ActionStates(toInt(Action::NUM_ACTIONS));
}
for (int poseIndex = 0; poseIndex < poseArrayList.size(); poseIndex++) {
QJsonArray poseState = poseArrayList[poseIndex].toArray();
for (int index = 0; index < poseState.size(); index++) {
@ -250,13 +250,13 @@ namespace controller {
for(auto& channel : _currentFramePoses) {
channel = Pose();
}
for(auto& channel : _currentFrameActions) {
channel = 0.0f;
}
}
}
float InputRecorder::getActionState(controller::Action action) {
if (_actionStateList.size() > 0 ) {
return _actionStateList[_playCount][toInt(action)];

View file

@ -1,5 +1,5 @@
set(TARGET_NAME gpu-gl)
setup_hifi_library()
setup_hifi_library(Concurrent)
link_hifi_libraries(shared gl gpu)
if (UNIX)
target_link_libraries(${TARGET_NAME} pthread)

View file

@ -160,8 +160,6 @@ const uvec3 GLVariableAllocationSupport::INITIAL_MIP_TRANSFER_DIMENSIONS { 64, 6
WorkQueue GLVariableAllocationSupport::_transferQueue;
WorkQueue GLVariableAllocationSupport::_promoteQueue;
WorkQueue GLVariableAllocationSupport::_demoteQueue;
TexturePointer GLVariableAllocationSupport::_currentTransferTexture;
TransferJobPointer GLVariableAllocationSupport::_currentTransferJob;
size_t GLVariableAllocationSupport::_frameTexturesCreated { 0 };
#define OVERSUBSCRIBED_PRESSURE_VALUE 0.95f
@ -176,30 +174,19 @@ const uvec3 GLVariableAllocationSupport::MAX_TRANSFER_DIMENSIONS { 1024, 1024, 1
const size_t GLVariableAllocationSupport::MAX_TRANSFER_SIZE = GLVariableAllocationSupport::MAX_TRANSFER_DIMENSIONS.x * GLVariableAllocationSupport::MAX_TRANSFER_DIMENSIONS.y * 4;
#if THREADED_TEXTURE_BUFFERING
std::shared_ptr<std::thread> TransferJob::_bufferThread { nullptr };
std::atomic<bool> TransferJob::_shutdownBufferingThread { false };
Mutex TransferJob::_mutex;
TransferJob::VoidLambdaQueue TransferJob::_bufferLambdaQueue;
void TransferJob::startTransferLoop() {
if (_bufferThread) {
return;
}
_shutdownBufferingThread = false;
_bufferThread = std::make_shared<std::thread>([] {
TransferJob::bufferLoop();
TexturePointer GLVariableAllocationSupport::_currentTransferTexture;
TransferJobPointer GLVariableAllocationSupport::_currentTransferJob;
QThreadPool* TransferJob::_bufferThreadPool { nullptr };
void TransferJob::startBufferingThread() {
static std::once_flag once;
std::call_once(once, [&] {
_bufferThreadPool = new QThreadPool(qApp);
_bufferThreadPool->setMaxThreadCount(1);
});
}
void TransferJob::stopTransferLoop() {
if (!_bufferThread) {
return;
}
_shutdownBufferingThread = true;
_bufferThread->join();
_bufferThread.reset();
_shutdownBufferingThread = false;
}
#endif
TransferJob::TransferJob(const GLTexture& parent, uint16_t sourceMip, uint16_t targetMip, uint8_t face, uint32_t lines, uint32_t lineOffset)
@ -233,7 +220,6 @@ TransferJob::TransferJob(const GLTexture& parent, uint16_t sourceMip, uint16_t t
// Buffering can invoke disk IO, so it should be off of the main and render threads
_bufferingLambda = [=] {
_mipData = _parent._gpuObject.accessStoredMipFace(sourceMip, face)->createView(_transferSize, _transferOffset);
_bufferingCompleted = true;
};
_transferLambda = [=] {
@ -243,65 +229,66 @@ TransferJob::TransferJob(const GLTexture& parent, uint16_t sourceMip, uint16_t t
}
TransferJob::TransferJob(const GLTexture& parent, std::function<void()> transferLambda)
: _parent(parent), _bufferingCompleted(true), _transferLambda(transferLambda) {
: _parent(parent), _bufferingRequired(false), _transferLambda(transferLambda) {
}
TransferJob::~TransferJob() {
Backend::updateTextureTransferPendingSize(_transferSize, 0);
}
bool TransferJob::tryTransfer() {
// Disable threaded texture transfer for now
#if THREADED_TEXTURE_BUFFERING
// Are we ready to transfer
if (_bufferingCompleted) {
_transferLambda();
if (!bufferingCompleted()) {
startBuffering();
return false;
}
#else
if (_bufferingRequired) {
_bufferingLambda();
}
#endif
_transferLambda();
return true;
}
#if THREADED_TEXTURE_BUFFERING
bool TransferJob::bufferingRequired() const {
if (!_bufferingRequired) {
return false;
}
// The default state of a QFuture is with status Canceled | Started | Finished,
// so we have to check isCancelled before we check the actual state
if (_bufferingStatus.isCanceled()) {
return true;
}
startBuffering();
return false;
#else
if (!_bufferingCompleted) {
_bufferingLambda();
_bufferingCompleted = true;
}
_transferLambda();
return true;
#endif
return !_bufferingStatus.isStarted();
}
#if THREADED_TEXTURE_BUFFERING
bool TransferJob::bufferingCompleted() const {
if (!_bufferingRequired) {
return true;
}
// The default state of a QFuture is with status Canceled | Started | Finished,
// so we have to check isCancelled before we check the actual state
if (_bufferingStatus.isCanceled()) {
return false;
}
return _bufferingStatus.isFinished();
}
void TransferJob::startBuffering() {
if (_bufferingStarted) {
return;
}
_bufferingStarted = true;
{
Lock lock(_mutex);
_bufferLambdaQueue.push(_bufferingLambda);
}
}
void TransferJob::bufferLoop() {
while (!_shutdownBufferingThread) {
VoidLambdaQueue workingQueue;
{
Lock lock(_mutex);
_bufferLambdaQueue.swap(workingQueue);
}
if (workingQueue.empty()) {
QThread::msleep(5);
continue;
}
while (!workingQueue.empty()) {
workingQueue.front()();
workingQueue.pop();
}
if (bufferingRequired()) {
assert(_bufferingStatus.isCanceled());
_bufferingStatus = QtConcurrent::run(_bufferThreadPool, [=] {
_bufferingLambda();
});
assert(!_bufferingStatus.isCanceled());
assert(_bufferingStatus.isStarted());
}
}
#endif
@ -316,7 +303,9 @@ GLVariableAllocationSupport::~GLVariableAllocationSupport() {
void GLVariableAllocationSupport::addMemoryManagedTexture(const TexturePointer& texturePointer) {
_memoryManagedTextures.push_back(texturePointer);
addToWorkQueue(texturePointer);
if (MemoryPressureState::Idle != _memoryPressureState) {
addToWorkQueue(texturePointer);
}
}
void GLVariableAllocationSupport::addToWorkQueue(const TexturePointer& texturePointer) {
@ -345,10 +334,8 @@ void GLVariableAllocationSupport::addToWorkQueue(const TexturePointer& texturePo
break;
case MemoryPressureState::Idle:
break;
default:
Q_UNREACHABLE();
break;
}
}
@ -364,10 +351,10 @@ WorkQueue& GLVariableAllocationSupport::getActiveWorkQueue() {
case MemoryPressureState::Transfer:
return _transferQueue;
default:
case MemoryPressureState::Idle:
Q_UNREACHABLE();
break;
}
Q_UNREACHABLE();
return empty;
}
@ -460,16 +447,11 @@ void GLVariableAllocationSupport::updateMemoryPressure() {
}
if (newState != _memoryPressureState) {
_memoryPressureState = newState;
#if THREADED_TEXTURE_BUFFERING
if (MemoryPressureState::Transfer == _memoryPressureState) {
TransferJob::stopTransferLoop();
TransferJob::startBufferingThread();
}
_memoryPressureState = newState;
if (MemoryPressureState::Transfer == _memoryPressureState) {
TransferJob::startTransferLoop();
}
#else
_memoryPressureState = newState;
#endif
// Clear the existing queue
_transferQueue = WorkQueue();
@ -487,49 +469,111 @@ void GLVariableAllocationSupport::updateMemoryPressure() {
}
}
TexturePointer GLVariableAllocationSupport::getNextWorkQueueItem(WorkQueue& workQueue) {
while (!workQueue.empty()) {
auto workTarget = workQueue.top();
auto texture = workTarget.first.lock();
if (!texture) {
workQueue.pop();
continue;
}
// Check whether the resulting texture can actually have work performed
GLTexture* gltexture = Backend::getGPUObject<GLTexture>(*texture);
GLVariableAllocationSupport* vartexture = dynamic_cast<GLVariableAllocationSupport*>(gltexture);
switch (_memoryPressureState) {
case MemoryPressureState::Oversubscribed:
if (vartexture->canDemote()) {
return texture;
}
break;
case MemoryPressureState::Undersubscribed:
if (vartexture->canPromote()) {
return texture;
}
break;
case MemoryPressureState::Transfer:
if (vartexture->hasPendingTransfers()) {
return texture;
}
break;
case MemoryPressureState::Idle:
Q_UNREACHABLE();
break;
}
// If we got here, then the texture has no work to do in the current state,
// so pop it off the queue and continue
workQueue.pop();
}
return TexturePointer();
}
void GLVariableAllocationSupport::processWorkQueue(WorkQueue& workQueue) {
if (workQueue.empty()) {
return;
}
// Get the front of the work queue to perform work
auto texture = getNextWorkQueueItem(workQueue);
if (!texture) {
return;
}
// Grab the first item off the demote queue
PROFILE_RANGE(render_gpu_gl, __FUNCTION__);
GLTexture* gltexture = Backend::getGPUObject<GLTexture>(*texture);
GLVariableAllocationSupport* vartexture = dynamic_cast<GLVariableAllocationSupport*>(gltexture);
switch (_memoryPressureState) {
case MemoryPressureState::Oversubscribed:
vartexture->demote();
workQueue.pop();
addToWorkQueue(texture);
break;
case MemoryPressureState::Undersubscribed:
vartexture->promote();
workQueue.pop();
addToWorkQueue(texture);
break;
case MemoryPressureState::Transfer:
if (vartexture->executeNextTransfer(texture)) {
workQueue.pop();
addToWorkQueue(texture);
#if THREADED_TEXTURE_BUFFERING
// Eagerly start the next buffering job if possible
texture = getNextWorkQueueItem(workQueue);
if (texture) {
gltexture = Backend::getGPUObject<GLTexture>(*texture);
vartexture = dynamic_cast<GLVariableAllocationSupport*>(gltexture);
vartexture->executeNextBuffer(texture);
}
#endif
}
break;
case MemoryPressureState::Idle:
Q_UNREACHABLE();
break;
}
}
void GLVariableAllocationSupport::processWorkQueues() {
if (MemoryPressureState::Idle == _memoryPressureState) {
return;
}
auto& workQueue = getActiveWorkQueue();
PROFILE_RANGE(render_gpu_gl, __FUNCTION__);
while (!workQueue.empty()) {
auto workTarget = workQueue.top();
workQueue.pop();
auto texture = workTarget.first.lock();
if (!texture) {
continue;
}
// Grab the first item off the demote queue
GLTexture* gltexture = Backend::getGPUObject<GLTexture>(*texture);
GLVariableAllocationSupport* vartexture = dynamic_cast<GLVariableAllocationSupport*>(gltexture);
if (MemoryPressureState::Oversubscribed == _memoryPressureState) {
if (!vartexture->canDemote()) {
continue;
}
vartexture->demote();
_memoryPressureStateStale = true;
} else if (MemoryPressureState::Undersubscribed == _memoryPressureState) {
if (!vartexture->canPromote()) {
continue;
}
vartexture->promote();
_memoryPressureStateStale = true;
} else if (MemoryPressureState::Transfer == _memoryPressureState) {
if (!vartexture->hasPendingTransfers()) {
continue;
}
vartexture->executeNextTransfer(texture);
} else {
Q_UNREACHABLE();
}
// Reinject into the queue if more work to be done
addToWorkQueue(texture);
break;
}
// Do work on the front of the queue
processWorkQueue(workQueue);
if (workQueue.empty()) {
_memoryPressureState = MemoryPressureState::Idle;
@ -543,28 +587,83 @@ void GLVariableAllocationSupport::manageMemory() {
processWorkQueues();
}
bool GLVariableAllocationSupport::executeNextTransfer(const TexturePointer& currentTexture) {
#if THREADED_TEXTURE_BUFFERING
// If a transfer job is active on the buffering thread, but has not completed it's buffering lambda,
// then we need to exit early, since we don't want to have the transfer job leave scope while it's
// being used in another thread -- See https://highfidelity.fogbugz.com/f/cases/4626
if (_currentTransferJob && !_currentTransferJob->bufferingCompleted()) {
return false;
}
#endif
void GLVariableAllocationSupport::executeNextTransfer(const TexturePointer& currentTexture) {
if (_populatedMip <= _allocatedMip) {
#if THREADED_TEXTURE_BUFFERING
_currentTransferJob.reset();
_currentTransferTexture.reset();
#endif
return true;
}
// If the transfer queue is empty, rebuild it
if (_pendingTransfers.empty()) {
populateTransferQueue();
}
bool result = false;
if (!_pendingTransfers.empty()) {
#if THREADED_TEXTURE_BUFFERING
// If there is a current transfer, but it's not the top of the pending transfer queue, then it's an orphan, so we want to abandon it.
if (_currentTransferJob && _currentTransferJob != _pendingTransfers.front()) {
_currentTransferJob.reset();
}
if (!_currentTransferJob) {
// Keeping hold of a strong pointer to the transfer job ensures that if the pending transfer queue is rebuilt, the transfer job
// doesn't leave scope, causing a crash in the buffering thread
_currentTransferJob = _pendingTransfers.front();
// Keeping hold of a strong pointer during the transfer ensures that the transfer thread cannot try to access a destroyed texture
_currentTransferTexture = currentTexture;
}
// transfer jobs use asynchronous buffering of the texture data because it may involve disk IO, so we execute a try here to determine if the buffering
// is complete
if (_currentTransferJob->tryTransfer()) {
_pendingTransfers.pop();
// Once a given job is finished, release the shared pointers keeping them alive
_currentTransferTexture.reset();
_currentTransferJob.reset();
result = true;
}
#else
if (_pendingTransfers.front()->tryTransfer()) {
_pendingTransfers.pop();
result = true;
}
#endif
}
return result;
}
#if THREADED_TEXTURE_BUFFERING
void GLVariableAllocationSupport::executeNextBuffer(const TexturePointer& currentTexture) {
if (_currentTransferJob && !_currentTransferJob->bufferingCompleted()) {
return;
}
// If the transfer queue is empty, rebuild it
if (_pendingTransfers.empty()) {
populateTransferQueue();
}
if (!_pendingTransfers.empty()) {
// Keeping hold of a strong pointer during the transfer ensures that the transfer thread cannot try to access a destroyed texture
_currentTransferTexture = currentTexture;
// Keeping hold of a strong pointer to the transfer job ensures that if the pending transfer queue is rebuilt, the transfer job
// doesn't leave scope, causing a crash in the buffering thread
_currentTransferJob = _pendingTransfers.front();
// transfer jobs use asynchronous buffering of the texture data because it may involve disk IO, so we execute a try here to determine if the buffering
// is complete
if (_currentTransferJob->tryTransfer()) {
_pendingTransfers.pop();
_currentTransferTexture.reset();
_currentTransferJob.reset();
if (!_currentTransferJob) {
_currentTransferJob = _pendingTransfers.front();
_currentTransferTexture = currentTexture;
}
_currentTransferJob->startBuffering();
}
}
#endif

View file

@ -8,6 +8,9 @@
#ifndef hifi_gpu_gl_GLTexture_h
#define hifi_gpu_gl_GLTexture_h
#include <QtCore/QThreadPool>
#include <QtConcurrent>
#include "GLShared.h"
#include "GLBackend.h"
#include "GLTexelFormat.h"
@ -47,24 +50,19 @@ public:
class TransferJob {
using VoidLambda = std::function<void()>;
using VoidLambdaQueue = std::queue<VoidLambda>;
using ThreadPointer = std::shared_ptr<std::thread>;
const GLTexture& _parent;
Texture::PixelsPointer _mipData;
size_t _transferOffset { 0 };
size_t _transferSize { 0 };
// Indicates if a transfer from backing storage to interal storage has started
bool _bufferingStarted { false };
bool _bufferingCompleted { false };
bool _bufferingRequired { true };
VoidLambda _transferLambda;
VoidLambda _bufferingLambda;
#if THREADED_TEXTURE_BUFFERING
static Mutex _mutex;
static VoidLambdaQueue _bufferLambdaQueue;
static ThreadPointer _bufferThread;
static std::atomic<bool> _shutdownBufferingThread;
static void bufferLoop();
// Indicates if a transfer from backing storage to interal storage has started
QFuture<void> _bufferingStatus;
static QThreadPool* _bufferThreadPool;
#endif
public:
@ -75,14 +73,13 @@ public:
bool tryTransfer();
#if THREADED_TEXTURE_BUFFERING
static void startTransferLoop();
static void stopTransferLoop();
void startBuffering();
bool bufferingRequired() const;
bool bufferingCompleted() const;
static void startBufferingThread();
#endif
private:
#if THREADED_TEXTURE_BUFFERING
void startBuffering();
#endif
void transfer();
};
@ -100,8 +97,10 @@ protected:
static WorkQueue _transferQueue;
static WorkQueue _promoteQueue;
static WorkQueue _demoteQueue;
#if THREADED_TEXTURE_BUFFERING
static TexturePointer _currentTransferTexture;
static TransferJobPointer _currentTransferJob;
#endif
static const uvec3 INITIAL_MIP_TRANSFER_DIMENSIONS;
static const uvec3 MAX_TRANSFER_DIMENSIONS;
static const size_t MAX_TRANSFER_SIZE;
@ -109,6 +108,8 @@ protected:
static void updateMemoryPressure();
static void processWorkQueues();
static void processWorkQueue(WorkQueue& workQueue);
static TexturePointer getNextWorkQueueItem(WorkQueue& workQueue);
static void addToWorkQueue(const TexturePointer& texture);
static WorkQueue& getActiveWorkQueue();
@ -118,7 +119,10 @@ protected:
bool canPromote() const { return _allocatedMip > _minAllocatedMip; }
bool canDemote() const { return _allocatedMip < _maxAllocatedMip; }
bool hasPendingTransfers() const { return _populatedMip > _allocatedMip; }
void executeNextTransfer(const TexturePointer& currentTexture);
#if THREADED_TEXTURE_BUFFERING
void executeNextBuffer(const TexturePointer& currentTexture);
#endif
bool executeNextTransfer(const TexturePointer& currentTexture);
virtual void populateTransferQueue() = 0;
virtual void promote() = 0;
virtual void demote() = 0;

View file

@ -17,7 +17,6 @@
#include <thread>
#define INCREMENTAL_TRANSFER 0
#define THREADED_TEXTURE_BUFFERING 1
#define GPU_SSBO_TRANSFORM_OBJECT 1
namespace gpu { namespace gl45 {

View file

@ -17,6 +17,7 @@
#include <unordered_set>
#include <QDir>
#include <QSaveFile>
#include <PathUtils.h>
@ -110,13 +111,14 @@ FilePointer FileCache::writeFile(const char* data, File::Metadata&& metadata) {
return file;
}
// write the new file
FILE* saveFile = fopen(filepath.c_str(), "wb");
if (saveFile != nullptr && fwrite(data, metadata.length, 1, saveFile) && fclose(saveFile) == 0) {
QSaveFile saveFile(QString::fromStdString(filepath));
if (saveFile.open(QIODevice::WriteOnly)
&& saveFile.write(data, metadata.length) == static_cast<qint64>(metadata.length)
&& saveFile.commit()) {
file = addFile(std::move(metadata), filepath);
} else {
qCWarning(file_cache, "[%s] Failed to write %s (%s)", _dirname.c_str(), metadata.key.c_str(), strerror(errno));
errno = 0;
qCWarning(file_cache, "[%s] Failed to write %s", _dirname.c_str(), metadata.key.c_str());
}
return file;

View file

@ -1376,7 +1376,9 @@ function MyController(hand) {
visible: true,
alpha: 1,
parentID: AVATAR_SELF_ID,
parentJointIndex: this.controllerJointIndex,
parentJointIndex: MyAvatar.getJointIndex(this.hand === RIGHT_HAND ?
"_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" :
"_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"),
endParentID: farParentID
};
this.overlayLine = Overlays.addOverlay("line3d", lineProperties);

View file

@ -366,6 +366,8 @@
return nearestAvatar;
}
function messageSend(message) {
// we always append whether or not we are logged in...
message.isLoggedIn = Account.isLoggedIn();
Messages.sendMessage(MESSAGE_CHANNEL, JSON.stringify(message));
}
function handStringMessageSend(message) {
@ -463,7 +465,9 @@
endHandshakeAnimation();
// No-op if we were successful, but this way we ensure that failures and abandoned handshakes don't leave us
// in a weird state.
request({ uri: requestUrl, method: 'DELETE' }, debug);
if (Account.isLoggedIn()) {
request({ uri: requestUrl, method: 'DELETE' }, debug);
}
}
function updateTriggers(value, fromKeyboard, hand) {
@ -590,7 +594,7 @@
}
}
function makeConnection(id) {
function makeConnection(id, isLoggedIn) {
// send done to let the connection know you have made connection.
messageSend({
key: "done",
@ -606,7 +610,10 @@
// It would be "simpler" to skip this and just look at the response, but:
// 1. We don't want to bother the metaverse with request that we know will fail.
// 2. We don't want our code here to be dependent on precisely how the metaverse responds (400, 401, etc.)
if (!Account.isLoggedIn()) {
// 3. We also don't want to connect to someone who is anonymous _now_, but was not earlier and still has
// the same node id. Since the messaging doesn't say _who_ isn't logged in, fail the same as if we are
// not logged in.
if (!Account.isLoggedIn() || isLoggedIn === false) {
handleConnectionResponseAndMaybeRepeat("401:Unauthorized", {statusCode: 401});
return;
}
@ -628,8 +635,12 @@
// we change states, start the connectionInterval where we check
// to be sure the hand is still close enough. If not, we terminate
// the interval, go back to the waiting state. If we make it
// the entire CONNECTING_TIME, we make the connection.
function startConnecting(id, jointIndex) {
// the entire CONNECTING_TIME, we make the connection. We pass in
// whether or not the connecting id is actually logged in, as now we
// will allow to start the connection process but have it stop with a
// fail message before trying to call the backend if the other guy isn't
// logged in.
function startConnecting(id, jointIndex, isLoggedIn) {
var count = 0;
debug("connecting", id, "hand", jointIndex);
// do we need to do this?
@ -671,7 +682,7 @@
startHandshake();
} else if (count > CONNECTING_TIME / CONNECTING_INTERVAL) {
debug("made connection with " + id);
makeConnection(id);
makeConnection(id, isLoggedIn);
stopConnecting();
}
}, CONNECTING_INTERVAL);
@ -736,7 +747,7 @@
if (state === STATES.WAITING && (!connectingId || connectingId === senderID)) {
if (message.id === MyAvatar.sessionUUID) {
stopWaiting();
startConnecting(senderID, exisitingOrSearchedJointIndex());
startConnecting(senderID, exisitingOrSearchedJointIndex(), message.isLoggedIn);
} else if (connectingId) {
// this is for someone else (we lost race in connectionRequest),
// so lets start over
@ -755,7 +766,7 @@
startHandshake();
break;
}
startConnecting(senderID, connectingHandJointIndex);
startConnecting(senderID, connectingHandJointIndex, message.isLoggedIn);
}
break;
case "done":
@ -775,7 +786,7 @@
} else {
// they just created a connection request to us, and we are connecting to
// them, so lets just stop connecting and make connection..
makeConnection(connectingId);
makeConnection(connectingId, message.isLoggedIn);
stopConnecting();
}
} else {

View file

@ -1,4 +1,5 @@
"use strict";
/*jslint nomen: true, plusplus: true, vars: true*/
/*global AvatarList, Entities, EntityViewer, Script, SoundCache, Audio, print, randFloat*/
//
@ -38,19 +39,27 @@ var DEFAULT_SOUND_DATA = {
playbackGapRange: 0 // in ms
};
//var AGENT_AVATAR_POSITION = { x: -1.5327, y: 0.672515, z: 5.91573 };
var AGENT_AVATAR_POSITION = { x: -2.83785, y: 1.45243, z: -13.6042 };
//var isACScript = this.EntityViewer !== undefined;
var isACScript = true;
Script.include("http://hifi-content.s3.amazonaws.com/ryan/development/utils_ryan.js");
if (isACScript) {
Agent.isAvatar = true; // This puts a robot at 0,0,0, but is currently necessary in order to use AvatarList.
Avatar.skeletonModelURL = "http://hifi-content.s3.amazonaws.com/ozan/dev/avatars/invisible_avatar/invisible_avatar.fst";
Avatar.position = AGENT_AVATAR_POSITION;
Agent.isListeningToAudioStream = true;
}
function ignore() {}
function debug() { // Display the arguments not just [Object object].
//print.apply(null, [].map.call(arguments, JSON.stringify));
}
function randFloat(low, high) {
return low + Math.random() * (high - low);
}
if (isACScript) {
EntityViewer.setCenterRadius(QUERY_RADIUS);
}
@ -93,6 +102,7 @@ function EntityDatum(entityIdentifier) { // Just the data of an entity that we n
return;
}
var properties, soundData; // Latest data, pulled from local octree.
// getEntityProperties locks the tree, which competes with the asynchronous processing of queryOctree results.
// Most entity updates are fast and only a very few do getEntityProperties.
function ensureSoundData() { // We only getEntityProperities when we need to.
@ -115,43 +125,54 @@ function EntityDatum(entityIdentifier) { // Just the data of an entity that we n
}
}
}
// Stumbling on big new pile of entities will do a lot of getEntityProperties. Once.
if (that.lastUserDataUpdate < userDataCutoff) { // NO DATA => SOUND DATA
ensureSoundData();
}
if (!that.url) { // NO DATA => NO DATA
return that.stop();
}
if (!that.sound) { // SOUND DATA => DOWNLOADING
that.sound = SoundCache.getSound(soundData.url); // SoundCache can manage duplicates better than we can.
}
if (!that.sound.downloaded) { // DOWNLOADING => DOWNLOADING
return;
}
if (that.playAfter > now) { // DOWNLOADING | WAITING => WAITING
return;
}
ensureSoundData(); // We'll try to play/setOptions and will need position, so we might as well get soundData, too.
if (soundData.url !== that.url) { // WAITING => NO DATA (update next time around)
return that.stop();
}
var options = {
position: properties.position,
loop: soundData.loop || DEFAULT_SOUND_DATA.loop,
volume: soundData.volume || DEFAULT_SOUND_DATA.volume
};
function repeat() {
return !options.loop && (soundData.playbackGap >= 0);
}
function randomizedNextPlay() { // time of next play or recheck, randomized to distribute the work
var range = soundData.playbackGapRange || DEFAULT_SOUND_DATA.playbackGapRange,
base = repeat() ? ((that.sound.duration * MSEC_PER_SEC) + (soundData.playbackGap || DEFAULT_SOUND_DATA.playbackGap)) : RECHECK_TIME;
return now + base + randFloat(-Math.min(base, range), range);
}
if (that.injector && soundData.playing === false) {
that.injector.stop();
that.injector = null;
}
if (!that.injector) {
if (soundData.playing === false) { // WAITING => PLAYING | WAITING
return;
@ -165,6 +186,7 @@ function EntityDatum(entityIdentifier) { // Just the data of an entity that we n
}
return;
}
that.injector.setOptions(options); // PLAYING => UPDATE POSITION ETC
if (!that.injector.playing) { // Subtle: a looping sound will not check playbackGap.
if (repeat()) { // WAITING => PLAYING
@ -178,6 +200,7 @@ function EntityDatum(entityIdentifier) { // Just the data of an entity that we n
}
};
}
function internEntityDatum(entityIdentifier, timestamp, avatarPosition, avatar) {
ignore(avatarPosition, avatar); // We could use avatars and/or avatarPositions to prioritize which ones to play.
var entitySound = entityCache[entityIdentifier];
@ -186,7 +209,9 @@ function internEntityDatum(entityIdentifier, timestamp, avatarPosition, avatar)
}
entitySound.timestamp = timestamp; // Might be updated for multiple avatars. That's fine.
}
var nUpdates = UPDATES_PER_STATS_LOG, lastStats = Date.now();
function updateAllEntityData() { // A fast update of all entities we know about. A few make sounds.
var now = Date.now(),
expirationCutoff = now - EXPIRATION_TIME,